@@ -38,10 +38,64 @@ type TypeTemplate struct {
3838
3939// TypeFields holds the information for each type field
4040type TypeFields struct {
41- Description string
42- Name string
43- Type string
44- SerializationInfo string
41+ Schema * openapi3.SchemaRef
42+ Name string
43+ Type string
44+ MarshalKey string
45+ Required bool
46+
47+ // FallbackDescription generates a generic description for the field when the Schema doesn't have one.
48+ // TODO: Drop this, since generated descriptions don't contain useful information.
49+ FallbackDescription bool
50+
51+ // OmitDirective overrides the derived omit directive for the field.
52+ // TODO: Drop this; we should set omit directives consistently rather than using overrides.
53+ OmitDirective string
54+ }
55+
56+ // Description returns the formatted description comment for this field.
57+ func (f TypeFields ) Description () string {
58+ if f .Schema == nil {
59+ return ""
60+ }
61+ if f .Schema .Value .Description != "" {
62+ return fmt .Sprintf ("// %s is %s" , f .Name , toLowerFirstLetter (
63+ strings .ReplaceAll (f .Schema .Value .Description , "\n " , "\n // " )))
64+ }
65+ if f .FallbackDescription {
66+ return fmt .Sprintf ("// %s is the type definition for a %s." , f .Name , f .Name )
67+ }
68+ return ""
69+ }
70+
71+ // StructTag returns the JSON/YAML struct tags for this field.
72+ func (f TypeFields ) StructTag () string {
73+ // Derive the omit directive.
74+ var omitDirective string
75+ switch {
76+ case f .OmitDirective != "" :
77+ // Explicit override.
78+ omitDirective = f .OmitDirective
79+ case f .Schema == nil :
80+ // Special case: no schema (e.g., Body field).
81+ omitDirective = "omitempty"
82+ case f .Required || isNullableArray (f .Schema ):
83+ // Required or nullable array: no directive (always serialize).
84+ omitDirective = ""
85+ case slices .Contains (omitzeroTypes (), f .Type ):
86+ // Special types: omitzero.
87+ omitDirective = "omitzero"
88+ default :
89+ omitDirective = "omitempty"
90+ }
91+
92+ // Build the tag value.
93+ tagValue := f .MarshalKey
94+ if omitDirective != "" {
95+ tagValue = f .MarshalKey + "," + omitDirective
96+ }
97+
98+ return fmt .Sprintf ("`json:\" %s\" yaml:\" %s\" `" , tagValue , tagValue )
4599}
46100
47101// EnumTemplate holds the information for enum types
@@ -129,18 +183,18 @@ func constructParamTypes(paths map[string]*openapi3.PathItem) []TypeTemplate {
129183 }
130184
131185 paramName := strcase .ToCamel (p .Value .Name )
186+ paramType := convertToValidGoType ("" , "" , p .Value .Schema )
132187 field := TypeFields {
133- Name : paramName ,
134- Type : convertToValidGoType ("" , "" , p .Value .Schema ),
188+ Name : paramName ,
189+ Type : paramType ,
190+ MarshalKey : p .Value .Name ,
191+ Schema : nil , // nil so StructTag always uses omitempty
135192 }
136193
137194 if p .Value .Required {
138195 requiredFields = requiredFields + fmt .Sprintf ("\n // - %s" , paramName )
139196 }
140197
141- serInfo := fmt .Sprintf ("`json:\" %s,omitempty\" yaml:\" %s,omitempty\" `" , p .Value .Name , p .Value .Name )
142- field .SerializationInfo = serInfo
143-
144198 fields = append (fields , field )
145199 }
146200 if o .RequestBody != nil {
@@ -151,17 +205,19 @@ func constructParamTypes(paths map[string]*openapi3.PathItem) []TypeTemplate {
151205 // TODO: Handle other mime types in a more idiomatic way
152206 if mt != "application/json" {
153207 field = TypeFields {
154- Name : "Body" ,
155- Type : "io.Reader" ,
156- SerializationInfo : "`json:\" body,omitempty\" yaml:\" body,omitempty\" `" ,
208+ Name : "Body" ,
209+ Type : "io.Reader" ,
210+ MarshalKey : "body" ,
211+ Schema : nil , // no schema for non-JSON body
157212 }
158213 break
159214 }
160215
161216 field = TypeFields {
162- Name : "Body" ,
163- Type : "*" + convertToValidGoType ("" , "" , r .Schema ),
164- SerializationInfo : "`json:\" body,omitempty\" yaml:\" body,omitempty\" `" ,
217+ Name : "Body" ,
218+ Type : "*" + convertToValidGoType ("" , "" , r .Schema ),
219+ MarshalKey : "body" ,
220+ Schema : nil , // Body uses special serialization
165221 }
166222 }
167223 // Body is always a required field
@@ -329,10 +385,10 @@ func writeTypes(f *os.File, typeCollection []TypeTemplate, typeValidationCollect
329385 if tt .Fields != nil {
330386 fmt .Fprint (f , " {\n " )
331387 for _ , ft := range tt .Fields {
332- if ft .Description != "" {
333- fmt .Fprintf (f , "\t %s\n " , splitDocString (ft . Description ))
388+ if desc := ft .Description (); desc != "" {
389+ fmt .Fprintf (f , "\t %s\n " , splitDocString (desc ))
334390 }
335- fmt .Fprintf (f , "\t %s %s %s\n " , ft .Name , ft .Type , ft .SerializationInfo )
391+ fmt .Fprintf (f , "\t %s %s %s\n " , ft .Name , ft .Type , ft .StructTag () )
336392 }
337393 fmt .Fprint (f , "}\n " )
338394 }
@@ -529,29 +585,14 @@ func createTypeObject(schema *openapi3.Schema, name, typeName, description strin
529585 }
530586 }
531587
532- field := TypeFields {}
533- if v .Value .Description != "" {
534- desc := fmt .Sprintf ("// %s is %s" , strcase .ToCamel (k ), toLowerFirstLetter (strings .ReplaceAll (v .Value .Description , "\n " , "\n // " )))
535- field .Description = desc
536- }
537-
538- field .Name = strcase .ToCamel (k )
539- field .Type = typeName
540-
541- // Configure json/yaml struct tags. By default, omit empty/zero
542- // values, but retain them for required fields.
543- //
544- // TODO: Use `omitzero` rather than `omitempty` on all relevant
545- // fields: https://github.com/oxidecomputer/oxide.go/issues/290
546- serInfo := fmt .Sprintf ("`json:\" %s,omitempty\" yaml:\" %s,omitempty\" `" , k , k )
547- if isNullableArray (v ) || isRequired {
548- serInfo = fmt .Sprintf ("`json:\" %s\" yaml:\" %s\" `" , k , k )
549- } else if slices .Contains (omitzeroTypes (), typeName ) {
550- serInfo = fmt .Sprintf ("`json:\" %s,omitzero\" yaml:\" %s,omitzero\" `" , k , k )
588+ field := TypeFields {
589+ Name : strcase .ToCamel (k ),
590+ Type : typeName ,
591+ MarshalKey : k ,
592+ Schema : v ,
593+ Required : isRequired ,
551594 }
552595
553- field .SerializationInfo = serInfo
554-
555596 fields = append (fields , field )
556597
557598 }
@@ -720,21 +761,24 @@ func createOneOf(s *openapi3.Schema, name, typeName string) ([]TypeTemplate, []E
720761
721762 // Avoids duplication for every enum
722763 if ! containsMatchFirstWord (parsedProperties , propertyName ) {
723- field := TypeFields {
724- Description : formatTypeDescription (propertyName , p .Value ),
725- Name : propertyName ,
726- Type : propertyType ,
727- SerializationInfo : fmt .Sprintf ("`json:\" %s,omitempty\" yaml:\" %s,omitempty\" `" , prop , prop ),
728- }
729-
730764 // We set the type of a field as "any" if every element of the oneOf property isn't the same
731765 if slices .Contains (genericTypes , prop ) {
732- field . Type = "any"
766+ propertyType = "any"
733767 }
734768
735- // Check if the field is nullable and use omitzero instead of omitempty.
769+ // Determine omit directive: nullable fields in oneOf use omitzero.
770+ var omitDirective string
736771 if p .Value != nil && p .Value .Nullable {
737- field .SerializationInfo = fmt .Sprintf ("`json:\" %s,omitzero\" yaml:\" %s,omitzero\" `" , prop , prop )
772+ omitDirective = "omitzero"
773+ }
774+
775+ field := TypeFields {
776+ Name : propertyName ,
777+ Type : propertyType ,
778+ MarshalKey : prop ,
779+ Schema : p ,
780+ FallbackDescription : true ,
781+ OmitDirective : omitDirective ,
738782 }
739783
740784 fields = append (fields , field )
0 commit comments