@@ -77,6 +77,12 @@ func processFlatteningInFile(path string, opts FlattenOptions, result *FlattenRe
7777
7878// processDocumentFlattening processes flattening in a document
7979func processDocumentFlattening (doc , root * yaml.Node , path string , opts FlattenOptions , result * FlattenResult ) (bool , error ) {
80+ // Validate composition structures before processing
81+ if validationErrors := ValidateAndReportCompositions (root , path ); validationErrors != "" {
82+ // Log validation warnings but continue processing
83+ fmt .Printf ("⚠️ Validation warnings for %s:\n %s" , path , validationErrors )
84+ }
85+
8086 // Track component references before flattening to identify unused ones later
8187 componentsBefore := extractComponentRefs (root )
8288
@@ -381,7 +387,11 @@ func flattenSchemaNode(node *yaml.Node, schemaName, path string, result *Flatten
381387
382388 switch key {
383389 case "oneOf" , "anyOf" , "allOf" :
384- if refValue := getSingleRefFromArray (value ); refValue != "" {
390+ if isEmptyComposition (value ) {
391+ // Handle empty composition by removing it entirely
392+ handleEmptyComposition (node , i , schemaName , key , path , result )
393+ changed = true
394+ } else if refValue := getSingleRefFromArray (value ); refValue != "" {
385395 // Replace the oneOf/anyOf/allOf with direct $ref
386396 node .Content [i ] = & yaml.Node {Kind : yaml .ScalarNode , Value : "$ref" }
387397 node .Content [i + 1 ] = & yaml.Node {Kind : yaml .ScalarNode , Value : refValue }
@@ -393,6 +403,54 @@ func flattenSchemaNode(node *yaml.Node, schemaName, path string, result *Flatten
393403 }
394404 result .FlattenedRefs [path ] = append (result .FlattenedRefs [path ], flattenedPath )
395405 changed = true
406+ } else if singleSchema := getSingleSchemaFromArray (value ); singleSchema != nil {
407+ // Replace the oneOf/anyOf/allOf with the single inline schema
408+ flattenCompositionWithInlineSchema (node , i , singleSchema , schemaName , key , path , result )
409+ changed = true
410+ }
411+ case "properties" :
412+ // Special handling for properties - process each property individually
413+ if value .Kind == yaml .MappingNode {
414+ propertiesToRemove := []int {}
415+
416+ for j := 0 ; j < len (value .Content ); j += 2 {
417+ propName := value .Content [j ].Value
418+ propNode := value .Content [j + 1 ]
419+
420+ if propNode .Kind == yaml .MappingNode {
421+ propSchemaName := fmt .Sprintf ("%s.properties.%s" , schemaName , propName )
422+ if flattenSchemaNode (propNode , propSchemaName , path , result ) {
423+ changed = true
424+ }
425+
426+ // Check if property became empty after flattening
427+ if len (propNode .Content ) == 0 {
428+ propertiesToRemove = append (propertiesToRemove , j )
429+ }
430+ }
431+ }
432+
433+ // Remove empty properties (in reverse order to maintain indices)
434+ for i := len (propertiesToRemove ) - 1 ; i >= 0 ; i -- {
435+ propIndex := propertiesToRemove [i ]
436+
437+ // Remove property key-value pair
438+ newContent := make ([]* yaml.Node , 0 , len (value .Content )- 2 )
439+
440+ // Copy content before the property
441+ for k := 0 ; k < propIndex ; k ++ {
442+ newContent = append (newContent , value .Content [k ])
443+ }
444+
445+ // Copy content after the property (skip the key-value pair)
446+ for k := propIndex + 2 ; k < len (value .Content ); k ++ {
447+ newContent = append (newContent , value .Content [k ])
448+ }
449+
450+ // Replace the properties node's content
451+ value .Content = newContent
452+ changed = true
453+ }
396454 }
397455 default :
398456 switch value .Kind {
@@ -517,3 +575,98 @@ func getSingleRefFromArray(arrayNode *yaml.Node) string {
517575
518576 return refValue
519577}
578+
579+ // getSingleSchemaFromArray checks if an array contains only one schema (not $ref) and returns it
580+ func getSingleSchemaFromArray (arrayNode * yaml.Node ) * yaml.Node {
581+ if arrayNode == nil || arrayNode .Kind != yaml .SequenceNode {
582+ return nil
583+ }
584+
585+ // Check if array has exactly one element
586+ if len (arrayNode .Content ) != 1 {
587+ return nil
588+ }
589+
590+ element := arrayNode .Content [0 ]
591+ if element .Kind != yaml .MappingNode {
592+ return nil
593+ }
594+
595+ // Check if the element is an inline schema (not a $ref)
596+ for i := 0 ; i < len (element .Content ); i += 2 {
597+ key := element .Content [i ].Value
598+ if key == "$ref" {
599+ // This is a $ref, not an inline schema
600+ return nil
601+ }
602+ }
603+
604+ // It's an inline schema
605+ return element
606+ }
607+
608+ // flattenCompositionWithInlineSchema replaces oneOf/anyOf/allOf with the single inline schema
609+ func flattenCompositionWithInlineSchema (parentNode * yaml.Node , keyIndex int , singleSchema * yaml.Node , schemaName , compositionType , path string , result * FlattenResult ) {
610+ // Remove the composition key and replace with the inline schema's properties
611+ // We need to merge the single schema's content into the parent node
612+
613+ // First, remove the composition key-value pair
614+ newContent := make ([]* yaml.Node , 0 , len (parentNode .Content )- 2 + len (singleSchema .Content ))
615+
616+ // Copy content before the composition
617+ for i := 0 ; i < keyIndex ; i ++ {
618+ newContent = append (newContent , parentNode .Content [i ])
619+ }
620+
621+ // Add the single schema's content (all its key-value pairs)
622+ newContent = append (newContent , singleSchema .Content ... )
623+
624+ // Copy content after the composition
625+ for i := keyIndex + 2 ; i < len (parentNode .Content ); i ++ {
626+ newContent = append (newContent , parentNode .Content [i ])
627+ }
628+
629+ // Replace the parent node's content
630+ parentNode .Content = newContent
631+
632+ // Record the flattening
633+ flattenedPath := fmt .Sprintf ("%s.%s -> inline schema" , schemaName , compositionType )
634+ if result .FlattenedRefs [path ] == nil {
635+ result .FlattenedRefs [path ] = []string {}
636+ }
637+ result .FlattenedRefs [path ] = append (result .FlattenedRefs [path ], flattenedPath )
638+ }
639+
640+ // isEmptyComposition checks if a composition array is empty
641+ func isEmptyComposition (arrayNode * yaml.Node ) bool {
642+ if arrayNode == nil || arrayNode .Kind != yaml .SequenceNode {
643+ return false
644+ }
645+ return len (arrayNode .Content ) == 0
646+ }
647+
648+ // handleEmptyComposition removes empty composition from schema
649+ func handleEmptyComposition (parentNode * yaml.Node , keyIndex int , schemaName , compositionType , path string , result * FlattenResult ) {
650+ // Remove the empty composition key-value pair
651+ newContent := make ([]* yaml.Node , 0 , len (parentNode .Content )- 2 )
652+
653+ // Copy content before the composition
654+ for i := 0 ; i < keyIndex ; i ++ {
655+ newContent = append (newContent , parentNode .Content [i ])
656+ }
657+
658+ // Copy content after the composition (skip the key-value pair)
659+ for i := keyIndex + 2 ; i < len (parentNode .Content ); i ++ {
660+ newContent = append (newContent , parentNode .Content [i ])
661+ }
662+
663+ // Replace the parent node's content
664+ parentNode .Content = newContent
665+
666+ // Record the removal
667+ flattenedPath := fmt .Sprintf ("%s.%s -> removed (empty)" , schemaName , compositionType )
668+ if result .FlattenedRefs [path ] == nil {
669+ result .FlattenedRefs [path ] = []string {}
670+ }
671+ result .FlattenedRefs [path ] = append (result .FlattenedRefs [path ], flattenedPath )
672+ }
0 commit comments