mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 09:33:21 +00:00
Add support for multiple body content types / examples (#2991)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@gitbook/react-openapi": patch
|
||||||
|
"gitbook": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Support body examples
|
||||||
@@ -392,7 +392,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.openapi-codesample-footer {
|
.openapi-codesample-footer {
|
||||||
@apply flex w-full justify-end;
|
@apply flex gap-3 w-full justify-between flex-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openapi-codesample-selectors {
|
||||||
|
@apply flex flex-row items-center gap-3 flex-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Path */
|
/* Path */
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
|
import {
|
||||||
|
OpenAPIMediaTypeExamplesBody,
|
||||||
|
OpenAPIMediaTypeExamplesSelector,
|
||||||
|
} from './OpenAPICodeSampleInteractive';
|
||||||
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
||||||
import { ScalarApiButton } from './ScalarApiButton';
|
import { ScalarApiButton } from './ScalarApiButton';
|
||||||
import { StaticSection } from './StaticSection';
|
import { StaticSection } from './StaticSection';
|
||||||
import { type CodeSampleInput, codeSampleGenerators } from './code-samples';
|
import { type CodeSampleGenerator, codeSampleGenerators } from './code-samples';
|
||||||
import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
|
import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
||||||
import { getDefaultServerURL } from './util/server';
|
import { getDefaultServerURL } from './util/server';
|
||||||
import { checkIsReference, createStateKey } from './utils';
|
import { checkIsReference, createStateKey } from './utils';
|
||||||
|
|
||||||
|
const CUSTOM_CODE_SAMPLES_KEYS = ['x-custom-examples', 'x-code-samples', 'x-codeSamples'] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display code samples to execute the operation.
|
* Display code samples to execute the operation.
|
||||||
* It supports the Redocly custom syntax as well (https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples/)
|
* It supports the Redocly custom syntax as well (https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples/)
|
||||||
@@ -16,6 +22,43 @@ import { checkIsReference, createStateKey } from './utils';
|
|||||||
export function OpenAPICodeSample(props: {
|
export function OpenAPICodeSample(props: {
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContextProps;
|
||||||
|
}) {
|
||||||
|
const { data } = props;
|
||||||
|
|
||||||
|
// If code samples are disabled at operation level, we don't display the code samples.
|
||||||
|
if (data.operation['x-codeSamples'] === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const customCodeSamples = getCustomCodeSamples(props);
|
||||||
|
|
||||||
|
// If code samples are disabled at the top-level and not custom code samples are defined,
|
||||||
|
// we don't display the code samples.
|
||||||
|
if (data['x-codeSamples'] === false && !customCodeSamples) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const samples = customCodeSamples ?? generateCodeSamples(props);
|
||||||
|
|
||||||
|
if (samples.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OpenAPITabs stateKey={createStateKey('codesample')} items={samples}>
|
||||||
|
<StaticSection header={<OpenAPITabsList />} className="openapi-codesample">
|
||||||
|
<OpenAPITabsPanels />
|
||||||
|
</StaticSection>
|
||||||
|
</OpenAPITabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate code samples for the operation.
|
||||||
|
*/
|
||||||
|
function generateCodeSamples(props: {
|
||||||
|
data: OpenAPIOperationData;
|
||||||
|
context: OpenAPIContextProps;
|
||||||
}) {
|
}) {
|
||||||
const { data, context } = props;
|
const { data, context } = props;
|
||||||
|
|
||||||
@@ -51,46 +94,137 @@ export function OpenAPICodeSample(props: {
|
|||||||
const requestBody = !checkIsReference(data.operation.requestBody)
|
const requestBody = !checkIsReference(data.operation.requestBody)
|
||||||
? data.operation.requestBody
|
? data.operation.requestBody
|
||||||
: undefined;
|
: undefined;
|
||||||
const requestBodyContentEntries = requestBody?.content
|
|
||||||
? Object.entries(requestBody.content)
|
|
||||||
: undefined;
|
|
||||||
const requestBodyContent = requestBodyContentEntries?.[0];
|
|
||||||
|
|
||||||
const input: CodeSampleInput = {
|
const url =
|
||||||
url:
|
getDefaultServerURL(data.servers) +
|
||||||
getDefaultServerURL(data.servers) +
|
data.path +
|
||||||
data.path +
|
(searchParams.size ? `?${searchParams.toString()}` : '');
|
||||||
(searchParams.size ? `?${searchParams.toString()}` : ''),
|
|
||||||
method: data.method,
|
const genericHeaders = {
|
||||||
body: requestBodyContent ? generateMediaTypeExample(requestBodyContent[1]) : undefined,
|
...getSecurityHeaders(data.securities),
|
||||||
headers: {
|
...headersObject,
|
||||||
...getSecurityHeaders(data.securities),
|
|
||||||
...headersObject,
|
|
||||||
...(requestBodyContent
|
|
||||||
? {
|
|
||||||
'Content-Type': requestBodyContent[0],
|
|
||||||
}
|
|
||||||
: undefined),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const autoCodeSamples = codeSampleGenerators.map((generator) => ({
|
const mediaTypeRendererFactories = Object.entries(requestBody?.content ?? {}).map(
|
||||||
key: `default-${generator.id}`,
|
([mediaType, mediaTypeObject]) => {
|
||||||
label: generator.label,
|
return (generator: CodeSampleGenerator) => {
|
||||||
body: context.renderCodeBlock({
|
const mediaTypeHeaders = {
|
||||||
code: generator.generate(input),
|
...genericHeaders,
|
||||||
syntax: generator.syntax,
|
'Content-Type': mediaType,
|
||||||
}),
|
};
|
||||||
footer: <OpenAPICodeSampleFooter data={data} context={context} />,
|
return {
|
||||||
}));
|
mediaType,
|
||||||
|
element: context.renderCodeBlock({
|
||||||
|
code: generator.generate({
|
||||||
|
url,
|
||||||
|
method: data.method,
|
||||||
|
body: undefined,
|
||||||
|
headers: mediaTypeHeaders,
|
||||||
|
}),
|
||||||
|
syntax: generator.syntax,
|
||||||
|
}),
|
||||||
|
examples: generateMediaTypeExamples(mediaTypeObject).map((example) => ({
|
||||||
|
example,
|
||||||
|
element: context.renderCodeBlock({
|
||||||
|
code: generator.generate({
|
||||||
|
url,
|
||||||
|
method: data.method,
|
||||||
|
body: example.value,
|
||||||
|
headers: mediaTypeHeaders,
|
||||||
|
}),
|
||||||
|
syntax: generator.syntax,
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
} satisfies MediaTypeRenderer;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return codeSampleGenerators.map((generator) => {
|
||||||
|
if (mediaTypeRendererFactories.length > 0) {
|
||||||
|
const renderers = mediaTypeRendererFactories.map((generate) => generate(generator));
|
||||||
|
return {
|
||||||
|
key: `default-${generator.id}`,
|
||||||
|
label: generator.label,
|
||||||
|
body: <OpenAPIMediaTypeExamplesBody data={data} renderers={renderers} />,
|
||||||
|
footer: (
|
||||||
|
<OpenAPICodeSampleFooter renderers={renderers} data={data} context={context} />
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: `default-${generator.id}`,
|
||||||
|
label: generator.label,
|
||||||
|
body: context.renderCodeBlock({
|
||||||
|
code: generator.generate({
|
||||||
|
url,
|
||||||
|
method: data.method,
|
||||||
|
body: undefined,
|
||||||
|
headers: genericHeaders,
|
||||||
|
}),
|
||||||
|
syntax: generator.syntax,
|
||||||
|
}),
|
||||||
|
footer: <OpenAPICodeSampleFooter data={data} renderers={[]} context={context} />,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaTypeRenderer {
|
||||||
|
mediaType: string;
|
||||||
|
element: React.ReactNode;
|
||||||
|
examples: Array<{
|
||||||
|
example: OpenAPIV3.ExampleObject;
|
||||||
|
element: React.ReactNode;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function OpenAPICodeSampleFooter(props: {
|
||||||
|
data: OpenAPIOperationData;
|
||||||
|
renderers: MediaTypeRenderer[];
|
||||||
|
context: OpenAPIContextProps;
|
||||||
|
}) {
|
||||||
|
const { data, context, renderers } = props;
|
||||||
|
const { method, path } = data;
|
||||||
|
const { specUrl } = context;
|
||||||
|
const hideTryItPanel = data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'];
|
||||||
|
const hasMediaTypes = renderers.length > 0;
|
||||||
|
|
||||||
|
if (hideTryItPanel && !hasMediaTypes) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateHttpMethod(method)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="openapi-codesample-footer">
|
||||||
|
{hasMediaTypes ? (
|
||||||
|
<OpenAPIMediaTypeExamplesSelector data={data} renderers={renderers} />
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{!hideTryItPanel && <ScalarApiButton method={method} path={path} specUrl={specUrl} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom code samples for the operation.
|
||||||
|
*/
|
||||||
|
function getCustomCodeSamples(props: {
|
||||||
|
data: OpenAPIOperationData;
|
||||||
|
context: OpenAPIContextProps;
|
||||||
|
}) {
|
||||||
|
const { data, context } = props;
|
||||||
|
|
||||||
// Use custom samples if defined
|
|
||||||
let customCodeSamples: null | Array<{
|
let customCodeSamples: null | Array<{
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
body: React.ReactNode;
|
body: React.ReactNode;
|
||||||
}> = null;
|
}> = null;
|
||||||
(['x-custom-examples', 'x-code-samples', 'x-codeSamples'] as const).forEach((key) => {
|
|
||||||
|
CUSTOM_CODE_SAMPLES_KEYS.forEach((key) => {
|
||||||
const customSamples = data.operation[key];
|
const customSamples = data.operation[key];
|
||||||
if (customSamples && Array.isArray(customSamples)) {
|
if (customSamples && Array.isArray(customSamples)) {
|
||||||
customCodeSamples = customSamples
|
customCodeSamples = customSamples
|
||||||
@@ -102,58 +236,20 @@ export function OpenAPICodeSample(props: {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((sample, index) => ({
|
.map((sample, index) => ({
|
||||||
key: `redocly-${sample.lang}-${index}`,
|
key: `custom-sample-${sample.lang}-${index}`,
|
||||||
label: sample.label,
|
label: sample.label,
|
||||||
body: context.renderCodeBlock({
|
body: context.renderCodeBlock({
|
||||||
code: sample.source,
|
code: sample.source,
|
||||||
syntax: sample.lang,
|
syntax: sample.lang,
|
||||||
}),
|
}),
|
||||||
footer: <OpenAPICodeSampleFooter data={data} context={context} />,
|
footer: (
|
||||||
|
<OpenAPICodeSampleFooter renderers={[]} data={data} context={context} />
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Code samples can be disabled at the top-level or at the operation level
|
return customCodeSamples;
|
||||||
// If code samples are defined at the operation level, it will override the top-level setting
|
|
||||||
const codeSamplesDisabled =
|
|
||||||
data['x-codeSamples'] === false || data.operation['x-codeSamples'] === false;
|
|
||||||
const samples = customCodeSamples ?? (!codeSamplesDisabled ? autoCodeSamples : []);
|
|
||||||
|
|
||||||
if (samples.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OpenAPITabs stateKey={createStateKey('codesample')} items={samples}>
|
|
||||||
<StaticSection header={<OpenAPITabsList />} className="openapi-codesample">
|
|
||||||
<OpenAPITabsPanels />
|
|
||||||
</StaticSection>
|
|
||||||
</OpenAPITabs>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function OpenAPICodeSampleFooter(props: {
|
|
||||||
data: OpenAPIOperationData;
|
|
||||||
context: OpenAPIContextProps;
|
|
||||||
}) {
|
|
||||||
const { data, context } = props;
|
|
||||||
const { method, path } = data;
|
|
||||||
const { specUrl } = context;
|
|
||||||
const hideTryItPanel = data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'];
|
|
||||||
|
|
||||||
if (hideTryItPanel) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateHttpMethod(method)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="openapi-codesample-footer">
|
|
||||||
<ScalarApiButton method={method} path={path} specUrl={specUrl} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
|
function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
'use client';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useStore } from 'zustand';
|
||||||
|
import type { MediaTypeRenderer } from './OpenAPICodeSample';
|
||||||
|
import type { OpenAPIOperationData } from './types';
|
||||||
|
import { getOrCreateTabStoreByKey } from './useSyncedTabsGlobalState';
|
||||||
|
|
||||||
|
function useMediaTypeState(data: OpenAPIOperationData, defaultKey: string) {
|
||||||
|
const { method, path } = data;
|
||||||
|
const store = useStore(getOrCreateTabStoreByKey(`media-type-${method}-${path}`, defaultKey));
|
||||||
|
if (typeof store.tabKey !== 'string') {
|
||||||
|
throw new Error('Media type key is not a string');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mediaType: store.tabKey,
|
||||||
|
setMediaType: useCallback((index: string) => store.setTabKey(index), [store.setTabKey]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function useMediaTypeSampleIndexState(data: OpenAPIOperationData, mediaType: string) {
|
||||||
|
const { method, path } = data;
|
||||||
|
const store = useStore(
|
||||||
|
getOrCreateTabStoreByKey(`media-type-sample-${mediaType}-${method}-${path}`, 0)
|
||||||
|
);
|
||||||
|
if (typeof store.tabKey !== 'number') {
|
||||||
|
throw new Error('Example key is not a number');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
index: store.tabKey,
|
||||||
|
setIndex: useCallback((index: number) => store.setTabKey(index), [store.setTabKey]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OpenAPIMediaTypeExamplesSelector(props: {
|
||||||
|
data: OpenAPIOperationData;
|
||||||
|
renderers: MediaTypeRenderer[];
|
||||||
|
}) {
|
||||||
|
const { data, renderers } = props;
|
||||||
|
if (!renderers[0]) {
|
||||||
|
throw new Error('No renderers provided');
|
||||||
|
}
|
||||||
|
const state = useMediaTypeState(data, renderers[0].mediaType);
|
||||||
|
const selected = renderers.find((r) => r.mediaType === state.mediaType) || renderers[0];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="openapi-codesample-selectors">
|
||||||
|
<select
|
||||||
|
className={clsx('openapi-select')}
|
||||||
|
value={state.mediaType}
|
||||||
|
onChange={(e) => state.setMediaType(e.target.value)}
|
||||||
|
>
|
||||||
|
{renderers.map((renderer) => (
|
||||||
|
<option key={renderer.mediaType} value={renderer.mediaType}>
|
||||||
|
{renderer.mediaType}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<ExamplesSelector data={data} renderer={selected} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExamplesSelector(props: {
|
||||||
|
data: OpenAPIOperationData;
|
||||||
|
renderer: MediaTypeRenderer;
|
||||||
|
}) {
|
||||||
|
const { data, renderer } = props;
|
||||||
|
const state = useMediaTypeSampleIndexState(data, renderer.mediaType);
|
||||||
|
if (renderer.examples.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
className={clsx('openapi-select')}
|
||||||
|
value={String(state.index)}
|
||||||
|
onChange={(e) => state.setIndex(Number(e.target.value))}
|
||||||
|
>
|
||||||
|
{renderer.examples.map((example, index) => (
|
||||||
|
<option key={index} value={index}>
|
||||||
|
{example.example.summary || `Example ${index + 1}`}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OpenAPIMediaTypeExamplesBody(props: {
|
||||||
|
data: OpenAPIOperationData;
|
||||||
|
renderers: MediaTypeRenderer[];
|
||||||
|
}) {
|
||||||
|
const { renderers, data } = props;
|
||||||
|
if (!renderers[0]) {
|
||||||
|
throw new Error('No renderers provided');
|
||||||
|
}
|
||||||
|
const mediaTypeState = useMediaTypeState(data, renderers[0].mediaType);
|
||||||
|
const selected =
|
||||||
|
renderers.find((r) => r.mediaType === mediaTypeState.mediaType) ?? renderers[0];
|
||||||
|
if (selected.examples.length === 0) {
|
||||||
|
return selected.element;
|
||||||
|
}
|
||||||
|
return <ExamplesBody data={data} renderer={selected} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExamplesBody(props: { data: OpenAPIOperationData; renderer: MediaTypeRenderer }) {
|
||||||
|
const { data, renderer } = props;
|
||||||
|
const exampleState = useMediaTypeSampleIndexState(data, renderer.mediaType);
|
||||||
|
const example = renderer.examples[exampleState.index] ?? renderer.examples[0];
|
||||||
|
if (!example) {
|
||||||
|
throw new Error(`No example found for index ${exampleState.index}`);
|
||||||
|
}
|
||||||
|
return example.element;
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ export interface CodeSampleInput {
|
|||||||
body?: any;
|
body?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CodeSampleGenerator {
|
export interface CodeSampleGenerator {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
syntax: string;
|
syntax: string;
|
||||||
@@ -240,7 +240,7 @@ const BodyGenerators = {
|
|||||||
body = `--data '${String(body).replace(/"/g, '')}'`;
|
body = `--data '${String(body).replace(/"/g, '')}'`;
|
||||||
} else if (isXML(contentType) || isCSV(contentType)) {
|
} else if (isXML(contentType) || isCSV(contentType)) {
|
||||||
// We use --data-binary to avoid cURL converting newlines to \r\n
|
// We use --data-binary to avoid cURL converting newlines to \r\n
|
||||||
body = `--data-binary $'${stringifyOpenAPI(body).replace(/"/g, '')}'`;
|
body = `--data-binary $'${stringifyOpenAPI(body).replace(/"/g, '').replace(/\\n/g, '\n')}'`;
|
||||||
} else if (isGraphQL(contentType)) {
|
} else if (isGraphQL(contentType)) {
|
||||||
body = `--data '${stringifyOpenAPI(body)}'`;
|
body = `--data '${stringifyOpenAPI(body)}'`;
|
||||||
// Set Content-Type to application/json for GraphQL, recommended by GraphQL spec
|
// Set Content-Type to application/json for GraphQL, recommended by GraphQL spec
|
||||||
@@ -249,7 +249,7 @@ const BodyGenerators = {
|
|||||||
// We use --data-binary to avoid cURL converting newlines to \r\n
|
// We use --data-binary to avoid cURL converting newlines to \r\n
|
||||||
body = `--data-binary '@${String(body)}'`;
|
body = `--data-binary '@${String(body)}'`;
|
||||||
} else {
|
} else {
|
||||||
body = `--data '${stringifyOpenAPI(body, null, 2)}'`;
|
body = `--data '${stringifyOpenAPI(body, null, 2).replace(/\\n/g, '\n')}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
|
import { checkIsReference } from './utils';
|
||||||
|
|
||||||
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
||||||
|
|
||||||
@@ -28,29 +29,39 @@ export function generateSchemaExample(
|
|||||||
/**
|
/**
|
||||||
* Generate an example for a media type.
|
* Generate an example for a media type.
|
||||||
*/
|
*/
|
||||||
export function generateMediaTypeExample(
|
export function generateMediaTypeExamples(
|
||||||
mediaType: OpenAPIV3.MediaTypeObject,
|
mediaType: OpenAPIV3.MediaTypeObject,
|
||||||
options?: GenerateSchemaExampleOptions
|
options?: GenerateSchemaExampleOptions
|
||||||
): JSONValue | undefined {
|
): OpenAPIV3.ExampleObject[] {
|
||||||
if (mediaType.example) {
|
if (mediaType.example) {
|
||||||
return mediaType.example;
|
return [{ summary: 'default', value: mediaType.example }];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType.examples) {
|
if (mediaType.examples) {
|
||||||
const key = Object.keys(mediaType.examples)[0];
|
const { examples } = mediaType;
|
||||||
if (key) {
|
const keys = Object.keys(examples);
|
||||||
const example = mediaType.examples[key];
|
if (keys.length > 0) {
|
||||||
if (example) {
|
return keys.reduce<OpenAPIV3.ExampleObject[]>((result, key) => {
|
||||||
return example.value;
|
const example = examples[key];
|
||||||
}
|
if (!example || checkIsReference(example)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
summary: example.summary || key,
|
||||||
|
value: example.value,
|
||||||
|
description: example.description,
|
||||||
|
externalValue: example.externalValue,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType.schema) {
|
if (mediaType.schema) {
|
||||||
return generateSchemaExample(mediaType.schema, options);
|
return [{ summary: 'default', value: generateSchemaExample(mediaType.schema, options) }];
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hard limit for rendering circular references */
|
/** Hard limit for rendering circular references */
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
|
|
||||||
export function checkIsReference(
|
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
||||||
input: unknown
|
|
||||||
): input is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {
|
|
||||||
return typeof input === 'object' && !!input && '$ref' in input;
|
return typeof input === 'object' && !!input && '$ref' in input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user