'use client'; // This component does not use any client feature but we don't want to // render it server-side because it has recursion. import type { OpenAPIV3 } from '@gitbook/openapi-parser'; import { useId } from 'react'; import clsx from 'clsx'; import { retrocycle } from 'json-decycle'; import { Markdown } from './Markdown'; import { OpenAPIDisclosure } from './OpenAPIDisclosure'; import { OpenAPISchemaName } from './OpenAPISchemaName'; import type { OpenAPIClientContext } from './types'; import { checkIsReference, resolveDescription, resolveFirstExample } from './utils'; type CircularRefsIds = Map; export interface OpenAPISchemaPropertyEntry { propertyName?: string | undefined; required?: boolean | undefined; schema: OpenAPIV3.SchemaObject; } /** * Render a property of an OpenAPI schema. */ function OpenAPISchemaProperty(props: { property: OpenAPISchemaPropertyEntry; context: OpenAPIClientContext; circularRefs: CircularRefsIds; className?: string; }) { const { circularRefs: parentCircularRefs, context, className, property } = props; const { schema } = property; const id = useId(); return (
{(() => { const circularRefId = parentCircularRefs.get(schema); // Avoid recursing infinitely, and instead render a link to the parent schema if (circularRefId) { return ; } const circularRefs = new Map(parentCircularRefs); circularRefs.set(schema, id); const properties = getSchemaProperties(schema); if (properties) { return ( ); } const ancestors = new Set(circularRefs.keys()); const alternatives = getSchemaAlternatives(schema, ancestors); if (alternatives) { return alternatives.map((schema, index) => ( )); } return null; })()}
); } /** * Render a set of properties of an OpenAPI schema. */ function OpenAPISchemaProperties(props: { id?: string; properties: OpenAPISchemaPropertyEntry[]; circularRefs?: CircularRefsIds; context: OpenAPIClientContext; }) { const { id, properties, circularRefs = new Map(), context, } = props; return (
{properties.map((property, index) => { return ( ); })}
); } export function OpenAPISchemaPropertiesFromServer(props: { id?: string; properties: string; context: OpenAPIClientContext; }) { return ( ); } /** * Render a root schema (such as the request body or response body). */ function OpenAPIRootSchema(props: { schema: OpenAPIV3.SchemaObject; context: OpenAPIClientContext; circularRefs?: CircularRefsIds; }) { const { schema, context, circularRefs: parentCircularRefs = new Map(), } = props; const id = useId(); const properties = getSchemaProperties(schema); if (properties?.length) { const circularRefs = new Map(parentCircularRefs); circularRefs.set(schema, id); return ( ); } return ( ); } export function OpenAPIRootSchemaFromServer(props: { schema: string; context: OpenAPIClientContext; }) { return ( ); } /** * Render a tab for an alternative schema. * It renders directly the properties if relevant; * for primitives, it renders the schema itself. */ function OpenAPISchemaAlternative(props: { schema: OpenAPIV3.SchemaObject; circularRefs: CircularRefsIds; context: OpenAPIClientContext; }) { const { schema, circularRefs, context } = props; const description = resolveDescription(schema); const properties = getSchemaProperties(schema); return ( <> {description ? ( ) : null} {properties?.length ? ( ) : ( )} ); } /** * Render a circular reference to a schema. */ function OpenAPISchemaCircularRef(props: { id: string; schema: OpenAPIV3.SchemaObject }) { const { id, schema } = props; return (
Circular reference to {getSchemaTitle(schema)}{' '} ↩
); } /** * Render the enum value for a schema. */ function OpenAPISchemaEnum(props: { enumValues: any[] }) { const { enumValues } = props; return (
Options:{' '} {enumValues.map((value, index) => ( {`${value}`} {index < enumValues.length - 1 ? ', ' : ''} ))}
); } /** * Render the top row of a schema. e.g: name, type, and required status. */ function OpenAPISchemaPresentation(props: { property: OpenAPISchemaPropertyEntry }) { const { property: { schema, propertyName, required }, } = props; const description = resolveDescription(schema); const example = resolveFirstExample(schema); return (
{typeof schema['x-deprecated-sunset'] === 'string' ? (
Sunset date:{' '} {schema['x-deprecated-sunset']}
) : null} {description ? ( ) : null} {typeof example === 'string' ? (
Example: {example}
) : null} {schema.pattern ? (
Pattern: {schema.pattern}
) : null} {schema.enum && schema.enum.length > 0 ? ( ) : null}
); } /** * Get the sub-properties of a schema. */ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] { // check array AND schema.items as this is sometimes null despite what the type indicates if (schema.type === 'array' && schema.items && !checkIsReference(schema.items)) { const items = schema.items; const itemProperties = getSchemaProperties(items); if (itemProperties) { return itemProperties; } // If the items are a primitive type, we don't need to display them if ( (items.type === 'string' || items.type === 'number' || items.type === 'boolean' || items.type === 'integer') && !items.enum ) { return null; } return [{ propertyName: 'items', schema: items }]; } if (schema.type === 'object' || schema.properties) { const result: OpenAPISchemaPropertyEntry[] = []; if (schema.properties) { Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => { if (checkIsReference(propertySchema)) { return; } result.push({ propertyName, required: Array.isArray(schema.required) ? schema.required.includes(propertyName) : undefined, schema: propertySchema, }); }); } if (schema.additionalProperties && !checkIsReference(schema.additionalProperties)) { result.push({ propertyName: 'Other properties', schema: schema.additionalProperties === true ? {} : schema.additionalProperties, }); } return result; } return null; } type AlternativeType = 'oneOf' | 'allOf' | 'anyOf'; /** * Get the alternatives to display for a schema. */ export function getSchemaAlternatives( schema: OpenAPIV3.SchemaObject, ancestors: Set = new Set() ): OpenAPIV3.SchemaObject[] | null { const alternatives: | [AlternativeType, (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[]] | null = (() => { if (schema.anyOf) { return ['anyOf', schema.anyOf]; } if (schema.oneOf) { return ['oneOf', schema.oneOf]; } if (schema.allOf) { return ['allOf', schema.allOf]; } return null; })(); if (!alternatives) { return null; } const [type, schemas] = alternatives; return flattenAlternatives(type, schemas, new Set(ancestors).add(schema)); } function flattenAlternatives( alternativeType: AlternativeType, schemasOrRefs: (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[], ancestors: Set ): OpenAPIV3.SchemaObject[] { return schemasOrRefs.reduce((acc, schemaOrRef) => { if (checkIsReference(schemaOrRef)) { return acc; } if (schemaOrRef[alternativeType] && !ancestors.has(schemaOrRef)) { const schemas = getSchemaAlternatives(schemaOrRef, ancestors); if (schemas) { acc.push(...schemas); } return acc; } acc.push(schemaOrRef); return acc; }, []); } function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string { // Otherwise try to infer a nice title let type = 'any'; if (schema.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}`; } } 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; } function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string { 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 'child attributes'; } return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items); } return schema.title || 'child attributes'; }