Rearrange item types in OpenAPI blocks (#2862)

This commit is contained in:
Nolann B.
2025-02-24 11:33:47 +01:00
committed by GitHub
parent 66d0fc0683
commit bdd6303bcc
4 changed files with 108 additions and 71 deletions
@@ -21,7 +21,7 @@
}
.openapi-deprecated-sunset-date {
@apply font-semibold font-mono;
@apply font-semibold font-mono truncate;
}
.openapi-description.openapi-markdown {
@@ -181,7 +181,7 @@
.openapi-schema-name {
/* To make double click on the property name select only the name,
we disable selection on the parent and re-enable it on the children. */
@apply select-none flex gap-2.5 items-baseline text-sm;
@apply select-none flex gap-x-2.5 items-baseline text-sm flex-wrap;
}
.openapi-schema-name .openapi-deprecated {
@@ -571,11 +571,15 @@
/* Disclosure */
.openapi-disclosure-trigger {
@apply transition-all duration-300 hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
@apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
}
.openapi-disclosure-trigger span {
@apply truncate;
}
.openapi-disclosure svg {
@apply size-3 transition-transform duration-300;
@apply size-3 shrink-0 transition-transform duration-300;
}
.openapi-disclosure-trigger[aria-expanded='true'] svg {
+58 -61
View File
@@ -47,23 +47,6 @@ export function OpenAPISchemaProperty(
? null
: getSchemaAlternatives(schema, new Set(circularRefs.keys()));
if ((properties && properties.length > 0) || schema.type === 'object') {
return (
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
<OpenAPISchemaPresentation {...props} />
{properties && properties.length > 0 ? (
<OpenAPIDisclosure context={context}>
<OpenAPISchemaProperties
properties={properties}
circularRefs={circularRefs}
context={context}
/>
</OpenAPIDisclosure>
) : null}
</InteractiveSection>
);
}
if (alternatives?.[0]?.length) {
return (
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
@@ -80,6 +63,23 @@ export function OpenAPISchemaProperty(
);
}
if ((properties && properties.length > 0) || schema.type === 'object') {
return (
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
<OpenAPISchemaPresentation {...props} />
{properties && properties.length > 0 ? (
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
<OpenAPISchemaProperties
properties={properties}
circularRefs={circularRefs}
context={context}
/>
</OpenAPIDisclosure>
) : null}
</InteractiveSection>
);
}
return (
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
<OpenAPISchemaPresentation {...props} />
@@ -166,16 +166,24 @@ function OpenAPISchemaAlternative(props: {
const { schema, circularRefs, context } = props;
const id = useId();
const subProperties = getSchemaProperties(schema);
const description = resolveDescription(schema);
return (
<OpenAPIDisclosure context={context}>
<OpenAPISchemaProperties
id={id}
properties={subProperties ?? [{ schema }]}
circularRefs={subProperties ? new Map(circularRefs).set(schema, id) : circularRefs}
context={context}
/>
</OpenAPIDisclosure>
<>
{description ? (
<Markdown source={description} className="openapi-schema-description" />
) : null}
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
<OpenAPISchemaProperties
id={id}
properties={subProperties ?? [{ schema }]}
circularRefs={
subProperties ? new Map(circularRefs).set(schema, id) : circularRefs
}
context={context}
/>
</OpenAPIDisclosure>
</>
);
}
@@ -219,7 +227,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
const shouldDisplayExample = (schema: OpenAPIV3.SchemaObject): boolean => {
return (
typeof schema.example === 'string' ||
(typeof schema.example === 'string' && !!schema.example) ||
typeof schema.example === 'number' ||
typeof schema.example === 'boolean' ||
(Array.isArray(schema.example) && schema.example.length > 0) ||
@@ -234,10 +242,10 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
return (
<div className="openapi-schema-presentation">
<OpenAPISchemaName
schema={schema}
type={getSchemaTitle(schema)}
propertyName={propertyName}
required={required}
deprecated={schema.deprecated}
/>
{schema['x-deprecated-sunset'] ? (
<div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
@@ -276,17 +284,6 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
* Get the sub-properties of a schema.
*/
function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] {
if (schema.allOf) {
return schema.allOf.reduce((acc, subSchema) => {
const properties = getSchemaProperties(subSchema) ?? [
{
schema: subSchema,
},
];
return [...acc, ...properties];
}, [] as OpenAPISchemaPropertyEntry[]);
}
// check array AND schema.items as this is sometimes null despite what the type indicates
if (schema.type === 'array' && !!schema.items) {
const items = schema.items;
@@ -295,6 +292,11 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
return itemProperties;
}
// If the items are a primitive type, we don't need to display them
if (['string', 'number', 'boolean', 'integer'].includes(items.type) && !items.enum) {
return null;
}
return [
{
propertyName: 'items',
@@ -351,8 +353,7 @@ export function getSchemaAlternatives(
}
if (schema.allOf) {
// allOf is managed in `getSchemaProperties`
return null;
return [flattenAlternatives('allOf', schema.allOf, downAncestors), schema.discriminator];
}
return null;
@@ -378,11 +379,6 @@ export function getSchemaTitle(
/** If the title is inferred in a oneOf with discriminator, we can use it to optimize the title */
discriminator?: OpenAPIV3.DiscriminatorObject,
): string {
if (schema.title) {
// If the schema has a title, use it
return schema.title;
}
// Try using the discriminator
if (discriminator?.propertyName && schema.properties) {
const discriminatorProperty = schema.properties[discriminator.propertyName];
@@ -419,21 +415,22 @@ export function getSchemaTitle(
type = 'not';
}
if (schema.minimum || schema.minLength) {
type += ` · min: ${schema.minimum || schema.minLength}`;
}
if (schema.maximum || schema.maxLength) {
type += ` · max: ${schema.maximum || schema.maxLength}`;
}
if (schema.default) {
type += ` · default: ${schema.default}`;
}
if (schema.nullable) {
type = `${type} | nullable`;
}
return type;
}
function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string | undefined {
if (schema.type === 'array' && !!schema.items) {
if (schema.items.oneOf) {
return 'available items';
}
// Fallback to "child attributes" for enums and objects
if (schema.items.enum || schema.items.type === 'object') {
return;
}
return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
}
return schema.title;
}
@@ -1,8 +1,10 @@
import { OpenAPIV3 } from '@gitbook/openapi-parser';
interface OpenAPISchemaNameProps {
schema?: OpenAPIV3.SchemaObject;
propertyName?: string | JSX.Element;
required?: boolean;
type?: string;
deprecated?: boolean;
}
/**
@@ -10,18 +12,48 @@ interface OpenAPISchemaNameProps {
* It includes the property name, type, required and deprecated status.
*/
export function OpenAPISchemaName(props: OpenAPISchemaNameProps): JSX.Element {
const { type, propertyName, required, deprecated } = props;
const { schema, type, propertyName, required } = props;
const additionalItems = schema && getAdditionalItems(schema);
return (
<div className="openapi-schema-name">
{propertyName ? (
<span data-deprecated={deprecated} className="openapi-schema-propertyname">
<span data-deprecated={schema?.deprecated} className="openapi-schema-propertyname">
{propertyName}
</span>
) : null}
{type ? <span className="openapi-schema-type">{type}</span> : null}
<span>
{type ? <span className="openapi-schema-type">{type}</span> : null}
{additionalItems ? (
<span className="openapi-schema-type">{additionalItems}</span>
) : null}
</span>
{required ? <span className="openapi-schema-required">required</span> : null}
{deprecated ? <span className="openapi-deprecated">Deprecated</span> : null}
{schema?.deprecated ? <span className="openapi-deprecated">Deprecated</span> : null}
</div>
);
}
function getAdditionalItems(schema: OpenAPIV3.SchemaObject): string {
let additionalItems = '';
if (schema.minimum || schema.minLength) {
additionalItems += ` · min: ${schema.minimum || schema.minLength}`;
}
if (schema.maximum || schema.maxLength) {
additionalItems += ` · max: ${schema.maximum || schema.maxLength}`;
}
// If the schema has a default value, we display it
if (typeof schema.default !== 'undefined') {
additionalItems += ` · default: ${schema.default}`;
}
if (schema.nullable) {
additionalItems = ` | nullable`;
}
return additionalItems;
}
+5 -1
View File
@@ -13,7 +13,11 @@ export function createStateKey(key: string, scope?: string) {
/**
* Resolve the description of an object.
*/
export function resolveDescription(object: AnyObject) {
export function resolveDescription(object: OpenAPIV3.SchemaObject | AnyObject) {
if ('items' in object && object.items) {
return resolveDescription(object.items);
}
return 'x-gitbook-description-html' in object &&
typeof object['x-gitbook-description-html'] === 'string'
? object['x-gitbook-description-html'].trim()