Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]
### Added
- Preprocessing: change prefix from underscore_ to x_ and require map key ([#216](https://github.com/opensearch-project/opensearch-protobufs/pull/216))
- Preprocessing: Change prefix from underscore_ to x_ and require map key ([#216](https://github.com/opensearch-project/opensearch-protobufs/pull/216))
- Add Cardinality and Missing aggregations. ([#245](https://github.com/opensearch-project/opensearch-protobufs/pull/245))
- Add terms aggregation protos ([#268](https://github.com/opensearch-project/opensearch-protobufs/pull/268))
- Preprocessing: Handle unnamed additionalProperties.([#272](https://github.com/opensearch-project/opensearch-protobufs/pull/272))

### Changed
- Update preprocessing for x-protobuf-excluded ([#266](https://github.com/opensearch-project/opensearch-protobufs/pull/266))
Expand Down
100 changes: 97 additions & 3 deletions tools/proto-convert/src/SchemaModifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ export class SchemaModifier {
traverse(this.root, {
onSchemaProperty: (schema) => {
this.deduplicateEnumValue(schema)
this.convertAdditionalPropertiesToProperty(schema, true)
this.handleAdditionalPropertiesUndefined(schema)
this.convertNullTypeToNullValue(schema)
this.collapseOrMergeOneOfArray(schema)
},
onSchema: (schema, schemaName) => {
if (!schema || isReferenceObject(schema)) return;
this.deduplicateEnumValue(schema)
this.convertAdditionalPropertiesToProperty(schema, false)
this.handleAdditionalPropertiesUndefined(schema)
this.convertNullTypeToNullValue(schema)
this.handleOneOfConst(schema, schemaName)
Expand All @@ -36,11 +38,14 @@ export class SchemaModifier {
const visit = new Set();
traverse(this.root, {
onSchemaProperty: (schema) => {
this.simplifySingleMapSchema(schema, visit)
this.simplifySingleMapSchema(schema, visit);
this.handleAdditionalPropertiesUndefined(schema)

},
onSchema: (schema) => {
if (!schema || isReferenceObject(schema)) return;
this.simplifySingleMapSchema(schema, visit)
this.handleAdditionalPropertiesUndefined(schema)
},
});
return this.root
Expand Down Expand Up @@ -70,7 +75,6 @@ export class SchemaModifier {
break;
}
}

// if found string+const, collect all values
if (hasStringWithConst) {
for (const item of schema.oneOf) {
Expand All @@ -84,7 +88,6 @@ export class SchemaModifier {
}
}
}

// Convert to enum
delete schema.oneOf;
schema.type = 'string';
Expand Down Expand Up @@ -343,4 +346,95 @@ export class SchemaModifier {
(schema as any).type = 'NullValue';
}
}

/**
* Converts additionalProperties with a title into a named property.
*
* @param schema - The schema to process
* @param isNestedProperty - If true, skip conversion (nested properties already have context)
*
* Example:
* Input:
* {
* type: "object",
* properties: { distance: { type: "string" } },
* propertyNames: { title: "field", type: "string" },
* additionalProperties: {
* title: "location",
* $ref: "#/components/schemas/GeoLocation"
* },
* minProperties: 2
* }
*
* Output:
* {
* type: "object",
* properties: {
* distance: { type: "string" },
* location: {
* type: "object",
* additionalProperties: {
* $ref: "#/components/schemas/GeoLocation"
* }
* }
* },
* minProperties: 2
* }
**/
convertAdditionalPropertiesToProperty(schema: OpenAPIV3.SchemaObject, isNestedProperty: boolean = false): void {
if (!schema.additionalProperties || typeof schema.additionalProperties !== 'object') {
return;
}

const additionalProps = schema.additionalProperties as any;

if (isNestedProperty) {
return;
}

if (!additionalProps.title || typeof additionalProps.title !== 'string') {
return;
}

const propertyName = additionalProps.title;

if (!schema.properties) {
schema.properties = {};
}

if (schema.properties[propertyName]) {
this.logger.warn(`Property '${propertyName}' already exists in schema, skipping additionalProperties conversion`);
return;
}

const innerAdditionalProps: any = {};
for (const key in additionalProps) {
if (key !== 'title') {
innerAdditionalProps[key] = additionalProps[key];
}
}
const hasSchemaDefinition = Boolean(
innerAdditionalProps.type ||
innerAdditionalProps.$ref ||
innerAdditionalProps.properties ||
innerAdditionalProps.enum ||
innerAdditionalProps.items ||
innerAdditionalProps.allOf ||
innerAdditionalProps.anyOf ||
innerAdditionalProps.oneOf
);

schema.properties[propertyName] = {
type: 'object',
additionalProperties: hasSchemaDefinition ? innerAdditionalProps : true
};

delete schema.additionalProperties;

if ('propertyNames' in schema) {
delete schema.propertyNames;
}

this.logger.info(`Converted additionalProperties to named property '${propertyName}' with type: object`);
}
}