Skip to content

Commit 4766092

Browse files
authored
Fix missing properties in allOf/oneOf (#3871)
1 parent 3e4e0b0 commit 4766092

File tree

2 files changed

+116
-33
lines changed

2 files changed

+116
-33
lines changed

.changeset/chatty-glasses-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@gitbook/react-openapi': patch
3+
---
4+
5+
Fix missing properties in allOf/oneOf

packages/react-openapi/src/OpenAPISchema.tsx

Lines changed: 111 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,75 @@ export function OpenAPISchemaPresentation(props: {
563563
);
564564
}
565565

566+
/**
567+
* Process properties from a schema object into property entries.
568+
*/
569+
function processSchemaProperties(
570+
schema: OpenAPIV3.SchemaObject,
571+
discriminator?: OpenAPIV3.DiscriminatorObject | undefined,
572+
discriminatorValue?: string | undefined,
573+
allRequired?: Set<string>
574+
): OpenAPISchemaPropertyEntry[] {
575+
const result: OpenAPISchemaPropertyEntry[] = [];
576+
577+
if (schema.properties) {
578+
Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
579+
const isDiscriminator = discriminator?.propertyName === propertyName;
580+
if (checkIsReference(propertySchema)) {
581+
if (!isDiscriminator || !discriminatorValue) {
582+
return;
583+
}
584+
}
585+
586+
let finalSchema = propertySchema;
587+
if (isDiscriminator && discriminatorValue) {
588+
finalSchema = {
589+
...propertySchema,
590+
const: discriminatorValue,
591+
enum: [discriminatorValue],
592+
};
593+
}
594+
595+
result.push({
596+
propertyName,
597+
required: Array.isArray(schema.required)
598+
? schema.required.includes(propertyName)
599+
: allRequired?.has(propertyName)
600+
? true
601+
: undefined,
602+
isDiscriminatorProperty: isDiscriminator,
603+
schema: finalSchema,
604+
});
605+
});
606+
}
607+
608+
if (schema.additionalProperties && !checkIsReference(schema.additionalProperties)) {
609+
result.push({
610+
propertyName: 'Other properties',
611+
schema: schema.additionalProperties === true ? {} : schema.additionalProperties,
612+
});
613+
}
614+
615+
return result;
616+
}
617+
618+
/**
619+
* Merge properties into a result array, with later properties overriding earlier ones.
620+
*/
621+
function mergeProperties(
622+
result: OpenAPISchemaPropertyEntry[],
623+
newProperties: OpenAPISchemaPropertyEntry[]
624+
): void {
625+
for (const prop of newProperties) {
626+
const existingIndex = result.findIndex((p) => p.propertyName === prop.propertyName);
627+
if (existingIndex >= 0) {
628+
result[existingIndex] = prop;
629+
} else {
630+
result.push(prop);
631+
}
632+
}
633+
}
634+
566635
/**
567636
* Get the sub-properties of a schema.
568637
*/
@@ -596,46 +665,55 @@ function getSchemaProperties(
596665
return [{ propertyName: 'items', schema: items }];
597666
}
598667

599-
if (schema.type === 'object' || schema.properties) {
600-
const result: OpenAPISchemaPropertyEntry[] = [];
601-
602-
if (schema.properties) {
603-
Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
604-
const isDiscriminator = discriminator?.propertyName === propertyName;
605-
if (checkIsReference(propertySchema)) {
606-
if (!isDiscriminator || !discriminatorValue) {
607-
return;
608-
}
609-
}
668+
// Handle allOf by collecting properties from all schemas in the allOf
669+
if (schema.allOf && Array.isArray(schema.allOf)) {
670+
const allOfSchemas = schema.allOf.filter(
671+
(s): s is OpenAPIV3.SchemaObject => !checkIsReference(s)
672+
);
610673

611-
let finalSchema = propertySchema;
612-
if (isDiscriminator && discriminatorValue) {
613-
finalSchema = {
614-
...propertySchema,
615-
const: discriminatorValue,
616-
enum: [discriminatorValue],
617-
};
674+
if (allOfSchemas.length > 0) {
675+
const result: OpenAPISchemaPropertyEntry[] = [];
676+
const allRequired = new Set<string>();
677+
678+
// Collect required fields and properties from all allOf schemas
679+
for (const allOfSchema of allOfSchemas) {
680+
if (Array.isArray(allOfSchema.required)) {
681+
allOfSchema.required.forEach((req) => allRequired.add(req));
682+
}
683+
const allOfProperties = getSchemaProperties(
684+
allOfSchema,
685+
discriminator,
686+
discriminatorValue
687+
);
688+
if (allOfProperties) {
689+
mergeProperties(result, allOfProperties);
618690
}
691+
}
619692

620-
result.push({
621-
propertyName,
622-
required: Array.isArray(schema.required)
623-
? schema.required.includes(propertyName)
624-
: undefined,
625-
isDiscriminatorProperty: isDiscriminator,
626-
schema: finalSchema,
627-
});
628-
});
629-
}
693+
// Collect required fields from the schema itself
694+
if (Array.isArray(schema.required)) {
695+
schema.required.forEach((req) => allRequired.add(req));
696+
}
630697

631-
if (schema.additionalProperties && !checkIsReference(schema.additionalProperties)) {
632-
result.push({
633-
propertyName: 'Other properties',
634-
schema: schema.additionalProperties === true ? {} : schema.additionalProperties,
698+
// Include direct properties from the schema itself
699+
mergeProperties(
700+
result,
701+
processSchemaProperties(schema, discriminator, discriminatorValue, allRequired)
702+
);
703+
704+
// Update required status for all properties
705+
result.forEach((prop) => {
706+
if (prop.propertyName && allRequired.has(prop.propertyName)) {
707+
prop.required = true;
708+
}
635709
});
710+
711+
return result.length > 0 ? result : null;
636712
}
713+
}
637714

638-
return result;
715+
if (schema.type === 'object' || schema.properties) {
716+
return processSchemaProperties(schema, discriminator, discriminatorValue);
639717
}
640718

641719
return null;

0 commit comments

Comments
 (0)