Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(openapi): generated classes implement interfaces #6500

Merged
merged 1 commit into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,26 +77,22 @@ func addOrAppend(commentLines []string, prefix, value string) []string {
}
return commentLines
}
func publicInterfaceName(name string) string {
if unicode.IsUpper(rune(name[0])) {
return name
}
return string(unicode.ToUpper(rune(name[0]))) + name[1:]
}

// func processProtobufOneof
// To generate interfaces and extending classes for oneof fields
// This is something extensively used in the Istio API, that uses these as marker interfaces
//
// For processing we'll add +k8s:openapi-gen=x-kubernetes tags that will be later processed by kube-openapi and added to the OpenAPI json spec
func processProtobufOneof(_ *generator.Context, pkg *types.Package, t *types.Type, m *types.Member, memberIndex int) {
publicInterfaceName := func(name string) string {
if unicode.IsUpper(rune(name[0])) {
return name
}
return string(unicode.ToUpper(rune(name[0]))) + name[1:]
}
// Interfaces
protobufOneOf := reflect.StructTag(m.Tags).Get("protobuf_oneof")
if protobufOneOf != "" {
// kube-openapi doesn't handle interfaces, so we need to change the interface to a struct
m.Type.Kind = types.Struct
// Ensure it's exported
t.Members[memberIndex].Type.Name.Name = publicInterfaceName(m.Type.Name.Name)
//// Add comment tag to the referenced type and mark it as an interface
t.Members[memberIndex].Type.CommentLines = append(m.Type.CommentLines, "+k8s:openapi-gen=x-kubernetes-fabric8-type:interface")
// Add comment tag to the current type to mark it as it has fields that are interfaces (useful for the OpenAPI Java generator)
Expand All @@ -119,13 +115,36 @@ func processProtobufOneof(_ *generator.Context, pkg *types.Package, t *types.Typ
}
}

// processProtobufPackageOneOf function to process the protobuf package and change the interfaces to structs
// kube-openapi doesn't handle interfaces, so we need to change the interface to a struct
func processProtobufPackageOneOf(_ *generator.Context, pkg *types.Package) {
for _, t := range pkg.Types {
if t.Kind != types.Interface {
continue
}
openApiGen := gengo.ExtractCommentTags("+", t.CommentLines)["k8s:openapi-gen"]
if len(openApiGen) > 0 {
for _, openApiGenValue := range openApiGen {
if openApiGenValue == "x-kubernetes-fabric8-type:interface" {
// Change to Struct so that it's processed by kube-openapi
t.Kind = types.Struct
// Ensure it's public so that it can be exported
t.Name.Name = publicInterfaceName(t.Name.Name)
break
}
}
}
}
}

// processProtobufTags function to process the protobuf field tags and fix json tags that might have mismatched names for serialization
// This happens for most of the istio APIs
func processProtobufTags(_ *generator.Context, _ *types.Package, t *types.Type, m *types.Member, memberIndex int) {
tags := reflect.StructTag(m.Tags)
protobufTag := tags.Get("protobuf")
jsonTag := tags.Get("json")
if protobufTag != "" && strings.Contains(protobufTag, "json=") {
// TODO, consider also name= (sometimes this is included instad of json=)
name := strings.Split(protobufTag, "json=")[1]
name = strings.Split(name, ",")[0]
var updatedJsonTag string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,25 @@ type GoGenerator struct {
openapiargs.Args
Patterns []string
inputPkgs map[string]bool
processors []func(context *generator.Context, pkg *types.Package, t *types.Type, member *types.Member, memberIndex int)
// memberProcessors are functions that are applied to each member of a type
memberProcessors []func(context *generator.Context, pkg *types.Package, t *types.Type, member *types.Member, memberIndex int)
// packageProcessors are functions that are applied to each package once all type members have been processed for that package
packageProcessors []func(context *generator.Context, pkg *types.Package)
}

func (g *GoGenerator) Generate() error {
g.ReportFilename = g.OutputFile + ".report.txt"
g.processors = []func(context *generator.Context, pkg *types.Package, t *types.Type, member *types.Member, memberIndex int){
g.memberProcessors = []func(context *generator.Context, pkg *types.Package, t *types.Type, member *types.Member, memberIndex int){
processMapKeyTypes,
processOmitPrivateFields,
processPatchComments,
processProtobufOneof,
processProtobufTags,
processSwaggerIgnore,
}
g.packageProcessors = []func(context *generator.Context, pkg *types.Package){
processProtobufPackageOneOf,
}
return gengo.Execute(
generators.NameSystems(),
generators.DefaultNameSystem(),
Expand Down Expand Up @@ -89,11 +95,14 @@ func (g *GoGenerator) processUniverse(context *generator.Context) {
for _, pkg := range context.Universe {
for _, t := range pkg.Types {
for memberIndex, member := range t.Members {
for _, processor := range g.processors {
processor(context, pkg, t, &member, memberIndex)
for _, memeberProcessor := range g.memberProcessors {
memeberProcessor(context, pkg, t, &member, memberIndex)
}
}
}
for _, packageProcessor := range g.packageProcessors {
packageProcessor(context, pkg)
}
}

// Non-deterministic bug-fix
Expand Down
10 changes: 0 additions & 10 deletions kubernetes-model-generator/openapi/maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,6 @@
<artifactId>swagger-parser</artifactId>
<version>2.1.23</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Loading