Skip to content

Commit 0bf3876

Browse files
authored
Merge pull request #537 from jpbetz/model-namer
Add OpenAPIModelNamer and opt-in generator support
2 parents d353059 + ab9df5f commit 0bf3876

File tree

9 files changed

+1510
-18
lines changed

9 files changed

+1510
-18
lines changed

cmd/openapi-gen/args/args.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ type Args struct {
3333
// by API linter. If specified, API rule violations will be printed to report file.
3434
// Otherwise default value "-" will be used which indicates stdout.
3535
ReportFilename string
36+
37+
// UseOpenAPIModelNames specifies the use of OpenAPI model names instead of
38+
// Go '<package>.<type>' names for types in the OpenAPI spec.
39+
UseOpenAPIModelNames bool
3640
}
3741

3842
// New returns default arguments for the generator. Returning the arguments instead
@@ -58,6 +62,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
5862
"the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year")
5963
fs.StringVarP(&args.ReportFilename, "report-filename", "r", args.ReportFilename,
6064
"Name of report file used by API linter to print API violations. Default \"-\" stands for standard output. NOTE that if valid filename other than \"-\" is specified, API linter won't return error on detected API violations. This allows further check of existing API violations without stopping the OpenAPI generation toolchain.")
65+
fs.BoolVar(&args.UseOpenAPIModelNames, "use-openapi-model-names", false, "Use OpenAPI model names instead of Go '<package>.<type>' names for types in the OpenAPI spec.")
6166
}
6267

6368
// Validate checks the given arguments.

pkg/generators/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
7474
newOpenAPIGen(
7575
args.OutputFile,
7676
args.OutputPkg,
77+
args.UseOpenAPIModelNames,
7778
),
7879
newAPIViolationGen(),
7980
}

