Skip to content

Commit 34a2606

Browse files
authored
Merge pull request #4 from solo-io/configure-types-to-skip-schemas
Configure types to skip schemas
2 parents 41ace51 + bcd398a commit 34a2606

File tree

5 files changed

+86
-53
lines changed

5 files changed

+86
-53
lines changed

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,10 @@ Other supported options are:
6666
* `use_ref`
6767
* when set to `true`, the output uses the `$ref` field in OpenAPI spec to reference other schemas.
6868
* `yaml`
69-
* when set to `true`, the output is in yaml file.
69+
* when set to `true`, the output is in yaml file.
70+
* `include_description`
71+
* when set to `true`, the openapi schema will include descriptions, generated from the proto message comment.
72+
* `enum_as_int_or_string`
73+
* when set to `true`, the openapi schema will include `x-kubernetes-int-or-string` on enums.
74+
* `additional_empty_schemas`
75+
* a `+` separated list of message names (`core.solo.io.Status`), whose generated schema should be an empty object that accepts all values.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
changelog:
2+
- type: BREAKING_CHANGE
3+
description: Support injecting a set of message names to generate empty validation schema
4+
issueLink: https://github.com/solo-io/gloo/issues/4789
5+
resolvesIssue: false

go.mod

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
module github.com/solo-io/protoc-gen-openapi
22

3-
go 1.16
3+
go 1.18
44

55
require (
66
github.com/getkin/kin-openapi v0.80.0
77
github.com/ghodss/yaml v1.0.0
88
github.com/golang/protobuf v1.3.2
99
)
10+
11+
require (
12+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
13+
github.com/go-openapi/swag v0.19.5 // indirect
14+
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
15+
gopkg.in/yaml.v2 v2.3.0 // indirect
16+
)

main.go

+12-10
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package main
1616

