Skip to content

Commit 729a349

Browse files
authored
misc: sort type properties before generating. (#320)
In order to support deterministic generated code, sort the keys in the type properties map before generating.
1 parent 7037f6b commit 729a349

File tree

4 files changed

+22
-63
lines changed

4 files changed

+22
-63
lines changed

internal/generate/paths.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,7 @@ func generatePaths(file string, spec *openapi3.T) error {
5353
defer f.Close()
5454

5555
// Iterate over all the paths in the spec and write the methods.
56-
// We want to ensure we keep the order.
57-
keys := make([]string, 0)
58-
for k := range spec.Paths.Map() {
59-
keys = append(keys, k)
60-
}
61-
sort.Strings(keys)
56+
keys := sortedKeys(spec.Paths.Map())
6257
for _, path := range keys {
6358
p := spec.Paths.Map()[path]
6459
if p.Ref != "" {

internal/generate/responses.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package main
66

77
import (
88
"fmt"
9-
"sort"
109
"strings"
1110

1211
"github.com/getkin/kin-openapi/openapi3"
@@ -25,12 +24,7 @@ func generateResponses(file string, spec *openapi3.T) error {
2524
enumCollection := make([]EnumTemplate, 0)
2625
typeValidationCollection := make([]ValidationTemplate, 0)
2726
// Iterate over all the responses in the spec and write the types.
28-
// We want to ensure we keep the order so the diffs don't look like shit.
29-
keys := make([]string, 0)
30-
for k := range spec.Components.Responses {
31-
keys = append(keys, k)
32-
}
33-
sort.Strings(keys)
27+
keys := sortedKeys(spec.Components.Responses)
3428
for _, name := range keys {
3529
r := spec.Components.Responses[name]
3630
if r.Ref != "" {

internal/generate/types.go

Lines changed: 12 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,15 @@ func generateTypes(file string, spec *openapi3.T) error {
8181
func constructParamTypes(paths map[string]*openapi3.PathItem) []TypeTemplate {
8282
paramTypes := make([]TypeTemplate, 0)
8383

84-
keys := make([]string, 0)
85-
for k := range paths {
86-
keys = append(keys, k)
87-
}
88-
sort.Strings(keys)
84+
keys := sortedKeys(paths)
8985
for _, path := range keys {
9086
p := paths[path]
9187
if p.Ref != "" {
9288
fmt.Printf("[WARN] TODO: skipping path for %q, since it is a reference\n", path)
9389
continue
9490
}
9591
ops := p.Operations()
96-
keys := make([]string, 0)
97-
for k := range ops {
98-
keys = append(keys, k)
99-
}
100-
sort.Strings(keys)
92+
keys := sortedKeys(ops)
10193
for _, op := range keys {
10294
o := ops[op]
10395
requiredFields := ""
@@ -195,23 +187,15 @@ func constructParamTypes(paths map[string]*openapi3.PathItem) []TypeTemplate {
195187
func constructParamValidation(paths map[string]*openapi3.PathItem) []ValidationTemplate {
196188
validationMethods := make([]ValidationTemplate, 0)
197189

198-
keys := make([]string, 0)
199-
for k := range paths {
200-
keys = append(keys, k)
201-
}
202-
sort.Strings(keys)
190+
keys := sortedKeys(paths)
203191
for _, path := range keys {
204192
p := paths[path]
205193
if p.Ref != "" {
206194
fmt.Printf("[WARN] TODO: skipping path for %q, since it is a reference\n", path)
207195
continue
208196
}
209197
ops := p.Operations()
210-
keys := make([]string, 0)
211-
for k := range ops {
212-
keys = append(keys, k)
213-
}
214-
sort.Strings(keys)
198+
keys := sortedKeys(ops)
215199
for _, op := range keys {
216200
o := ops[op]
217201
if len(o.Parameters) > 0 || o.RequestBody != nil {
@@ -256,11 +240,7 @@ func constructTypes(schemas openapi3.Schemas) ([]TypeTemplate, []EnumTemplate) {
256240
typeCollection := make([]TypeTemplate, 0)
257241
enumCollection := make([]EnumTemplate, 0)
258242

259-
keys := make([]string, 0)
260-
for k := range schemas {
261-
keys = append(keys, k)
262-
}
263-
sort.Strings(keys)
243+
keys := sortedKeys(schemas)
264244
for _, name := range keys {
265245
s := schemas[name]
266246
if s.Ref != "" {
@@ -300,12 +280,7 @@ func constructEnums(enumStrCollection map[string][]string) []EnumTemplate {
300280
enumCollection := make([]EnumTemplate, 0)
301281

302282
// Iterate over all the enum types and add in the slices.
303-
// We want to ensure we keep the order
304-
keys := make([]string, 0)
305-
for k := range enumStrCollection {
306-
keys = append(keys, k)
307-
}
308-
sort.Strings(keys)
283+
keys := sortedKeys(enumStrCollection)
309284
for _, name := range keys {
310285
// TODO: Remove once all types are constructed through structs
311286
enums := enumStrCollection[name]
@@ -445,7 +420,9 @@ func populateTypeTemplates(name string, s *openapi3.Schema, enumFieldName string
445420
typeTpl = createTypeObject(s, name, typeName, formatTypeDescription(typeName, s))
446421

447422
// Iterate over the properties and append the types, if we need to.
448-
for k, v := range s.Properties {
423+
properties := sortedKeys(s.Properties)
424+
for _, k := range properties {
425+
v := s.Properties[k]
449426
if isLocalEnum(v) {
450427
tt, et := populateTypeTemplates(fmt.Sprintf("%s%s", name, strcase.ToCamel(k)), v.Value, "")
451428
types = append(types, tt...)
@@ -497,13 +474,8 @@ func createTypeObject(schema *openapi3.Schema, name, typeName, description strin
497474
}
498475

499476
schemas := schema.Properties
500-
// We want to ensure we keep the order
501-
keys := make([]string, 0)
502-
for k := range schemas {
503-
keys = append(keys, k)
504-
}
505477
fields := []TypeFields{}
506-
sort.Strings(keys)
478+
keys := sortedKeys(schemas)
507479
for _, k := range keys {
508480
v := schemas[k]
509481
// Check if we need to generate a type for this type.
@@ -658,12 +630,7 @@ func createOneOf(s *openapi3.Schema, name, typeName string) ([]TypeTemplate, []E
658630
fields := make([]TypeFields, 0)
659631
for _, v := range s.OneOf {
660632
// Iterate over all the schema components in the spec and write the types.
661-
// We want to ensure we keep the order.
662-
keys := make([]string, 0)
663-
for k := range v.Value.Properties {
664-
keys = append(keys, k)
665-
}
666-
sort.Strings(keys)
633+
keys := sortedKeys(v.Value.Properties)
667634

668635
for _, prop := range keys {
669636
p := v.Value.Properties[prop]
@@ -710,12 +677,7 @@ func createOneOf(s *openapi3.Schema, name, typeName string) ([]TypeTemplate, []E
710677
var enumFieldName string
711678

712679
// Iterate over all the schema components in the spec and write the types.
713-
// We want to ensure we keep the order.
714-
keys := make([]string, 0)
715-
for k := range v.Value.Properties {
716-
keys = append(keys, k)
717-
}
718-
sort.Strings(keys)
680+
keys := sortedKeys(v.Value.Properties)
719681
for _, prop := range keys {
720682
p := v.Value.Properties[prop]
721683
// We want to collect all the unique properties to create our global oneOf type.

internal/generate/utils.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ package main
77
import (
88
"bytes"
99
"fmt"
10+
"maps"
1011
"os"
1112
"path/filepath"
13+
"slices"
1214
"strings"
1315
"unicode"
1416

@@ -265,3 +267,9 @@ func allItemsAreSame[T comparable](a []T) bool {
265267
}
266268
return true
267269
}
270+
271+
// sortedKeys returns a []string of sorted keys from a map. Used to ensure
272+
// deterministic ordering of generated code.
273+
func sortedKeys[T any](m map[string]T) []string {
274+
return slices.Sorted(maps.Keys(m))
275+
}

0 commit comments

Comments
 (0)