pkg/generators/openapi.go

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,19 @@ const (
127127
type openAPIGen struct {
128128
generator.GoGenerator
129129
// TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
130-
targetPackage string
131-
imports namer.ImportTracker
130+
targetPackage string
131+
imports namer.ImportTracker
132+
useOpenAPIModelNames bool
132133
}
133134

134-
func newOpenAPIGen(outputFilename string, targetPackage string) generator.Generator {
135+
func newOpenAPIGen(outputFilename string, targetPackage string, useOpenAPIModelNames bool) generator.Generator {
135136
return &openAPIGen{
136137
GoGenerator: generator.GoGenerator{
137138
OutputFilename: outputFilename,
138139
},
139-
imports: generator.NewImportTrackerForPackage(targetPackage),
140-
targetPackage: targetPackage,
140+
imports: generator.NewImportTrackerForPackage(targetPackage),
141+
targetPackage: targetPackage,
142+
useOpenAPIModelNames: useOpenAPIModelNames,
141143
}
142144
}
143145

@@ -179,7 +181,7 @@ func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
179181
sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
180182

181183
for _, t := range c.Order {
182-
err := newOpenAPITypeWriter(sw, c).generateCall(t)
184+
err := newOpenAPITypeWriter(sw, c, g.useOpenAPIModelNames).generateCall(t)
183185
if err != nil {
184186
return err
185187
}
@@ -194,7 +196,7 @@ func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
194196
func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
195197
klog.V(5).Infof("generating for type %v", t)
196198
sw := generator.NewSnippetWriter(w, c, "$", "$")
197-
err := newOpenAPITypeWriter(sw, c).generate(t)
199+
err := newOpenAPITypeWriter(sw, c, g.useOpenAPIModelNames).generate(t)
198200
if err != nil {
199201
return err
200202
}
@@ -233,14 +235,16 @@ type openAPITypeWriter struct {
233235
refTypes map[string]*types.Type
234236
enumContext *enumContext
235237
GetDefinitionInterface *types.Type
238+
useOpenAPIModelNames bool
236239
}
237240

238-
func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context) openAPITypeWriter {
241+
func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context, useOpenAPIModelNames bool) openAPITypeWriter {
239242
return openAPITypeWriter{
240-
SnippetWriter: sw,
241-
context: c,
242-
refTypes: map[string]*types.Type{},
243-
enumContext: newEnumContext(c),
243+
SnippetWriter: sw,
244+
context: c,
245+
refTypes: map[string]*types.Type{},
246+
enumContext: newEnumContext(c),
247+
useOpenAPIModelNames: useOpenAPIModelNames,
244248
}
245249
}
246250

@@ -339,8 +343,18 @@ func (g openAPITypeWriter) generateCall(t *types.Type) error {
339343
// Only generate for struct type and ignore the rest
340344
switch t.Kind {
341345
case types.Struct:
346+
if namer.IsPrivateGoName(t.Name.Name) { // skip private types
347+
return nil
348+
}
349+
342350
args := argsFromType(t)
343-
g.Do("\"$.$\": ", t.Name)
351+
352+
if g.useOpenAPIModelNames {
353+
g.Do("$.|raw${}.OpenAPIModelName(): ", t)
354+
} else {
355+
// Legacy case: use the "canonical type name"
356+
g.Do("\"$.$\": ", t.Name)
357+
}
344358

345359
hasV2Definition := hasOpenAPIDefinitionMethod(t)
346360
hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t)
@@ -667,7 +681,12 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
667681
if len(deps) > 0 {
668682
g.Do("Dependencies: []string{\n", args)
669683
for _, k := range deps {
670-
g.Do("\"$.$\",", k)
684+
t := g.refTypes[k]
685+
if g.useOpenAPIModelNames {
686+
g.Do("$.|raw${}.OpenAPIModelName(),", t)
687+
} else {
688+
g.Do("\"$.$\",", k)
689+
}
671690
}
672691
g.Do("},\n", nil)
673692
}
@@ -1027,7 +1046,11 @@ func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
10271046

10281047
func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
10291048
g.refTypes[t.Name.String()] = t
1030-
g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
1049+
if g.useOpenAPIModelNames {
1050+
g.Do("Ref: ref($.|raw${}.OpenAPIModelName()),\n", t)
1051+
} else {
1052+
g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
1053+
}
10311054
}
10321055

10331056
func resolvePtrType(t *types.Type) *types.Type {

pkg/generators/openapi_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ func testOpenAPITypeWriter(t *testing.T, cfg *packages.Config) (error, error, *b
6868

6969
callBuffer := &bytes.Buffer{}
7070
callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$")
71-
callError := newOpenAPITypeWriter(callSW, context).generateCall(blahT)
71+
callError := newOpenAPITypeWriter(callSW, context, false).generateCall(blahT)
7272

7373
funcBuffer := &bytes.Buffer{}
7474
funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$")
75-
funcError := newOpenAPITypeWriter(funcSW, context).generate(blahT)
75+
funcError := newOpenAPITypeWriter(funcSW, context, false).generate(blahT)
7676

7777
return callError, funcError, callBuffer, funcBuffer, imports.ImportLines()
7878
}

pkg/util/util.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,21 @@ type OpenAPICanonicalTypeNamer interface {
9292
OpenAPICanonicalTypeName() string
9393
}
9494

95+
// OpenAPIModelNamer is an interface Go types may implement to provide an OpenAPI model name.
96+
//
97+
// This takes precedence over OpenAPICanonicalTypeNamer, and should be used when a Go type has a model
98+
// name that differs from its canonical type name as determined by Go package name reflection.
99+
type OpenAPIModelNamer interface {
100+
OpenAPIModelName() string
101+
}
102+
95103
// GetCanonicalTypeName will find the canonical type name of a sample object, removing
96104
// the "vendor" part of the path
97105
func GetCanonicalTypeName(model interface{}) string {
98-
if namer, ok := model.(OpenAPICanonicalTypeNamer); ok {
106+
switch namer := model.(type) {
107+
case OpenAPIModelNamer:
108+
return namer.OpenAPIModelName()
109+
case OpenAPICanonicalTypeNamer:
99110
return namer.OpenAPICanonicalTypeName()
100111
}
101112
t := reflect.TypeOf(model)

test/integration/integration_suite_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,23 @@ var _ = BeforeSuite(func() {
104104
Expect(err).ShouldNot(HaveOccurred())
105105
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
106106

107+
// Run the OpenAPI code generator with --use-openapi-model-names
108+
Expect(terr).ShouldNot(HaveOccurred())
109+
110+
By("'namedmodels' running openapi-gen")
111+
args = append([]string{
112+
"--output-dir", tempDir + "/namedmodels",
113+
"--output-pkg", outputPkg + "/namedmodels",
114+
"--output-file", generatedCodeFileName,
115+
"--use-openapi-model-names",
116+
"--go-header-file", headerFilePath,
117+
}, path.Join(testPkgRoot, "namedmodels"))
118+
command = exec.Command(openAPIGenPath, args...)
119+
command.Dir = workingDirectory
120+
session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter)
121+
Expect(err).ShouldNot(HaveOccurred())
122+
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
123+
107124
By("writing swagger v2.0")
108125
// Create the OpenAPI swagger builder.
109126
binaryPath, berr = gexec.Build("./builder/main.go")
@@ -131,6 +148,20 @@ var _ = BeforeSuite(func() {
131148
session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter)
132149
Expect(err).ShouldNot(HaveOccurred())
133150
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
151+
152+
By("'namedmodels' writing OpenAPI v3.0")
153+
// Create the OpenAPI swagger builder.
154+
binaryPath, berr = gexec.Build("./builder3/main.go")
155+
Expect(berr).ShouldNot(HaveOccurred())
156+
157+
// Execute the builder, generating an OpenAPI swagger file with definitions.
158+
gov3 = generatedFile("namedmodels/" + generatedOpenAPIv3FileName)
159+
By("'namedmodels' writing swagger to " + gov3)
160+
command = exec.Command(binaryPath, gov3)
161+
command.Dir = workingDirectory
162+
session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter)
163+
Expect(err).ShouldNot(HaveOccurred())
164+
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
134165
})
135166

136167
var _ = AfterSuite(func() {
@@ -152,6 +183,18 @@ var _ = Describe("Open API Definitions Generation", func() {
152183
Expect(err).ShouldNot(HaveOccurred())
153184
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
154185
})
186+
It("'namedmodels' Generated code should match golden files", func() {
187+
// Diff the generated code against the golden code. Exit code should be zero.
188+
command := exec.Command(
189+
"diff", "-u",
190+
"pkg/generated/namedmodels/"+generatedCodeFileName,
191+
generatedFile("namedmodels/"+generatedCodeFileName),
192+
)
193+
command.Dir = workingDirectory
194+
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
195+
Expect(err).ShouldNot(HaveOccurred())
196+
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
197+
})
155198
})
156199

157200
Describe("Validating OpenAPI V2 Definition Generation", func() {

test/integration/pkg/generated/namedmodels/openapi_generated.go

Lines changed: 94 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)