Skip to content

Commit d980d48

Browse files
committed
convert aggregation
1 parent 48aa8ea commit d980d48

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

tools/proto-convert/src/SchemaModifier.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class SchemaModifier {
3434
this.collapseOrMergeOneOfArray(schema)
3535
this.collapseOneOfObjectPropContainsTitleSchema(schema)
3636
this.removeArrayOfMapWrapper(schema)
37+
this.convertOneOfToMinMaxProperties(schema)
3738
},
3839
});
3940
const visit = new Set();
@@ -477,6 +478,110 @@ export class SchemaModifier {
477478
}
478479
}
479480

481+
/**
482+
* Converts oneOf pattern with single-property objects into minProperties/maxProperties pattern.
483+
* This allows the template to recognize it as a oneof constraint.
484+
*
485+
* Example:
486+
* Input:
487+
* {
488+
* allOf: [
489+
* { properties: { meta: {...} } },
490+
* { oneOf: [
491+
* { properties: { field1: {...} }, required: [field1] },
492+
* { properties: { field2: {...} }, required: [field2] }
493+
* ]
494+
* }
495+
* ]
496+
* }
497+
*
498+
* Output:
499+
* {
500+
* allOf: [
501+
* { properties: { meta: {...} } },
502+
* {
503+
* properties: {
504+
* field1: {...},
505+
* field2: {...}
506+
* },
507+
* minProperties: 1,
508+
* maxProperties: 1
509+
* }
510+
* ]
511+
* }
512+
**/
513+
convertOneOfToMinMaxProperties(schema: OpenAPIV3.SchemaObject): void {
514+
// Check allOf/anyOf/oneOf for oneOf patterns
515+
const composedKeys = ['allOf', 'anyOf', 'oneOf'] as const;
516+
517+
for (const key of composedKeys) {
518+
const items = schema[key];
519+
if (Array.isArray(items)) {
520+
for (let i = 0; i < items.length; i++) {
521+
const item = items[i];
522+
if (item && typeof item === 'object' && !('$ref' in item)) {
523+
const itemSchema = item as any;
524+
525+
// Check if this item has a oneOf with single-property patterns
526+
if (Array.isArray(itemSchema.oneOf) && itemSchema.oneOf.length > 0) {
527+
if (this.isOneOfWithSingleProperties(itemSchema.oneOf)) {
528+
// Convert oneOf to minProperties/maxProperties pattern
529+
this.flattenOneOfToProperties(itemSchema);
530+
this.logger.info(`Converted oneOf pattern to minProperties/maxProperties`);
531+
}
532+
}
533+
}
534+
}
535+
}
536+
}
537+
}
538+
539+
/**
540+
* Checks if oneOf items are all single-property objects
541+
**/
542+
private isOneOfWithSingleProperties(oneOfItems: any[]): boolean {
543+
return oneOfItems.every(item => {
544+
if (!item || typeof item !== 'object' || '$ref' in item) {
545+
return false;
546+
}
547+
// Check if item has exactly one property and a required field
548+
const hasProperties = item.properties && Object.keys(item.properties).length === 1;
549+
const hasRequired = Array.isArray(item.required) && item.required.length === 1;
550+
return hasProperties && hasRequired;
551+
});
552+
}
553+
554+
/**
555+
* Flattens oneOf structure into merged properties with minProperties/maxProperties
556+
**/
557+
private flattenOneOfToProperties(schema: any): void {
558+
if (!Array.isArray(schema.oneOf)) {
559+
return;
560+
}
561+
562+
const mergedProperties: any = {};
563+
564+
// Merge all properties from each oneOf item
565+
for (const item of schema.oneOf) {
566+
if (item && item.properties) {
567+
Object.assign(mergedProperties, item.properties);
568+
}
569+
}
570+
571+
// Replace oneOf with merged properties and add constraints
572+
schema.properties = mergedProperties;
573+
schema.minProperties = 1;
574+
schema.maxProperties = 1;
575+
576+
// Remove unevaluatedProperties if present
577+
if ('unevaluatedProperties' in schema) {
578+
delete schema.unevaluatedProperties;
579+
}
580+
581+
delete schema.oneOf;
582+
delete schema.required;
583+
}
584+
480585
/**
481586
* Marks schemas and properties with oneOf extensions.
482587
* Adds x-oneof-property to properties when schema has maxProperties=1.

0 commit comments

Comments
 (0)