Support for OpenAPI Array request body (#3420)

This commit is contained in:
Nolann B.
2025-07-03 17:18:26 +02:00
committed by GitHub
parent bc1eca815e
commit 2350baa75f
7 changed files with 97 additions and 41 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': patch
'gitbook': patch
---
Support for OpenAPI Array request body
@@ -358,6 +358,11 @@
.openapi-requestbody-header-content {
/* 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 {
@@ -1,5 +1,6 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { InteractiveSection } from './InteractiveSection';
import { OpenAPIRequestBodyHeaderType } from './OpenAPIRequestBodyHeaderType';
import { OpenAPIRootSchema } from './OpenAPISchemaServer';
import type { OpenAPIClientContext } from './context';
import { t } from './translate';
@@ -20,11 +21,18 @@ export function OpenAPIRequestBody(props: {
return null;
}
const stateKey = createStateKey('request-body-media-type', context.blockKey);
return (
<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"
stateKey={createStateKey('request-body-media-type', context.blockKey)}
stateKey={stateKey}
selectIcon={context.icons.chevronDown}
tabs={Object.entries(requestBody.content ?? {}).map(
([contentType, mediaTypeObject]) => {
@@ -35,6 +43,7 @@ export function OpenAPIRequestBody(props: {
<OpenAPIRootSchema
schema={mediaTypeObject.schema ?? {}}
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>
);
}
+1 -38
View File
@@ -16,7 +16,7 @@ import { retrocycle } from './decycle';
import { getDisclosureLabel } from './getDisclosureLabel';
import { stringifyOpenAPI } from './stringifyOpenAPI';
import { tString } from './translate';
import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
import { checkIsReference, getSchemaTitle, resolveDescription, resolveFirstExample } from './utils';
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
@@ -652,40 +652,3 @@ function mergeRequiredFields(
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;
}
+1 -1
View File
@@ -33,7 +33,7 @@ interface OpenAPISelectProps<T extends OpenAPISelectItem> extends Omit<SelectPro
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));
return {
key: store.key,
+37
View File
@@ -216,3 +216,40 @@ function getStatusCodeCategory(statusCode: number | string): number | string {
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;
}