@@ -648,128 +648,112 @@ func createAllOf(s *openapi3.Schema, stringEnums map[string][]string, name, type
648648 return typeTpls
649649}
650650
651+ type oneOfItem struct {
652+ schema * openapi3.Schema
653+ enumField string
654+ }
655+
651656func createOneOf (s * openapi3.Schema , name , typeName string ) ([]TypeTemplate , []EnumTemplate ) {
657+ // First pass: collect metadata about variants.
652658 discriminatorKeys := map [string ]struct {}{}
653659 propertyToVariantTypes := map [string ]map [string ]struct {}{}
660+ oneOfItems := []oneOfItem {}
654661 for _ , variantRef := range s .OneOf {
655- for propName , propRef := range variantRef .Value .Properties {
662+ item := oneOfItem {schema : variantRef .Value }
663+ keys := sortedKeys (variantRef .Value .Properties )
664+ for _ , propName := range keys {
665+ propRef := variantRef .Value .Properties [propName ]
666+ propertyField := strcase .ToCamel (propName )
667+
656668 if len (propRef .Value .Enum ) == 1 {
657669 discriminatorKeys [propName ] = struct {}{}
670+ item .enumField = strcase .ToCamel (propRef .Value .Enum [0 ].(string ))
671+ } else if propRef .Value .Enum == nil && len (keys ) == 1 {
672+ item .enumField = propertyField
658673 }
659674 if _ , ok := propertyToVariantTypes [propName ]; ! ok {
660675 propertyToVariantTypes [propName ] = map [string ]struct {}{}
661676 }
662677 goType := convertToValidGoType (propName , typeName , propRef )
663678 propertyToVariantTypes [propName ][goType ] = struct {}{}
664679 }
680+ oneOfItems = append (oneOfItems , item )
665681 }
682+
666683 if len (discriminatorKeys ) > 1 {
667684 panic (fmt .Sprintf ("[ERROR] Found multiple discriminator properties for type %s: %+v" , name , discriminatorKeys ))
668685 }
686+
687+ // Find properties that have different types across variants.
669688 variantProperties := []string {}
670689 for propName , variantTypes := range propertyToVariantTypes {
671690 if len (variantTypes ) > 1 {
672691 variantProperties = append (variantProperties , propName )
673692 }
674693 }
675694
676- var parsedProperties []string
677- enumTpls := make ([]EnumTemplate , 0 )
678- typeTpls := make ([]TypeTemplate , 0 )
679- fields := make ([]TypeFields , 0 )
680-
681- for _ , v := range s .OneOf {
682- // We want to iterate over the properties of the embedded object
683- // and find the type that is a string.
684- var enumFieldName string
685-
686- // Iterate over all the schema components in the spec and write the types.
687- keys := sortedKeys (v .Value .Properties )
688- for _ , prop := range keys {
689- p := v .Value .Properties [prop ]
690- // We want to collect all the unique properties to create our global oneOf type.
691- propertyType := convertToValidGoType (prop , typeName , p )
692-
693- // Check if we have an enum in order to use the corresponding type instead of
694- // "string"
695- if propertyType == "string" && len (p .Value .Enum ) != 0 {
696- propertyType = typeName + strcase .ToCamel (prop )
695+ // Second pass: build the unified fields list for the oneOf struct.
696+ oneOfFields := []TypeFields {}
697+ seenFields := map [string ]struct {}{}
698+ for _ , variantRef := range s .OneOf {
699+ for _ , propName := range sortedKeys (variantRef .Value .Properties ) {
700+ if _ , ok := seenFields [propName ]; ok {
701+ continue
697702 }
703+ seenFields [propName ] = struct {}{}
698704
699- propertyName := strcase .ToCamel (prop )
700-
701- // Avoids duplication for every enum
702- fmt .Printf ("DEBUG FIELD %s %s\n " , typeName , propertyName )
703- if ! containsMatchFirstWord (parsedProperties , propertyName ) {
704- field := TypeFields {
705- Description : formatTypeDescription (propertyName , p .Value ),
706- Name : propertyName ,
707- Type : propertyType ,
708- SerializationInfo : fmt .Sprintf ("`json:\" %s,omitempty\" yaml:\" %s,omitempty\" `" , prop , prop ),
709- }
710-
711- // We set the type of a field as "any" if every element of the oneOf property isn't the same
712- if slices .Contains (variantProperties , prop ) {
713- field .Type = "any"
714- }
715-
716- // Check if the field is nullable and use omitzero instead of omitempty.
717- if p .Value != nil && p .Value .Nullable {
718- field .SerializationInfo = fmt .Sprintf ("`json:\" %s,omitzero\" yaml:\" %s,omitzero\" `" , prop , prop )
719- }
720-
721- fields = append (fields , field )
705+ propRef := variantRef .Value .Properties [propName ]
706+ propertyField := strcase .ToCamel (propName )
707+ propertyType := convertToValidGoType (propName , typeName , propRef )
722708
723- parsedProperties = append (parsedProperties , propertyName )
709+ // Use the enum type name instead of "string" when the property has an enum.
710+ if propertyType == "string" && len (propRef .Value .Enum ) != 0 {
711+ propertyType = typeName + strcase .ToCamel (propName )
724712 }
725713
726- if p .Value .Enum != nil {
727- // We want to get the enum value.
728- // Make sure there is only one.
729- if len (p .Value .Enum ) != 1 {
730- fmt .Printf ("[WARN] TODO: oneOf for %q -> %q enum %#v\n " , name , prop , p .Value .Enum )
731- continue
732- }
714+ // Use "any" if this property has different types across variants.
715+ if slices .Contains (variantProperties , propName ) {
716+ propertyType = "any"
717+ }
733718
734- enumFieldName = strcase .ToCamel (p .Value .Enum [0 ].(string ))
719+ field := TypeFields {
720+ Description : formatTypeDescription (propertyField , propRef .Value ),
721+ Name : propertyField ,
722+ Type : propertyType ,
723+ SerializationInfo : fmt .Sprintf ("`json:\" %s,omitempty\" yaml:\" %s,omitempty\" `" , propName , propName ),
735724 }
736725
737- // Enums can appear in a valid OpenAPI spec as a OneOf without necessarily
738- // being identified as such. If we find an object with a single property
739- // nested inside a OneOf we will assume this is an enum and modify the name of
740- // the struct that will be created out of this object.
741- // e.g. https://github.com/oxidecomputer/omicron/blob/158c0b205f23772dc6c4c97633fd1769cc0e00d4/openapi/nexus.json#L18637-L18682
742- if len (keys ) == 1 && p .Value .Enum == nil {
743- enumFieldName = propertyName
726+ // Check if the field is nullable and use omitzero instead of omitempty.
727+ if propRef .Value != nil && propRef .Value .Nullable {
728+ field .SerializationInfo = fmt .Sprintf ("`json:\" %s,omitzero\" yaml:\" %s,omitzero\" `" , propName , propName )
744729 }
730+
731+ oneOfFields = append (oneOfFields , field )
745732 }
733+ }
746734
735+ enumTpls := make ([]EnumTemplate , 0 )
736+ typeTpls := make ([]TypeTemplate , 0 )
737+
738+ for _ , v := range oneOfItems {
747739 // TODO: This is the only place that has an "additional name" at the end
748740 // TODO: This is where the "allOf" is being detected
749- tt , et := populateTypeTemplates (name , v .Value , enumFieldName )
741+ tt , et := populateTypeTemplates (name , v .schema , v . enumField )
750742 typeTpls = append (typeTpls , tt ... )
751743 enumTpls = append (enumTpls , et ... )
752744 }
753745
754- // TODO: For now AllOf values within a OneOf are treated as enums
755- // because that's how they are being used. Keep an eye out if this
756- // changes
757- for _ , v := range s .OneOf {
758- if v .Value .AllOf != nil {
759- return typeTpls , enumTpls
760- }
761- }
762-
763746 // Make sure to only create structs if the oneOf is not a replacement for enums on the API spec
764- if len (fields ) > 0 {
747+ if len (oneOfFields ) > 0 {
765748 typeTpl := TypeTemplate {
766749 Description : formatTypeDescription (typeName , s ),
767750 Name : typeName ,
768751 Type : "struct" ,
769- Fields : fields ,
752+ Fields : oneOfFields ,
770753 }
771754 typeTpls = append (typeTpls , typeTpl )
772755 }
756+
773757 return typeTpls , enumTpls
774758}
775759
0 commit comments