Skip to content

Commit f71b59b

Browse files
truschLucasRoesler
andauthored
add hub tests (#2)
* add hub tests Co-authored-by: Lucas Roesler <[email protected]>
1 parent b1e14a7 commit f71b59b

File tree

55 files changed

+1203
-21
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1203
-21
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
bin
2+
*~

pkg/generators/models/enums.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package models
22

33
import (
4+
"bytes"
45
"fmt"
6+
"go/format"
57
"io"
68
"io/ioutil"
7-
"os"
89
"path/filepath"
910
"sort"
1011
"strings"
@@ -84,23 +85,25 @@ func GenerateEnums(specFile io.Reader, dst string, opts Options) error {
8485
modelName := strings.ToLower(strings.ReplaceAll(fmt.Sprintf("model_%s.go", tpl.ToSnakeCase(tctx.Name)), " ", "_"))
8586
filename := filepath.Join(dst, modelName)
8687
logrus.Debugf("writing %s\n", filename)
87-
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
88-
if err != nil {
89-
return err
90-
}
9188

89+
buf := &bytes.Buffer{}
9290
if len(tctx.Values) == 1 {
93-
err = constTemplate.Execute(f, tctx)
91+
err = constTemplate.Execute(buf, tctx)
9492
} else {
95-
err = enumTemplate.Execute(f, tctx)
93+
err = enumTemplate.Execute(buf, tctx)
9694
}
9795
if err != nil {
9896
return fmt.Errorf("failed to generate enum code: %w", err)
9997
}
10098

101-
err = f.Close()
99+
content, err := format.Source(buf.Bytes())
100+
if err != nil {
101+
return fmt.Errorf("failed to format source code: %w", err)
102+
}
103+
104+
err = ioutil.WriteFile(filename, content, 0644)
102105
if err != nil {
103-
return fmt.Errorf("failed to close output file: %w", err)
106+
return fmt.Errorf("failed to write output file: %w", err)
104107
}
105108
}
106109
return nil

pkg/generators/models/models.go

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ func goTypeFromSpec(schemaRef *openapi3.SchemaRef) string {
2323
if schemaRef == nil {
2424
log.Fatal().Msg("got nil schema ref")
2525
}
26+
// add missing object types
27+
if len(schemaRef.Value.Properties) > 0 {
28+
schemaRef.Value.Type = "object"
29+
}
2630
schema := schemaRef.Value
2731
propertyType := schemaRef.Value.Type
2832
switch propertyType {
@@ -63,13 +67,30 @@ func goTypeForObject(schemaRef *openapi3.SchemaRef) (propType string) {
6367
case schemaRef.Value.AdditionalProperties != nil:
6468
subType := goTypeFromSpec(schemaRef.Value.AdditionalProperties)
6569
propType = "map[string]" + subType
70+
case schemaRef.Value.AdditionalPropertiesAllowed != nil && *schemaRef.Value.AdditionalPropertiesAllowed:
71+
propType = "map[string]interface{}"
6672
case len(schemaRef.Value.Properties) > 0:
6773
structBuilder := &strings.Builder{}
6874
structBuilder.WriteString("struct {\n")
69-
for name, ref := range schemaRef.Value.Properties {
75+
for _, name := range sortedKeys(schemaRef.Value.Properties) {
76+
ref := schemaRef.Value.Properties[name]
77+
propName := tpl.ToPascalCase(name)
78+
omitEmpty := true
79+
for _, required := range ref.Value.Required {
80+
if required == propName {
81+
omitEmpty = false
82+
break
83+
}
84+
}
85+
jsonTags := "`json:\"" + name
86+
if omitEmpty {
87+
jsonTags += ",omitempty"
88+
}
89+
jsonTags += "\"`"
7090
structBuilder.WriteString(tpl.ToPascalCase(name))
7191
structBuilder.WriteString(" ")
7292
structBuilder.WriteString(goTypeFromSpec(ref))
93+
structBuilder.WriteString(jsonTags)
7394
structBuilder.WriteString("\n")
7495
}
7596
structBuilder.WriteString("}")
@@ -99,6 +120,11 @@ func GenerateModels(specFile io.Reader, dst string, opts Options) error {
99120

100121
// to do sort and iterate over the sorted schema
101122
for name, s := range swagger.Components.Schemas {
123+
// add forgotten "type: object"
124+
if len(s.Value.Properties) > 0 || len(s.Value.OneOf) > 0 || len(s.Value.AllOf) > 0 {
125+
s.Value.Type = "object"
126+
}
127+
102128
// resolve toplevel allof
103129
if len(s.Value.AllOf) > 0 {
104130
s.Value.Type = "object"
@@ -112,8 +138,17 @@ func GenerateModels(specFile io.Reader, dst string, opts Options) error {
112138
for propName, propSpec := range subSpec.Value.Properties {
113139
s.Value.Properties[propName] = propSpec
114140
}
141+
// Here we bubble up the additionalProperties if we find it in any of the allof entries.
142+
// If we find it, we delete all collected property information and will return an map[string]interface{}
143+
if subSpec.Value.AdditionalPropertiesAllowed != nil && *subSpec.Value.AdditionalPropertiesAllowed {
144+
s.Value.AdditionalPropertiesAllowed = subSpec.Value.AdditionalPropertiesAllowed
145+
}
146+
}
147+
if s.Value.AdditionalPropertiesAllowed != nil && *s.Value.AdditionalPropertiesAllowed {
148+
s.Value.Properties = nil
115149
}
116150
}
151+
117152
if s.Value.Type != "object" {
118153
continue
119154
}
@@ -125,24 +160,28 @@ func GenerateModels(specFile io.Reader, dst string, opts Options) error {
125160
ModelName: tpl.ToPascalCase(name),
126161
Description: s.Value.Description,
127162
}
163+
128164
for propName, propSpec := range s.Value.Properties {
129-
// resolve allof
130165
if len(propSpec.Value.AllOf) > 0 {
131166
propSpec.Value.Type = "object"
132167
if len(propSpec.Value.AllOf) == 1 {
133168
if propSpec.Value.AllOf[0].Ref != "" {
134169
propSpec.Ref = propSpec.Value.AllOf[0].Ref
135170
}
136-
137171
}
138172
propSpec.Value.Properties = make(map[string]*openapi3.SchemaRef)
139173
for _, subSpec := range propSpec.Value.AllOf {
140-
for p, s := range subSpec.Value.Properties {
141-
propSpec.Value.Properties[p] = s
174+
for propName, subPropSpec := range subSpec.Value.Properties {
175+
propSpec.Value.Properties[propName] = subPropSpec
176+
}
177+
if subSpec.Value.AdditionalPropertiesAllowed != nil && *subSpec.Value.AdditionalPropertiesAllowed {
178+
propSpec.Value.AdditionalPropertiesAllowed = subSpec.Value.AdditionalPropertiesAllowed
142179
}
143180
}
181+
if propSpec.Value.AdditionalPropertiesAllowed != nil && *propSpec.Value.AdditionalPropertiesAllowed {
182+
propSpec.Value.Properties = nil
183+
}
144184
}
145-
146185
propertyType := goTypeFromSpec(propSpec)
147186
if propertyType == "time.Time" || propertyType == "*time.Time" {
148187
found := false
@@ -203,6 +242,7 @@ func GenerateModels(specFile io.Reader, dst string, opts Options) error {
203242
return fmt.Errorf("failed to close output file: %w", err)
204243
}
205244
}
245+
206246
return nil
207247
}
208248

@@ -279,3 +319,11 @@ var modelTemplate = template.Must(
279319
Funcs(fmap).
280320
Parse(modelTemplateSource),
281321
)
322+
323+
func sortedKeys(obj map[string]*openapi3.SchemaRef) (res []string) {
324+
for k := range obj {
325+
res = append(res, k)
326+
}
327+
sort.Strings(res)
328+
return res
329+
}

pkg/generators/models/models_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package models
22

33
import (
4+
"bytes"
45
"io/ioutil"
56
"os"
67
"path/filepath"
@@ -60,3 +61,103 @@ func TestGenerateModels(t *testing.T) {
6061
require.NoError(t, err)
6162
require.Equal(t, string(expectedSubType), string(subTypeContent))
6263
}
64+
65+
func TestModels(t *testing.T) {
66+
cases := []struct {
67+
name string
68+
directory string
69+
}{
70+
{
71+
name: "simple model",
72+
directory: "testdata/cases/simple_model",
73+
},
74+
{
75+
name: "embedded type",
76+
directory: "testdata/cases/embedded_type",
77+
},
78+
{
79+
name: "required property removes omitempty",
80+
directory: "testdata/cases/required_properties",
81+
},
82+
{
83+
name: "nullable converts to pointer",
84+
directory: "testdata/cases/nullable_properties",
85+
},
86+
{
87+
name: "untyped object converts to map[string]interface{}",
88+
directory: "testdata/cases/untyped_object",
89+
},
90+
{
91+
name: "oneOf converts to interface{}",
92+
directory: "testdata/cases/oneof",
93+
},
94+
{
95+
name: "allOf merges multiple inlined object definitions on property level",
96+
directory: "testdata/cases/allof1",
97+
},
98+
{
99+
name: "allOf merges multiple inlined object definitions on top level",
100+
directory: "testdata/cases/allof2",
101+
},
102+
{
103+
name: "allOf merges refs and inlined object definitions",
104+
directory: "testdata/cases/allof3",
105+
},
106+
{
107+
name: "allOf can be used in conjunction with nullable to produce pointers to other types",
108+
directory: "testdata/cases/allof4",
109+
},
110+
{
111+
name: "typed arrays generated typed arrays in go",
112+
directory: "testdata/cases/typed_arrays",
113+
},
114+
{
115+
name: "if the array prop is nullable it is NOT converted to a pointer (slices are already nullable in go)",
116+
directory: "testdata/cases/nullable_arrays",
117+
},
118+
{
119+
name: "if an untyped object prop is nullable it is NOT converted to a pointer (maps are already nullable in go)",
120+
directory: "testdata/cases/nullable_untyped_object",
121+
},
122+
{
123+
name: "an untyped object may have additional properties of a specific type",
124+
directory: "testdata/cases/object_with_additional_properties",
125+
},
126+
}
127+
128+
for _, tc := range cases {
129+
t.Run(tc.name, func(t *testing.T) {
130+
dir := filepath.Join(tc.directory, "generated")
131+
err := os.MkdirAll(dir, 0755)
132+
require.NoError(t, err)
133+
bs, err := ioutil.ReadFile(filepath.Join(tc.directory, "api.yaml"))
134+
require.NoError(t, err)
135+
reader := bytes.NewReader(bs)
136+
err = GenerateModels(reader, dir, Options{
137+
PackageName: "generatortest",
138+
})
139+
require.NoError(t, err)
140+
reader = bytes.NewReader(bs)
141+
err = GenerateEnums(reader, dir, Options{
142+
PackageName: "generatortest",
143+
})
144+
require.NoError(t, err)
145+
files, err := filepath.Glob(filepath.Join(dir, "*"))
146+
require.NoError(t, err)
147+
for _, f := range files {
148+
equalFiles(t,
149+
filepath.Join(tc.directory, "expected", filepath.Base(f)),
150+
filepath.Join(tc.directory, "generated", filepath.Base(f)),
151+
)
152+
}
153+
})
154+
}
155+
}
156+
157+
func equalFiles(t *testing.T, expected, actual string) {
158+
bs1, err := ioutil.ReadFile(expected)
159+
require.NoError(t, err)
160+
bs2, err := ioutil.ReadFile(actual)
161+
require.NoError(t, err)
162+
require.Equal(t, string(bs1), string(bs2))
163+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
openapi: 3.0.0
2+
info:
3+
version: 0.1.0
4+
title: Test
5+
6+
components:
7+
schemas:
8+
Foo:
9+
type: object
10+
properties:
11+
bar:
12+
allOf:
13+
- type: object
14+
properties:
15+
foo:
16+
type: string
17+
- type: object
18+
properties:
19+
bar:
20+
type: string
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// This file is auto-generated, DO NOT EDIT.
2+
//
3+
// Source:
4+
// Title: Test
5+
// Version: 0.1.0
6+
package generatortest
7+
8+
// Foo is an object.
9+
type Foo struct {
10+
// Bar:
11+
Bar struct {
12+
Bar string `json:"bar,omitempty"`
13+
Foo string `json:"foo,omitempty"`
14+
} `json:"bar,omitempty"`
15+
}
16+
17+
// GetBar returns the Bar property
18+
func (m Foo) GetBar() struct {
19+
Bar string `json:"bar,omitempty"`
20+
Foo string `json:"foo,omitempty"`
21+
} {
22+
return m.Bar
23+
}
24+
25+
// SetBar sets the Bar property
26+
func (m Foo) SetBar(val struct {
27+
Bar string `json:"bar,omitempty"`
28+
Foo string `json:"foo,omitempty"`
29+
}) {
30+
m.Bar = val
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// This file is auto-generated, DO NOT EDIT.
2+
//
3+
// Source:
4+
// Title: Test
5+
// Version: 0.1.0
6+
package generatortest
7+
8+
// Foo is an object.
9+
type Foo struct {
10+
// Bar:
11+
Bar struct {
12+
Bar string `json:"bar,omitempty"`
13+
Foo string `json:"foo,omitempty"`
14+
} `json:"bar,omitempty"`
15+
}
16+
17+
// GetBar returns the Bar property
18+
func (m Foo) GetBar() struct {
19+
Bar string `json:"bar,omitempty"`
20+
Foo string `json:"foo,omitempty"`
21+
} {
22+
return m.Bar
23+
}
24+
25+
// SetBar sets the Bar property
26+
func (m Foo) SetBar(val struct {
27+
Bar string `json:"bar,omitempty"`
28+
Foo string `json:"foo,omitempty"`
29+
}) {
30+
m.Bar = val
31+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
openapi: 3.0.0
2+
info:
3+
version: 0.1.0
4+
title: Test
5+
6+
components:
7+
schemas:
8+
Foo:
9+
allOf:
10+
- type: object
11+
properties:
12+
foo:
13+
type: string
14+
- type: object
15+
properties:
16+
bar:
17+
type: string

0 commit comments

Comments
 (0)