mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 10:03:31 +00:00
Enhance discriminator handling in OpenAPISchema (#3855)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@gitbook/react-openapi': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Enhance discriminator handling in OpenAPISchema
|
||||||
@@ -36,9 +36,19 @@ function OpenAPISchemaProperty(
|
|||||||
context: OpenAPIClientContext;
|
context: OpenAPIClientContext;
|
||||||
circularRefs: CircularRefsIds;
|
circularRefs: CircularRefsIds;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
discriminator?: OpenAPIV3.DiscriminatorObject;
|
||||||
|
discriminatorValue?: string;
|
||||||
} & Omit<ComponentPropsWithoutRef<'div'>, 'property' | 'context' | 'circularRefs' | 'className'>
|
} & Omit<ComponentPropsWithoutRef<'div'>, 'property' | 'context' | 'circularRefs' | 'className'>
|
||||||
) {
|
) {
|
||||||
const { circularRefs: parentCircularRefs, context, className, property, ...rest } = props;
|
const {
|
||||||
|
circularRefs: parentCircularRefs,
|
||||||
|
context,
|
||||||
|
className,
|
||||||
|
property,
|
||||||
|
discriminator,
|
||||||
|
discriminatorValue,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
const { schema } = property;
|
const { schema } = property;
|
||||||
|
|
||||||
@@ -59,7 +69,7 @@ function OpenAPISchemaProperty(
|
|||||||
const circularRefs = new Map(parentCircularRefs);
|
const circularRefs = new Map(parentCircularRefs);
|
||||||
circularRefs.set(schema, id);
|
circularRefs.set(schema, id);
|
||||||
|
|
||||||
const properties = getSchemaProperties(schema);
|
const properties = getSchemaProperties(schema, discriminator, discriminatorValue);
|
||||||
|
|
||||||
const ancestors = new Set(circularRefs.keys());
|
const ancestors = new Set(circularRefs.keys());
|
||||||
const alternatives = getSchemaAlternatives(schema, ancestors);
|
const alternatives = getSchemaAlternatives(schema, ancestors);
|
||||||
@@ -67,26 +77,15 @@ function OpenAPISchemaProperty(
|
|||||||
const header = <OpenAPISchemaPresentation id={id} context={context} property={property} />;
|
const header = <OpenAPISchemaPresentation id={id} context={context} property={property} />;
|
||||||
const content = (() => {
|
const content = (() => {
|
||||||
if (alternatives?.schemas) {
|
if (alternatives?.schemas) {
|
||||||
const { schemas, discriminator } = alternatives;
|
|
||||||
return (
|
return (
|
||||||
<div className="openapi-schema-alternatives">
|
<OpenAPISchemaAlternatives
|
||||||
{schemas.map((alternativeSchema, index) => (
|
alternatives={alternatives}
|
||||||
<div key={index} className="openapi-schema-alternative">
|
schema={schema}
|
||||||
<OpenAPISchemaAlternative
|
circularRefs={circularRefs}
|
||||||
schema={alternativeSchema}
|
context={context}
|
||||||
discriminator={discriminator}
|
parentDiscriminator={discriminator}
|
||||||
circularRefs={circularRefs}
|
parentDiscriminatorValue={discriminatorValue}
|
||||||
context={context}
|
/>
|
||||||
/>
|
|
||||||
{index < schemas.length - 1 ? (
|
|
||||||
<OpenAPISchemaAlternativeSeparator
|
|
||||||
schema={schema}
|
|
||||||
context={context}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,11 +186,30 @@ function OpenAPIRootSchema(props: {
|
|||||||
const id = useId();
|
const id = useId();
|
||||||
const properties = getSchemaProperties(schema);
|
const properties = getSchemaProperties(schema);
|
||||||
const description = resolveDescription(schema);
|
const description = resolveDescription(schema);
|
||||||
|
const ancestors = new Set(parentCircularRefs.keys());
|
||||||
|
const alternatives = getSchemaAlternatives(schema, ancestors);
|
||||||
|
|
||||||
|
const circularRefs = new Map(parentCircularRefs);
|
||||||
|
circularRefs.set(schema, id);
|
||||||
|
|
||||||
|
// Handle root-level oneOf/allOf/anyOf
|
||||||
|
if (alternatives?.schemas) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{description ? (
|
||||||
|
<Markdown source={description} className="openapi-schema-root-description" />
|
||||||
|
) : null}
|
||||||
|
<OpenAPISchemaAlternatives
|
||||||
|
alternatives={alternatives}
|
||||||
|
schema={schema}
|
||||||
|
circularRefs={circularRefs}
|
||||||
|
context={context}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (properties?.length) {
|
if (properties?.length) {
|
||||||
const circularRefs = new Map(parentCircularRefs);
|
|
||||||
circularRefs.set(schema, id);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{description ? (
|
{description ? (
|
||||||
@@ -228,6 +246,116 @@ export function OpenAPIRootSchemaFromServer(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discriminator value for a schema.
|
||||||
|
*/
|
||||||
|
function getDiscriminatorValue(
|
||||||
|
schema: OpenAPIV3.SchemaObject,
|
||||||
|
discriminator: OpenAPIV3.DiscriminatorObject | undefined
|
||||||
|
): string | undefined {
|
||||||
|
if (!discriminator) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discriminator.mapping) {
|
||||||
|
const mappingEntry = Object.entries(discriminator.mapping).find(([key, ref]) => {
|
||||||
|
if (schema.title === ref || (!!schema.title && ref.endsWith(`/${schema.title}`))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: check if the title contains the key (normalized)
|
||||||
|
if (schema.title?.toLowerCase().replace(/\s/g, '').includes(key.toLowerCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mappingEntry) {
|
||||||
|
return mappingEntry[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!discriminator.propertyName || !schema.properties) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const property = schema.properties[discriminator.propertyName];
|
||||||
|
if (!property || checkIsReference(property)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.const) {
|
||||||
|
return String(property.const);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.enum?.length === 1) {
|
||||||
|
return String(property.enum[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render alternatives (oneOf/allOf/anyOf) for a schema.
|
||||||
|
*/
|
||||||
|
function OpenAPISchemaAlternatives(props: {
|
||||||
|
alternatives: SchemaAlternatives;
|
||||||
|
schema: OpenAPIV3.SchemaObject;
|
||||||
|
circularRefs: CircularRefsIds;
|
||||||
|
context: OpenAPIClientContext;
|
||||||
|
parentDiscriminator?: OpenAPIV3.DiscriminatorObject;
|
||||||
|
parentDiscriminatorValue?: string;
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
alternatives,
|
||||||
|
schema,
|
||||||
|
circularRefs,
|
||||||
|
context,
|
||||||
|
parentDiscriminator,
|
||||||
|
parentDiscriminatorValue,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (!alternatives?.schemas) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { schemas, discriminator: alternativeDiscriminator, type } = alternatives;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="openapi-schema-alternatives">
|
||||||
|
{schemas.map((alternativeSchema, index) => {
|
||||||
|
// If the alternative has its own discriminator, use it.
|
||||||
|
// Otherwise, for allOf, inherit from parent discriminator.
|
||||||
|
const effectiveDiscriminator =
|
||||||
|
alternativeDiscriminator ||
|
||||||
|
(type === 'allOf' ? parentDiscriminator : undefined);
|
||||||
|
|
||||||
|
// If we are inheriting and using parent discriminator, pass down the value.
|
||||||
|
const effectiveDiscriminatorValue =
|
||||||
|
!alternativeDiscriminator && type === 'allOf'
|
||||||
|
? parentDiscriminatorValue
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={index} className="openapi-schema-alternative">
|
||||||
|
<OpenAPISchemaAlternative
|
||||||
|
schema={alternativeSchema}
|
||||||
|
discriminator={effectiveDiscriminator}
|
||||||
|
discriminatorValue={effectiveDiscriminatorValue}
|
||||||
|
circularRefs={circularRefs}
|
||||||
|
context={context}
|
||||||
|
/>
|
||||||
|
{index < schemas.length - 1 ? (
|
||||||
|
<OpenAPISchemaAlternativeSeparator schema={schema} context={context} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a tab for an alternative schema.
|
* Render a tab for an alternative schema.
|
||||||
* It renders directly the properties if relevant;
|
* It renders directly the properties if relevant;
|
||||||
@@ -236,11 +364,14 @@ export function OpenAPIRootSchemaFromServer(props: {
|
|||||||
function OpenAPISchemaAlternative(props: {
|
function OpenAPISchemaAlternative(props: {
|
||||||
schema: OpenAPIV3.SchemaObject;
|
schema: OpenAPIV3.SchemaObject;
|
||||||
discriminator: OpenAPIV3.DiscriminatorObject | undefined;
|
discriminator: OpenAPIV3.DiscriminatorObject | undefined;
|
||||||
|
discriminatorValue?: string;
|
||||||
circularRefs: CircularRefsIds;
|
circularRefs: CircularRefsIds;
|
||||||
context: OpenAPIClientContext;
|
context: OpenAPIClientContext;
|
||||||
}) {
|
}) {
|
||||||
const { schema, discriminator, circularRefs, context } = props;
|
const { schema, discriminator, circularRefs, context } = props;
|
||||||
const properties = getSchemaProperties(schema, discriminator);
|
const discriminatorValue =
|
||||||
|
props.discriminatorValue || getDiscriminatorValue(schema, discriminator);
|
||||||
|
const properties = getSchemaProperties(schema, discriminator, discriminatorValue);
|
||||||
|
|
||||||
return properties?.length ? (
|
return properties?.length ? (
|
||||||
<OpenAPIDisclosure
|
<OpenAPIDisclosure
|
||||||
@@ -257,6 +388,8 @@ function OpenAPISchemaAlternative(props: {
|
|||||||
) : (
|
) : (
|
||||||
<OpenAPISchemaProperty
|
<OpenAPISchemaProperty
|
||||||
property={{ schema }}
|
property={{ schema }}
|
||||||
|
discriminator={discriminator}
|
||||||
|
discriminatorValue={discriminatorValue}
|
||||||
circularRefs={circularRefs}
|
circularRefs={circularRefs}
|
||||||
context={context}
|
context={context}
|
||||||
/>
|
/>
|
||||||
@@ -435,12 +568,13 @@ export function OpenAPISchemaPresentation(props: {
|
|||||||
*/
|
*/
|
||||||
function getSchemaProperties(
|
function getSchemaProperties(
|
||||||
schema: OpenAPIV3.SchemaObject,
|
schema: OpenAPIV3.SchemaObject,
|
||||||
discriminator?: OpenAPIV3.DiscriminatorObject | undefined
|
discriminator?: OpenAPIV3.DiscriminatorObject | undefined,
|
||||||
|
discriminatorValue?: string | undefined
|
||||||
): null | OpenAPISchemaPropertyEntry[] {
|
): null | OpenAPISchemaPropertyEntry[] {
|
||||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||||
if (schema.type === 'array' && schema.items && !checkIsReference(schema.items)) {
|
if (schema.type === 'array' && schema.items && !checkIsReference(schema.items)) {
|
||||||
const items = schema.items;
|
const items = schema.items;
|
||||||
const itemProperties = getSchemaProperties(items);
|
const itemProperties = getSchemaProperties(items, discriminator, discriminatorValue);
|
||||||
if (itemProperties) {
|
if (itemProperties) {
|
||||||
return itemProperties.map((prop) => ({
|
return itemProperties.map((prop) => ({
|
||||||
...prop,
|
...prop,
|
||||||
@@ -467,8 +601,20 @@ function getSchemaProperties(
|
|||||||
|
|
||||||
if (schema.properties) {
|
if (schema.properties) {
|
||||||
Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
|
Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
|
||||||
|
const isDiscriminator = discriminator?.propertyName === propertyName;
|
||||||
if (checkIsReference(propertySchema)) {
|
if (checkIsReference(propertySchema)) {
|
||||||
return;
|
if (!isDiscriminator || !discriminatorValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalSchema = propertySchema;
|
||||||
|
if (isDiscriminator && discriminatorValue) {
|
||||||
|
finalSchema = {
|
||||||
|
...propertySchema,
|
||||||
|
const: discriminatorValue,
|
||||||
|
enum: [discriminatorValue],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
@@ -476,8 +622,8 @@ function getSchemaProperties(
|
|||||||
required: Array.isArray(schema.required)
|
required: Array.isArray(schema.required)
|
||||||
? schema.required.includes(propertyName)
|
? schema.required.includes(propertyName)
|
||||||
: undefined,
|
: undefined,
|
||||||
isDiscriminatorProperty: discriminator?.propertyName === propertyName,
|
isDiscriminatorProperty: isDiscriminator,
|
||||||
schema: propertySchema,
|
schema: finalSchema,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user