{
))}
{examplesNames.map(name => (
- {sampleView(examples[name].value)}
+
+
+
))}
);
} else {
const name = examplesNames[0];
- return {sampleView(examples[name].value)}
;
+ return (
+
+
+
+ );
}
}
}
diff --git a/src/components/PayloadSamples/exernalExampleHook.ts b/src/components/PayloadSamples/exernalExampleHook.ts
new file mode 100644
index 00000000..e3e33f08
--- /dev/null
+++ b/src/components/PayloadSamples/exernalExampleHook.ts
@@ -0,0 +1,34 @@
+import { useEffect, useRef, useState } from 'react';
+import { ExampleModel } from '../../services/models/Example';
+
+export function useExternalExample(example: ExampleModel, mimeType: string) {
+ const [, setIsLoading] = useState(true); // to trigger component reload
+
+ const value = useRef(undefined);
+ const prevRef = useRef(undefined);
+
+ if (prevRef.current !== example) {
+ value.current = undefined;
+ }
+
+ prevRef.current = example;
+
+ useEffect(
+ () => {
+ const load = async () => {
+ setIsLoading(true);
+ try {
+ value.current = await example.getExternalValue(mimeType);
+ } catch (e) {
+ value.current = e;
+ }
+ setIsLoading(false);
+ };
+
+ load();
+ },
+ [example, mimeType],
+ );
+
+ return value.current;
+}
diff --git a/src/components/SourceCode/SourceCode.tsx b/src/components/SourceCode/SourceCode.tsx
index b6707663..3b7d4eb2 100644
--- a/src/components/SourceCode/SourceCode.tsx
+++ b/src/components/SourceCode/SourceCode.tsx
@@ -1,19 +1,8 @@
import * as React from 'react';
import { highlight } from '../../utils';
-import { SampleControls, SampleControlsWrap } from '../../common-elements';
+import { SampleControls, SampleControlsWrap, StyledPre } from '../../common-elements';
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
-import { PrismDiv } from '../../common-elements/PrismDiv';
-import styled from '../../styled-components';
-
-const StyledPre = styled(PrismDiv.withComponent('pre'))`
- font-family: ${props => props.theme.typography.code.fontFamily};
- font-size: ${props => props.theme.typography.code.fontSize};
- overflow-x: auto;
- margin: 0;
-
- white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
-`;
export interface SourceCodeProps {
source: string;
diff --git a/src/services/models/Example.ts b/src/services/models/Example.ts
index f043bccd..a142a956 100644
--- a/src/services/models/Example.ts
+++ b/src/services/models/Example.ts
@@ -1,14 +1,55 @@
+import { resolve as urlResolve } from 'url';
+
import { OpenAPIExample, Referenced } from '../../types';
+import { isJsonLike } from '../../utils/openapi';
import { OpenAPIParser } from '../OpenAPIParser';
+const externalExamplesCache: { [url: string]: Promise } = {};
+
export class ExampleModel {
value: any;
summary?: string;
description?: string;
- externalValue?: string;
+ externalValueUrl?: string;
constructor(parser: OpenAPIParser, infoOrRef: Referenced) {
- Object.assign(this, parser.deref(infoOrRef));
+ const example = parser.deref(infoOrRef);
+ this.value = example.value;
+ this.summary = example.summary;
+ this.description = example.description;
+ if (example.externalValue) {
+ this.externalValueUrl = urlResolve(parser.specUrl || '', example.externalValue);
+ }
parser.exitRef(infoOrRef);
}
+
+ getExternalValue(mimeType: string): Promise {
+ if (!this.externalValueUrl) {
+ return Promise.resolve(undefined);
+ }
+
+ if (externalExamplesCache[this.externalValueUrl]) {
+ return externalExamplesCache[this.externalValueUrl];
+ }
+
+ externalExamplesCache[this.externalValueUrl] = fetch(this.externalValueUrl).then(res => {
+ return res.text().then(txt => {
+ if (!res.ok) {
+ return Promise.reject(new Error(txt));
+ }
+
+ if (isJsonLike(mimeType)) {
+ try {
+ return JSON.parse(txt);
+ } catch (e) {
+ return txt;
+ }
+ } else {
+ return txt;
+ }
+ });
+ });
+
+ return externalExamplesCache[this.externalValueUrl];
+ }
}
diff --git a/src/services/models/MediaType.ts b/src/services/models/MediaType.ts
index 941a35fb..5b1ab5d2 100644
--- a/src/services/models/MediaType.ts
+++ b/src/services/models/MediaType.ts
@@ -1,6 +1,6 @@
import * as Sampler from 'openapi-sampler';
-import { OpenAPIExample, OpenAPIMediaType } from '../../types';
+import { OpenAPIMediaType } from '../../types';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { SchemaModel } from './Schema';
@@ -9,7 +9,7 @@ import { OpenAPIParser } from '../OpenAPIParser';
import { ExampleModel } from './Example';
export class MediaTypeModel {
- examples?: { [name: string]: OpenAPIExample };
+ examples?: { [name: string]: ExampleModel };
schema?: SchemaModel;
name: string;
isRequestType: boolean;
@@ -33,7 +33,7 @@ export class MediaTypeModel {
this.examples = mapValues(info.examples, example => new ExampleModel(parser, example));
} else if (info.example !== undefined) {
this.examples = {
- default: new ExampleModel(parser, { value: info.example }),
+ default: new ExampleModel(parser, { value: parser.shalowDeref(info.example) }),
};
} else if (isJsonLike(name)) {
this.generateExample(parser, info);
@@ -49,28 +49,20 @@ export class MediaTypeModel {
if (this.schema && this.schema.oneOf) {
this.examples = {};
for (const subSchema of this.schema.oneOf) {
- const sample = Sampler.sample(
- subSchema.rawSchema,
- samplerOptions,
- parser.spec,
- );
+ const sample = Sampler.sample(subSchema.rawSchema, samplerOptions, parser.spec);
if (this.schema.discriminatorProp && typeof sample === 'object' && sample) {
sample[this.schema.discriminatorProp] = subSchema.title;
}
- this.examples[subSchema.title] = {
+ this.examples[subSchema.title] = new ExampleModel(parser, {
value: sample,
- };
+ });
}
} else if (this.schema) {
this.examples = {
default: new ExampleModel(parser, {
- value: Sampler.sample(
- info.schema,
- samplerOptions,
- parser.spec,
- ),
+ value: Sampler.sample(info.schema, samplerOptions, parser.spec),
}),
};
}