@@ -7,6 +7,7 @@ import logger from "./utils/logger";
77
88const DEFAULT_MAP_KEY = 'field' // default key for simplified additionalProperties
99const DEFAULT_MAP_VALUE = 'value' // default value for simplified additionalProperties
10+ const QUERY_CONTAINER_SCHEMA_NAME = 'QueryContainer' // schema name that requires special inline handling.
1011
1112export class SchemaModifier {
1213 root : OpenAPIV3 . Document ;
@@ -37,8 +38,8 @@ export class SchemaModifier {
3738 } ) ;
3839 const visit = new Set ( ) ;
3940 traverse ( this . root , {
40- onSchemaProperty : ( schema ) => {
41- this . simplifySingleMapSchema ( schema , visit ) ;
41+ onSchemaProperty : ( schema , propertyName , parentSchemaName ) => {
42+ this . simplifySingleMapSchema ( schema , visit , parentSchemaName ) ;
4243 this . handleAdditionalPropertiesUndefined ( schema )
4344
4445 } ,
@@ -279,7 +280,6 @@ export class SchemaModifier {
279280
280281 /**
281282 * Extracts type name from $ref or title
282- * Note: Schema names have already been sanitized by Sanitizer, so '___' prefixes are already removed
283283 **/
284284 private getTypeName ( schema : OpenAPIV3 . SchemaObject | OpenAPIV3 . ReferenceObject ) : string | null {
285285 if ( '$ref' in schema ) {
@@ -293,58 +293,115 @@ export class SchemaModifier {
293293 }
294294
295295 /**
296- * Transforms SchemaObject that single-key maps (`minProperties = 1` and `maxProperties = 1`) into a new wrapper schema.
297- *
296+ * Transforms SchemaObject that single-key maps (`minProperties = 1` and `maxProperties = 1`) into a $ref to a new map schema.
298297 *
299- * Example:
300- * Input:
298+ * Input (items of DecayFunction):
301299 * {
302300 * type: "object",
301+ * propertyNames: { title: "field", type: "string" },
303302 * additionalProperties: {
304- * $ref: "#/components/schemas/SortOrder"
303+ * title: "placement",
304+ * $ref: "#/components/schemas/DecayPlacement"
305305 * },
306306 * minProperties: 1,
307307 * maxProperties: 1,
308308 * }
309309 *
310310 * Output:
311+ * - Replaces with: { $ref: "#/components/schemas/DecayPlacementSingleMap" }
312+ * - Creates new schema DecayPlacementSingleMap:
311313 * {
312314 * type: "object",
313- * title: "SortOrderMap",
314315 * properties: {
315316 * field: { type: "string" },
316- * sort_order: { $ref: "#/components/schemas/SortOrder" }
317+ * decay_placement: {
318+ * title: "placement",
319+ * $ref: "#/components/schemas/DecayPlacement"
320+ * }
317321 * },
318- * required: ["field", "sort_order "]
322+ * required: ["field", "decay_placement "]
319323 * }
320324 *
321325 **/
322- simplifySingleMapSchema ( schema : OpenAPIV3 . SchemaObject , visit : Set < any > ) : void {
326+ simplifySingleMapSchema ( schema : OpenAPIV3 . SchemaObject , visit : Set < any > , parentSchemaName ?: string ) : void {
323327 if ( schema . type === 'object' && typeof schema . additionalProperties === 'object' &&
324328 ! Array . isArray ( schema . additionalProperties ) && schema . minProperties === 1 && schema . maxProperties === 1 ) {
325329
330+ // Check if this is a QueryContainer property
331+ // If so, use the old inline behavior to avoid breaking QueryContainer structure
332+ // TODO: Remove this special case in next major release and use SingleMap wrapper for all schemas
333+ if ( parentSchemaName === QUERY_CONTAINER_SCHEMA_NAME ) {
334+ logger . info ( `Using inline modification for ${ QUERY_CONTAINER_SCHEMA_NAME } property (legacy behavior)` + JSON . stringify ( schema ) ) ;
335+ // Old behavior: inline modification
336+ const reconstructAdditionalPropertySchema = this . reconstructAdditionalPropertySchema ( schema . additionalProperties , visit ) ;
337+ Object . assign ( schema , reconstructAdditionalPropertySchema ) ;
338+
339+ delete schema . additionalProperties ;
340+ delete schema . minProperties ;
341+ delete schema . maxProperties ;
342+ delete schema . type ;
343+ if ( 'propertyNames' in schema ) {
344+ delete schema . propertyNames ;
345+ }
346+ return ;
347+ }
348+
349+ // New behavior: create intermediate SingleMap schema
326350 const valueSchema = schema . additionalProperties ;
327- const typeName = this . getTypeName ( valueSchema ) || 'Value' ; // Use 'Value' as fallback
328-
329- // Create new wrapper schema
330- // Avoid collision with the reserved 'field' key property name
331- const rawPropertyName = toSnakeCase ( typeName ) ;
332- const valuePropertyName = rawPropertyName === 'field' ? `${ rawPropertyName } _value` : rawPropertyName ;
333- const wrapperTitle = schema . title || `${ typeName } Map` ;
334-
335- schema . title = wrapperTitle ;
336- schema . properties = {
337- field : { type : 'string' as const } ,
338- [ valuePropertyName ] : valueSchema
351+ const typeName = this . getTypeName ( valueSchema ) || 'Value' ;
352+
353+ // Extract field property name and schema from propertyNames
354+ let fieldPropertyName = 'field' ;
355+ let fieldPropertySchema : any = { type : 'string' as const } ;
356+
357+ if ( ( schema as any ) . propertyNames ) {
358+ const propertyNames = ( schema as any ) . propertyNames ;
359+
360+ // If propertyNames has a title, use it as the property name
361+ if ( propertyNames . title && typeof propertyNames . title === 'string' ) {
362+ fieldPropertyName = propertyNames . title ;
363+
364+ fieldPropertySchema = { ...propertyNames } ;
365+ delete fieldPropertySchema . title ;
366+ } else {
367+ // Use propertyNames as-is for the field schema
368+ fieldPropertySchema = propertyNames ;
369+ }
370+ }
371+
372+ // Create new map schema name
373+ const mapSchemaName = `${ typeName } SingleMap` ;
374+
375+ // Create the new map schema
376+ const newMapSchema : OpenAPIV3 . SchemaObject = {
377+ type : 'object' ,
378+ properties : {
379+ [ fieldPropertyName ] : fieldPropertySchema ,
380+ [ toSnakeCase ( typeName ) ] : valueSchema
381+ } ,
382+ required : [ fieldPropertyName , toSnakeCase ( typeName ) ]
339383 } ;
340- schema . required = [ 'field' , valuePropertyName ] ;
341384
385+ // Add to components.schemas if not already exists
386+ if ( ! this . root . components ) {
387+ this . root . components = { } ;
388+ }
389+ if ( ! this . root . components . schemas ) {
390+ this . root . components . schemas = { } ;
391+ }
392+ if ( ! this . root . components . schemas [ mapSchemaName ] ) {
393+ this . root . components . schemas [ mapSchemaName ] = newMapSchema ;
394+ }
395+
396+ // Replace current schema with $ref to the new map schema
397+ // Only delete the properties we don't need, keep vendor extensions (x-*)
398+ delete schema . type ;
342399 delete schema . additionalProperties ;
343400 delete schema . minProperties ;
344401 delete schema . maxProperties ;
345- if ( 'propertyNames' in schema ) {
346- delete schema . propertyNames ;
347- }
402+ delete ( schema as any ) . propertyNames ;
403+
404+ ( schema as any ) . $ref = `#/components/schemas/ ${ mapSchemaName } ` ;
348405 }
349406 }
350407
0 commit comments