Skip to content

Commit b8071b6

Browse files
author
kaassaly
committed
feat(openapi/generator): add the x-omitempty extension to true on all fields that contains an omitempty in json tag
1 parent fe54d35 commit b8071b6

File tree

10 files changed

+327
-197
lines changed

10 files changed

+327
-197
lines changed

openapi/generator.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,9 @@ func (g *Generator) newSchemaFromStructField(sf reflect.StructField, required bo
751751
if sor == nil {
752752
return nil
753753
}
754+
755+
sor = g.addExtensions(sor, sf)
756+
754757
// Get the underlying schema, it may be a reference
755758
// to a component, and update its fields using the
756759
// informations in the struct field tags.
@@ -834,6 +837,26 @@ func (g *Generator) newSchemaFromStructField(sf reflect.StructField, required bo
834837
return sor
835838
}
836839

840+
func (g *Generator) addExtensions(sor *SchemaOrRef, sf reflect.StructField) *SchemaOrRef {
841+
// Check if the json field has the omitempty tag.
842+
jsonTag := sf.Tag.Get("json")
843+
hasOmitEmpty := strings.Contains(jsonTag, "omitempty")
844+
845+
if sor.Schema != nil {
846+
if sor.Schema.Extensions == nil {
847+
sor.Schema.Extensions = make(map[string]interface{})
848+
}
849+
sor.Schema.Extensions["x-omitempty"] = hasOmitEmpty
850+
} else if sor.Reference != nil {
851+
if sor.Reference.Extensions == nil {
852+
sor.Reference.Extensions = make(map[string]interface{})
853+
}
854+
sor.Reference.Extensions["x-omitempty"] = hasOmitEmpty
855+
}
856+
857+
return sor
858+
}
859+
837860
func (g *Generator) enumFromStructField(sf reflect.StructField, fname string, parent reflect.Type) []interface{} {
838861
var enum []interface{}
839862

@@ -1255,7 +1278,7 @@ func fieldNameFromTag(sf reflect.StructField, tagName string) string {
12551278
return name
12561279
}
12571280

1258-
/// parseExampleValue is used to transform the string representation of the example value to the correct type.
1281+
// / parseExampleValue is used to transform the string representation of the example value to the correct type.
12591282
func parseExampleValue(t reflect.Type, value string) (interface{}, error) {
12601283
// If the type implements Exampler use the ParseExample method to create the example
12611284
i, ok := reflect.New(t).Interface().(Exampler)

openapi/generator_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package openapi
33
import (
44
"encoding/json"
55
"fmt"
6-
"io/ioutil"
76
"math"
7+
"os"
88
"reflect"
99
"strconv"
1010
"testing"
@@ -213,7 +213,7 @@ func TestSchemaFromComplex(t *testing.T) {
213213
t.Error(err)
214214
}
215215
// see testdata/X.json.
216-
expected, err := ioutil.ReadFile("../testdata/schemas/X.json")
216+
expected, err := os.ReadFile("../testdata/schemas/X.json")
217217
if err != nil {
218218
t.Error(err)
219219
}
@@ -234,7 +234,7 @@ func TestSchemaFromComplex(t *testing.T) {
234234
t.Error(err)
235235
}
236236
// see testdata/Y.json.
237-
expected, err = ioutil.ReadFile("../testdata/schemas/Y.json")
237+
expected, err = os.ReadFile("../testdata/schemas/Y.json")
238238
if err != nil {
239239
t.Error(err)
240240
}
@@ -550,7 +550,7 @@ func TestAddOperation(t *testing.T) {
550550
t.Error(err)
551551
}
552552
// see testdata/schemas/path-item.json.
553-
expected, err := ioutil.ReadFile("../testdata/schemas/path-item.json")
553+
expected, err := os.ReadFile("../testdata/schemas/path-item.json")
554554
if err != nil {
555555
t.Error(err)
556556
}

openapi/spec.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,26 @@ type PathItem struct {
9393
// other components in the specification, internally and
9494
// externally.
9595
type Reference struct {
96-
Ref string `json:"$ref" yaml:"$ref"`
96+
Ref string `json:"$ref" yaml:"$ref"`
97+
Extensions map[string]interface{} `json:"-" yaml:"-"`
98+
}
99+
100+
func (r *Reference) MarshalJSON() ([]byte, error) {
101+
if r == nil {
102+
return []byte("{}"), nil
103+
}
104+
105+
m := map[string]interface{}{
106+
"$ref": r.Ref,
107+
}
108+
109+
if r.Extensions != nil {
110+
for k, v := range r.Extensions {
111+
m[k] = v
112+
}
113+
}
114+
115+
return json.Marshal(m)
97116
}
98117

99118
// Parameter describes a single operation parameter.
@@ -124,6 +143,16 @@ func (por *ParameterOrRef) MarshalYAML() (interface{}, error) {
124143
return por.Reference, nil
125144
}
126145

146+
func (p *ParameterOrRef) MarshalJSON() ([]byte, error) {
147+
if p.Parameter != nil {
148+
return json.Marshal(p.Parameter)
149+
}
150+
if p.Reference != nil {
151+
return json.Marshal(p.Reference)
152+
}
153+
return []byte("{}"), nil
154+
}
155+
127156
// RequestBody represents a request body.
128157
type RequestBody struct {
129158
Description string `json:"description,omitempty" yaml:"description,omitempty"`
@@ -146,6 +175,22 @@ func (sor *SchemaOrRef) MarshalYAML() (interface{}, error) {
146175
return sor.Reference, nil
147176
}
148177

178+
func (sor *SchemaOrRef) MarshalJSON() ([]byte, error) {
179+
if sor == nil {
180+
return nil, nil
181+
}
182+
183+
if sor.Schema != nil {
184+
return json.Marshal(sor.Schema)
185+
}
186+
187+
if sor.Reference != nil {
188+
return json.Marshal(sor.Reference)
189+
}
190+
191+
return []byte("{}"), nil
192+
}
193+
149194
// Schema represents the definition of input and output data
150195
// types of the API.
151196
type Schema struct {
@@ -184,6 +229,35 @@ type Schema struct {
184229
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
185230
Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
186231
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
232+
233+
Extensions map[string]interface{} `json:"-" yaml:"-"`
234+
}
235+
236+
func (s *Schema) MarshalJSON() ([]byte, error) {
237+
if s == nil {
238+
return nil, nil
239+
}
240+
241+
type Alias Schema
242+
base, err := json.Marshal((*Alias)(s))
243+
if err != nil {
244+
return nil, err
245+
}
246+
247+
if len(s.Extensions) == 0 {
248+
return base, nil
249+
}
250+
251+
var baseMap map[string]interface{}
252+
if err := json.Unmarshal(base, &baseMap); err != nil {
253+
return nil, err
254+
}
255+
256+
for k, v := range s.Extensions {
257+
baseMap[k] = v
258+
}
259+
260+
return json.Marshal(baseMap)
187261
}
188262

189263
// Operation describes an API operation on a path.
@@ -271,6 +345,16 @@ func (ror *ResponseOrRef) MarshalYAML() (interface{}, error) {
271345
return ror.Reference, nil
272346
}
273347

348+
func (r *ResponseOrRef) MarshalJSON() ([]byte, error) {
349+
if r.Response != nil {
350+
return json.Marshal(r.Response)
351+
}
352+
if r.Reference != nil {
353+
return json.Marshal(r.Reference)
354+
}
355+
return []byte("{}"), nil
356+
}
357+
274358
// Response describes a single response from an API.
275359
type Response struct {
276360
Description string `json:"description,omitempty" yaml:"description,omitempty"`
@@ -317,6 +401,13 @@ func (mtor *MediaTypeOrRef) MarshalYAML() (interface{}, error) {
317401
return mtor.Reference, nil
318402
}
319403

404+
func (m *MediaTypeOrRef) MarshalJSON() ([]byte, error) {
405+
if m.MediaType != nil {
406+
return json.Marshal(m.MediaType)
407+
}
408+
return []byte("{}"), nil
409+
}
410+
320411
// MediaType represents the type of a media.
321412
type MediaType struct {
322413
Schema *SchemaOrRef `json:"schema" yaml:"schema"`

openapi/validation_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package openapi
22

33
import (
44
"encoding/json"
5-
"io/ioutil"
5+
"os"
66
"testing"
77

88
"github.com/stretchr/testify/assert"
@@ -52,7 +52,7 @@ func TestSchemaValidation(t *testing.T) {
5252
t.Error(err)
5353
}
5454
// see testdata/validation/len.json.
55-
expected, err := ioutil.ReadFile("../testdata/schemas/validation.json")
55+
expected, err := os.ReadFile("../testdata/schemas/validation.json")
5656
if err != nil {
5757
t.Error(err)
5858
}

testdata/schemas/X.json

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,94 +2,114 @@
22
"type": "object",
33
"properties": {
44
"A": {
5-
"type": "string"
5+
"type": "string",
6+
"x-omitempty": false
67
},
78
"B": {
8-
"type": "integer",
99
"format": "int32",
10-
"nullable": true
10+
"nullable": true,
11+
"type": "integer",
12+
"x-omitempty": false
1113
},
1214
"C": {
15+
"deprecated": true,
1316
"type": "boolean",
14-
"deprecated": true
17+
"x-omitempty": false
1518
},
1619
"D": {
17-
"type": "array",
1820
"items": {
1921
"$ref": "#/components/schemas/Y"
20-
}
22+
},
23+
"type": "array",
24+
"x-omitempty": false
2125
},
2226
"E": {
23-
"type": "array",
2427
"items": {
2528
"$ref": "#/components/schemas/XXX"
2629
},
2730
"maxItems": 3,
28-
"minItems": 3
31+
"minItems": 3,
32+
"type": "array",
33+
"x-omitempty": false
2934
},
3035
"F": {
31-
"$ref": "#/components/schemas/XXX"
36+
"$ref": "#/components/schemas/XXX",
37+
"x-omitempty": false
3238
},
3339
"G": {
34-
"$ref": "#/components/schemas/Y"
40+
"$ref": "#/components/schemas/Y",
41+
"x-omitempty": false
3542
},
3643
"H": {
44+
"format": "float",
3745
"type": "number",
38-
"format": "float"
46+
"x-omitempty": false
3947
},
4048
"I": {
49+
"format": "date",
4150
"type": "string",
42-
"format": "date"
51+
"x-omitempty": false
4352
},
4453
"J": {
45-
"type": "integer",
4654
"format": "int32",
47-
"nullable": true
55+
"nullable": true,
56+
"type": "integer",
57+
"x-omitempty": false
4858
},
4959
"K": {
50-
"type": "object",
5160
"additionalProperties": {
5261
"$ref": "#/components/schemas/Y"
53-
}
62+
},
63+
"type": "object",
64+
"x-omitempty": false
5465
},
5566
"N": {
56-
"type": "object",
5767
"properties": {
5868
"Na": {
59-
"type": "string"
69+
"type": "string",
70+
"x-omitempty": false
6071
},
6172
"Nb": {
62-
"type": "string"
73+
"type": "string",
74+
"x-omitempty": false
6375
},
6476
"Nc": {
77+
"format": "duration",
6578
"type": "string",
66-
"format": "duration"
79+
"x-omitempty": false
6780
}
68-
}
81+
},
82+
"type": "object",
83+
"x-omitempty": false
6984
},
70-
"S": {
85+
"NI": {
86+
"format": "int32",
87+
"nullable": true,
7188
"type": "integer",
72-
"format": "int32"
73-
},
74-
"nnNnnN":{
75-
"type":"string"
76-
},
77-
"data": {
78-
"$ref": "#/components/schemas/V"
89+
"x-omitempty": false
7990
},
8091
"NS": {
8192
"nullable": true,
82-
"type": "string"
93+
"type": "string",
94+
"x-omitempty": false
8395
},
84-
"NI" : {
85-
"nullable": true,
96+
"S": {
97+
"format": "int32",
8698
"type": "integer",
87-
"format": "int32"
99+
"x-omitempty": false
100+
},
101+
"data": {
102+
"$ref": "#/components/schemas/V",
103+
"x-omitempty": false
104+
},
105+
"nnNnnN": {
106+
"type": "string",
107+
"x-omitempty": false
88108
}
89109
},
90110
"required": [
91111
"A",
92112
"H",
93113
"K"
94114
]
95-
}
115+
}

0 commit comments

Comments
 (0)