mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 17:43:24 +00:00
Support for OpenAPI Array request body (#3420)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'@gitbook/react-openapi': patch
|
||||||
|
'gitbook': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Support for OpenAPI Array request body
|
||||||
@@ -358,6 +358,11 @@
|
|||||||
|
|
||||||
.openapi-requestbody-header-content {
|
.openapi-requestbody-header-content {
|
||||||
/* unstyled */
|
/* unstyled */
|
||||||
|
@apply flex flex-row items-center gap-2.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openapi-requestbody-header-type {
|
||||||
|
@apply text-tint select-text text-[0.813rem] font-mono font-normal [word-spacing:-0.25rem];
|
||||||
}
|
}
|
||||||
|
|
||||||
.openapi-requestbody-description.openapi-markdown {
|
.openapi-requestbody-description.openapi-markdown {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
|
import { OpenAPIRequestBodyHeaderType } from './OpenAPIRequestBodyHeaderType';
|
||||||
import { OpenAPIRootSchema } from './OpenAPISchemaServer';
|
import { OpenAPIRootSchema } from './OpenAPISchemaServer';
|
||||||
import type { OpenAPIClientContext } from './context';
|
import type { OpenAPIClientContext } from './context';
|
||||||
import { t } from './translate';
|
import { t } from './translate';
|
||||||
@@ -20,11 +21,18 @@ export function OpenAPIRequestBody(props: {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stateKey = createStateKey('request-body-media-type', context.blockKey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection
|
||||||
header={t(context.translation, 'name' in data ? 'payload' : 'body')}
|
header={
|
||||||
|
<>
|
||||||
|
<span>{t(context.translation, 'name' in data ? 'payload' : 'body')}</span>
|
||||||
|
<OpenAPIRequestBodyHeaderType requestBody={requestBody} stateKey={stateKey} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
className="openapi-requestbody"
|
className="openapi-requestbody"
|
||||||
stateKey={createStateKey('request-body-media-type', context.blockKey)}
|
stateKey={stateKey}
|
||||||
selectIcon={context.icons.chevronDown}
|
selectIcon={context.icons.chevronDown}
|
||||||
tabs={Object.entries(requestBody.content ?? {}).map(
|
tabs={Object.entries(requestBody.content ?? {}).map(
|
||||||
([contentType, mediaTypeObject]) => {
|
([contentType, mediaTypeObject]) => {
|
||||||
@@ -35,6 +43,7 @@ export function OpenAPIRequestBody(props: {
|
|||||||
<OpenAPIRootSchema
|
<OpenAPIRootSchema
|
||||||
schema={mediaTypeObject.schema ?? {}}
|
schema={mediaTypeObject.schema ?? {}}
|
||||||
context={context}
|
context={context}
|
||||||
|
key={contentType}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
|
import { useSelectState } from './OpenAPISelect';
|
||||||
|
import { getSchemaTitle } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the type of a request body. It only displays the type if the selected content is an array.
|
||||||
|
*/
|
||||||
|
export function OpenAPIRequestBodyHeaderType(props: {
|
||||||
|
requestBody: OpenAPIV3.RequestBodyObject;
|
||||||
|
stateKey: string;
|
||||||
|
}) {
|
||||||
|
const { requestBody, stateKey } = props;
|
||||||
|
const content = requestBody.content ?? {};
|
||||||
|
const state = useSelectState(stateKey, Object.keys(content)[0]);
|
||||||
|
|
||||||
|
const selectedContentMediaType = Object.entries(content).find(
|
||||||
|
([contentType]) => contentType === state.key
|
||||||
|
)?.[1];
|
||||||
|
|
||||||
|
// If the selected content is not an array, we don't display the type
|
||||||
|
if (
|
||||||
|
!selectedContentMediaType ||
|
||||||
|
!selectedContentMediaType.schema?.type ||
|
||||||
|
selectedContentMediaType.schema.type !== 'array'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="openapi-requestbody-header-type">
|
||||||
|
{`${getSchemaTitle(selectedContentMediaType.schema)}`}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import { retrocycle } from './decycle';
|
|||||||
import { getDisclosureLabel } from './getDisclosureLabel';
|
import { getDisclosureLabel } from './getDisclosureLabel';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import { tString } from './translate';
|
import { tString } from './translate';
|
||||||
import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
|
import { checkIsReference, getSchemaTitle, resolveDescription, resolveFirstExample } from './utils';
|
||||||
|
|
||||||
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
||||||
|
|
||||||
@@ -652,40 +652,3 @@ function mergeRequiredFields(
|
|||||||
new Set([...(latestAncestor?.required || []), ...(schemaOrRef.required || [])])
|
new Set([...(latestAncestor?.required || []), ...(schemaOrRef.required || [])])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
|
|
||||||
// Otherwise try to infer a nice title
|
|
||||||
let type = 'any';
|
|
||||||
|
|
||||||
if (schema.enum || schema['x-enumDescriptions'] || schema['x-gitbook-enum']) {
|
|
||||||
type = `${schema.type} · enum`;
|
|
||||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
|
||||||
} else if (schema.type === 'array' && !!schema.items) {
|
|
||||||
type = `${getSchemaTitle(schema.items)}[]`;
|
|
||||||
} else if (Array.isArray(schema.type)) {
|
|
||||||
type = schema.type.join(' | ');
|
|
||||||
} else if (schema.type || schema.properties) {
|
|
||||||
type = schema.type ?? 'object';
|
|
||||||
|
|
||||||
if (schema.format) {
|
|
||||||
type += ` · ${schema.format}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only add the title if it's an object (no need for the title of a string, number, etc.)
|
|
||||||
if (type === 'object' && schema.title) {
|
|
||||||
type += ` · ${schema.title.replaceAll(' ', '')}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('anyOf' in schema) {
|
|
||||||
type = 'any of';
|
|
||||||
} else if ('oneOf' in schema) {
|
|
||||||
type = 'one of';
|
|
||||||
} else if ('allOf' in schema) {
|
|
||||||
type = 'all of';
|
|
||||||
} else if ('not' in schema) {
|
|
||||||
type = 'not';
|
|
||||||
}
|
|
||||||
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ interface OpenAPISelectProps<T extends OpenAPISelectItem> extends Omit<SelectPro
|
|||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSelectState(stateKey = 'select-state', initialKey?: Key) {
|
export function useSelectState(stateKey = 'select-state', initialKey: Key = 'default') {
|
||||||
const store = useStore(getOrCreateStoreByKey(stateKey, initialKey));
|
const store = useStore(getOrCreateStoreByKey(stateKey, initialKey));
|
||||||
return {
|
return {
|
||||||
key: store.key,
|
key: store.key,
|
||||||
|
|||||||
@@ -216,3 +216,40 @@ function getStatusCodeCategory(statusCode: number | string): number | string {
|
|||||||
|
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
|
||||||
|
// Otherwise try to infer a nice title
|
||||||
|
let type = 'any';
|
||||||
|
|
||||||
|
if (schema.enum || schema['x-enumDescriptions'] || schema['x-gitbook-enum']) {
|
||||||
|
type = `${schema.type} · enum`;
|
||||||
|
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||||
|
} else if (schema.type === 'array' && !!schema.items) {
|
||||||
|
type = `${getSchemaTitle(schema.items)}[]`;
|
||||||
|
} else if (Array.isArray(schema.type)) {
|
||||||
|
type = schema.type.join(' | ');
|
||||||
|
} else if (schema.type || schema.properties) {
|
||||||
|
type = schema.type ?? 'object';
|
||||||
|
|
||||||
|
if (schema.format) {
|
||||||
|
type += ` · ${schema.format}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add the title if it's an object (no need for the title of a string, number, etc.)
|
||||||
|
if (type === 'object' && schema.title) {
|
||||||
|
type += ` · ${schema.title.replaceAll(' ', '')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('anyOf' in schema) {
|
||||||
|
type = 'any of';
|
||||||
|
} else if ('oneOf' in schema) {
|
||||||
|
type = 'one of';
|
||||||
|
} else if ('allOf' in schema) {
|
||||||
|
type = 'all of';
|
||||||
|
} else if ('not' in schema) {
|
||||||
|
type = 'not';
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user