1717
import (
1818
"fmt"
19-
"strconv"
2019
"strings"
2120

2221
"github.com/solo-io/protoc-gen-openapi/pkg/protocgen"
@@ -49,9 +48,9 @@ func generate(request plugin.CodeGeneratorRequest) (*plugin.CodeGeneratorRespons
4948
singleFile := false
5049
yaml := false
5150
useRef := false
52-
maxCharactersInDescription := 0
5351
includeDescription := true
5452
enumAsIntOrString := false
53+
var messagesWithEmptySchema []string
5554

5655
p := extractParams(request.GetParameter())
5756
for k, v := range p {
@@ -87,12 +86,6 @@ func generate(request plugin.CodeGeneratorRequest) (*plugin.CodeGeneratorRespons
8786
default:
8887
return nil, fmt.Errorf("unknown value '%s' for use_ref", v)
8988
}
90-
} else if k == "max_description_characters" {
91-
var err error
92-
maxCharactersInDescription, err = strconv.Atoi(v)
93-
if err != nil {
94-
return nil, fmt.Errorf("unknown value '%s' for max_description_characters", v)
95-
}
9689
} else if k == "include_description" {
9790
switch strings.ToLower(v) {
9891
case "true":
@@ -111,6 +104,8 @@ func generate(request plugin.CodeGeneratorRequest) (*plugin.CodeGeneratorRespons
111104
default:
112105
return nil, fmt.Errorf("unknown value '%s' for enum_as_int_or_string", v)
113106
}
107+
} else if k == "additional_empty_schema" {
108+
messagesWithEmptySchema = strings.Split(v, "+")
114109
} else {
115110
return nil, fmt.Errorf("unknown argument '%s' specified", k)
116111
}
@@ -128,11 +123,18 @@ func generate(request plugin.CodeGeneratorRequest) (*plugin.CodeGeneratorRespons
128123
}
129124

130125
descriptionConfiguration := &DescriptionConfiguration{
131-
MaxDescriptionCharacters: maxCharactersInDescription,
132126
IncludeDescriptionInSchema: includeDescription,
133127
}
134128

135-
g := newOpenAPIGenerator(m, perFile, singleFile, yaml, useRef, descriptionConfiguration, enumAsIntOrString)
129+
g := newOpenAPIGenerator(
130+
m,
131+
perFile,
132+
singleFile,
133+
yaml,
134+
useRef,
135+
descriptionConfiguration,
136+
enumAsIntOrString,
137+
messagesWithEmptySchema)
136138
return g.generateOutput(filesToGen)
137139
}
138140

openapiGenerator.go

+54-41
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,11 @@ import (
3535
// Some special types with predefined schemas.
3636
// This is to catch cases where solo apis contain recursive definitions
3737
// Normally these would result in stack-overflow errors when generating the open api schema
38-
// The imperfect solution, is to just genrate an empty object for these types
38+
// The imperfect solution, is to just generate an empty object for these types
3939
var specialSoloTypes = map[string]openapi3.Schema{
40-
"core.solo.io.Status": {
41-
Type: openapi3.TypeObject,
42-
Properties: make(map[string]*openapi3.SchemaRef),
43-
ExtensionProps: openapi3.ExtensionProps{
44-
Extensions: map[string]interface{}{
45-
"x-kubernetes-preserve-unknown-fields": true,
46-
},
47-
},
48-
},
4940
"core.solo.io.Metadata": {
5041
Type: openapi3.TypeObject,
5142
},
52-
"ratelimit.api.solo.io.Descriptor": {
53-
Type: openapi3.TypeObject,
54-
Properties: make(map[string]*openapi3.SchemaRef),
55-
ExtensionProps: openapi3.ExtensionProps{
56-
Extensions: map[string]interface{}{
57-
"x-kubernetes-preserve-unknown-fields": true,
58-
},
59-
},
60-
},
6143
"google.protobuf.ListValue": *openapi3.NewArraySchema().WithItems(openapi3.NewObjectSchema()),
6244
"google.protobuf.Struct": {
6345
Type: openapi3.TypeObject,
@@ -116,31 +98,68 @@ type openapiGenerator struct {
11698
// @solo.io customization to support enum validation schemas with int or string values
11799
// we need to support this since some controllers marshal enums as integers and others as strings
118100
enumAsIntOrString bool
101+
102+
// @solo.io customizations to define schemas for certain messages
103+
customSchemasByMessageName map[string]openapi3.Schema
119104
}
120105

121106
type DescriptionConfiguration struct {
122107
// Whether or not to include a description in the generated open api schema
123108
IncludeDescriptionInSchema bool
124-
125-
// The maximum number of characters to include in a description
126-
// If IncludeDescriptionsInSchema is set to false, this will be ignored
127-
// A 0 value will be interpreted as "include all characters"
128-
// Default: 0
129-
MaxDescriptionCharacters int
130109
}
131110

132-
func newOpenAPIGenerator(model *protomodel.Model, perFile bool, singleFile bool, yaml bool, useRef bool, descriptionConfiguration *DescriptionConfiguration, enumAsIntOrString bool) *openapiGenerator {
111+
func newOpenAPIGenerator(
112+
model *protomodel.Model,
113+
perFile bool,
114+
singleFile bool,
115+
yaml bool,
116+
useRef bool,
117+
descriptionConfiguration *DescriptionConfiguration,
118+
enumAsIntOrString bool,
119+
messagesWithEmptySchema []string,
120+
) *openapiGenerator {
133121
return &openapiGenerator{
134-
model: model,
135-
perFile: perFile,
136-
singleFile: singleFile,
137-
yaml: yaml,
138-
useRef: useRef,
139-
descriptionConfiguration: descriptionConfiguration,
140-
enumAsIntOrString: enumAsIntOrString,
122+
model: model,
123+
perFile: perFile,
124+
singleFile: singleFile,
125+
yaml: yaml,
126+
useRef: useRef,
127+
descriptionConfiguration: descriptionConfiguration,
128+
enumAsIntOrString: enumAsIntOrString,
129+
customSchemasByMessageName: buildCustomSchemasByMessageName(messagesWithEmptySchema),
141130
}
142131
}
143132

133+
// buildCustomSchemasByMessageName name returns a mapping of message name to a pre-defined openapi schema
134+
// It includes:
135+
// 1. `specialSoloTypes`, a set of pre-defined schemas
136+
// 2. `messagesWithEmptySchema`, a list of messages that are injected at runtime that should contain
137+
// and empty schema which accepts and preserves all fields
138+
func buildCustomSchemasByMessageName(messagesWithEmptySchema []string) map[string]openapi3.Schema {
139+
schemasByMessageName := make(map[string]openapi3.Schema)
140+
141+
// Initialize the hard-coded values
142+
for name, schema := range specialSoloTypes {
143+
schemasByMessageName[name] = schema
144+
}
145+
146+
// Add the messages that were injected at runtime
147+
for _, messageName := range messagesWithEmptySchema {
148+
emptyMessage := openapi3.Schema{
149+
Type: openapi3.TypeObject,
150+
Properties: make(map[string]*openapi3.SchemaRef),
151+
ExtensionProps: openapi3.ExtensionProps{
152+
Extensions: map[string]interface{}{
153+
"x-kubernetes-preserve-unknown-fields": true,
154+
},
155+
},
156+
}
157+
schemasByMessageName[messageName] = emptyMessage
158+
}
159+
160+
return schemasByMessageName
161+
}
162+
144163
func (g *openapiGenerator) generateOutput(filesToGen map[*protomodel.FileDescriptor]bool) (*plugin.CodeGeneratorResponse, error) {
145164
response := plugin.CodeGeneratorResponse{}
146165

@@ -438,13 +457,7 @@ func (g *openapiGenerator) generateDescription(desc protomodel.CoreDesc) string
438457
return ""
439458
}
440459

441-
fullDescription := strings.Join(strings.Fields(t), " ")
442-
maxCharacters := g.descriptionConfiguration.MaxDescriptionCharacters
443-
if maxCharacters > 0 && len(fullDescription) > maxCharacters {
444-
// return the first [maxCharacters] characters, including an ellipsis to mark that it has been truncated
445-
return fmt.Sprintf("%s...", fullDescription[0:maxCharacters])
446-
}
447-
return fullDescription
460+
return strings.Join(strings.Fields(t), " ")
448461
}
449462

450463
func (g *openapiGenerator) fieldType(field *protomodel.FieldDescriptor) *openapi3.Schema {
@@ -474,7 +487,7 @@ func (g *openapiGenerator) fieldType(field *protomodel.FieldDescriptor) *openapi
474487

475488
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
476489
msg := field.FieldType.(*protomodel.MessageDescriptor)
477-
if soloSchema, ok := specialSoloTypes[g.absoluteName(msg)]; ok {
490+
if soloSchema, ok := g.customSchemasByMessageName[g.absoluteName(msg)]; ok {
478491
// Allow for defining special Solo types
479492
schema = g.generateSoloMessageSchema(msg, &soloSchema)
480493
} else if msg.GetOptions().GetMapEntry() {

0 commit comments

Comments
 (0)