Skip to content

Commit bd2d5a2

Browse files
committed
Teach struct types to render themselves to go code.
Rather than including all go rendering logic in a large writeTypes function, this patch teaches go structs that represent types and fields to render themselves with Render() methods. Then we can write and test each Render method independently, and combine them in writeTypes.
1 parent 24ae859 commit bd2d5a2

4 files changed

Lines changed: 172 additions & 13 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{{splitDocString .Description}}
2+
{{if .Fields -}}
3+
type {{.Name}} {{.Type}} {
4+
{{range .Fields}}{{.Render}}
5+
{{- end}}}
6+
7+
{{else -}}
8+
type {{.Name}} {{.Type}}
9+
{{end -}}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{{if .Description}} {{splitDocString .Description}}
2+
{{end}} {{.Name}} {{.Type}} {{.SerializationInfo}}

internal/generate/types.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
package main
66

77
import (
8+
"bytes"
89
"fmt"
910
"os"
1011
"slices"
1112
"sort"
1213
"strings"
14+
"text/template"
1315

1416
"github.com/getkin/kin-openapi/openapi3"
1517
"github.com/iancoleman/strcase"
@@ -36,6 +38,24 @@ type TypeTemplate struct {
3638
Fields []TypeFields
3739
}
3840

41+
// Render renders the TypeTemplate as a string using a Go template.
42+
func (t TypeTemplate) Render() string {
43+
funcMap := template.FuncMap{
44+
"splitDocString": splitDocString,
45+
}
46+
47+
tmpl, err := template.New("type.tpl").Funcs(funcMap).ParseFiles("./templates/type.tpl")
48+
if err != nil {
49+
panic(fmt.Sprintf("error parsing type.tpl: %v", err))
50+
}
51+
52+
var buf bytes.Buffer
53+
if err := tmpl.Execute(&buf, t); err != nil {
54+
panic(fmt.Sprintf("error executing type.tpl: %v", err))
55+
}
56+
return buf.String()
57+
}
58+
3959
// TypeFields holds the information for each type field
4060
type TypeFields struct {
4161
Description string
@@ -44,6 +64,24 @@ type TypeFields struct {
4464
SerializationInfo string
4565
}
4666

67+
// Render renders the TypeFields as a string using a Go template.
68+
func (f TypeFields) Render() string {
69+
funcMap := template.FuncMap{
70+
"splitDocString": splitDocString,
71+
}
72+
73+
t, err := template.New("type_field.tpl").Funcs(funcMap).ParseFiles("./templates/type_field.tpl")
74+
if err != nil {
75+
panic(fmt.Sprintf("error parsing type_field.tpl: %v", err))
76+
}
77+
78+
var buf bytes.Buffer
79+
if err := t.Execute(&buf, f); err != nil {
80+
panic(fmt.Sprintf("error executing type_field.tpl: %v", err))
81+
}
82+
return buf.String()
83+
}
84+
4785
// EnumTemplate holds the information for enum types
4886
type EnumTemplate struct {
4987
Description string
@@ -324,19 +362,7 @@ func writeTypes(f *os.File, typeCollection []TypeTemplate, typeValidationCollect
324362
continue
325363
}
326364

327-
fmt.Fprintf(f, "%s\n", splitDocString(tt.Description))
328-
fmt.Fprintf(f, "type %s %s", tt.Name, tt.Type)
329-
if tt.Fields != nil {
330-
fmt.Fprint(f, " {\n")
331-
for _, ft := range tt.Fields {
332-
if ft.Description != "" {
333-
fmt.Fprintf(f, "\t%s\n", splitDocString(ft.Description))
334-
}
335-
fmt.Fprintf(f, "\t%s %s %s\n", ft.Name, ft.Type, ft.SerializationInfo)
336-
}
337-
fmt.Fprint(f, "}\n")
338-
}
339-
fmt.Fprint(f, "\n")
365+
fmt.Fprint(f, tt.Render())
340366
}
341367

342368
// Write all collected validation methods to file

internal/generate/types_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package main
66

77
import (
8+
"fmt"
89
"testing"
910

1011
"github.com/getkin/kin-openapi/openapi3"
@@ -344,3 +345,124 @@ func Test_createAllOf(t *testing.T) {
344345
})
345346
}
346347
}
348+
349+
func Test_TypeFields_Render(t *testing.T) {
350+
nameTag := "`json:\"name,omitempty\" yaml:\"name,omitempty\"`"
351+
352+
tests := []struct {
353+
name string
354+
field TypeFields
355+
want string
356+
}{
357+
{
358+
name: "field without description",
359+
field: TypeFields{
360+
Name: "Name",
361+
Type: "string",
362+
SerializationInfo: nameTag,
363+
},
364+
want: fmt.Sprintf(` Name string %s
365+
`, nameTag),
366+
},
367+
{
368+
name: "field with description",
369+
field: TypeFields{
370+
Description: "// Name is the name of the resource",
371+
Name: "Name",
372+
Type: "string",
373+
SerializationInfo: nameTag,
374+
},
375+
want: fmt.Sprintf(` // Name is the name of the resource
376+
Name string %s
377+
`, nameTag),
378+
},
379+
}
380+
for _, tt := range tests {
381+
t.Run(tt.name, func(t *testing.T) {
382+
got := tt.field.Render()
383+
assert.Equal(t, tt.want, got)
384+
})
385+
}
386+
}
387+
388+
func Test_TypeTemplate_Render(t *testing.T) {
389+
nameTag := "`json:\"name,omitempty\" yaml:\"name,omitempty\"`"
390+
streetTag := "`json:\"street\" yaml:\"street\"`"
391+
cityTag := "`json:\"city\" yaml:\"city\"`"
392+
393+
tests := []struct {
394+
name string
395+
template TypeTemplate
396+
want string
397+
}{
398+
{
399+
name: "primitive type without fields",
400+
template: TypeTemplate{
401+
Description: "// FleetRole is the type definition for a FleetRole.",
402+
Name: "FleetRole",
403+
Type: "string",
404+
},
405+
want: `// FleetRole is the type definition for a FleetRole.
406+
type FleetRole string
407+
`,
408+
},
409+
{
410+
name: "struct type with fields",
411+
template: TypeTemplate{
412+
Description: "// DiskIdentifier is the identifier for a disk.",
413+
Name: "DiskIdentifier",
414+
Type: "struct",
415+
Fields: []TypeFields{
416+
{
417+
Name: "Name",
418+
Type: "string",
419+
SerializationInfo: nameTag,
420+
},
421+
},
422+
},
423+
want: fmt.Sprintf(`// DiskIdentifier is the identifier for a disk.
424+
type DiskIdentifier struct {
425+
Name string %s
426+
}
427+
428+
`, nameTag),
429+
},
430+
{
431+
name: "struct type with field descriptions",
432+
template: TypeTemplate{
433+
Description: "// Address is an address.",
434+
Name: "Address",
435+
Type: "struct",
436+
Fields: []TypeFields{
437+
{
438+
Description: "// Street is the street name",
439+
Name: "Street",
440+
Type: "string",
441+
SerializationInfo: streetTag,
442+
},
443+
{
444+
Description: "// City is the city name",
445+
Name: "City",
446+
Type: "string",
447+
SerializationInfo: cityTag,
448+
},
449+
},
450+
},
451+
want: fmt.Sprintf(`// Address is an address.
452+
type Address struct {
453+
// Street is the street name
454+
Street string %s
455+
// City is the city name
456+
City string %s
457+
}
458+
459+
`, streetTag, cityTag),
460+
},
461+
}
462+
for _, tt := range tests {
463+
t.Run(tt.name, func(t *testing.T) {
464+
got := tt.template.Render()
465+
assert.Equal(t, tt.want, got)
466+
})
467+
}
468+
}

0 commit comments

Comments
 (0)