Skip to content

Commit

Permalink
protoc-gen-swagger: add fqn_for_swagger_name option
Browse files Browse the repository at this point in the history
Adds a new option fqn_for_swagger_name.
This uses the protobuf FQN in the generated swagger specification.
  • Loading branch information
hypnoce authored and johanbrandhorst committed Mar 1, 2019
1 parent fff1507 commit 0c2b3b1
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 17 deletions.
16 changes: 16 additions & 0 deletions protoc-gen-grpc-gateway/descriptor/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ type Registry struct {
// otherwise the original proto name is used. It's helpful for synchronizing the swagger definition
// with grpc-gateway response, if it uses json tags for marshaling.
useJSONNamesForFields bool

// useFQNForSwaggerName if true swagger names will use the full qualified name (FQN) from proto definition,
// and generate a dot-separated swagger name concatenating all elements from the proto FQN.
// If false, the default behavior is to concat the last 2 elements of the FQN if they are unique, otherwise concat
// all the elements of the FQN without any separator
useFQNForSwaggerName bool
}

type repeatedFieldSeparator struct {
Expand Down Expand Up @@ -411,6 +417,16 @@ func (r *Registry) GetUseJSONNamesForFields() bool {
return r.useJSONNamesForFields
}

// SetUseFQNForSwaggerName sets useFQNForSwaggerName
func (r *Registry) SetUseFQNForSwaggerName(use bool) {
r.useFQNForSwaggerName = use
}

// GetUseFQNForSwaggerName returns useFQNForSwaggerName
func (r *Registry) GetUseFQNForSwaggerName() bool {
return r.useFQNForSwaggerName
}

// GetMergeFileName return the target merge swagger file name
func (r *Registry) GetMergeFileName() string {
return r.mergeFileName
Expand Down
25 changes: 15 additions & 10 deletions protoc-gen-swagger/genswagger/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ func fullyQualifiedNameToSwaggerName(fqn string, reg *descriptor.Registry) strin
if mapping, present := registriesSeen[reg]; present {
return mapping[fqn]
}
mapping := resolveFullyQualifiedNameToSwaggerNames(append(reg.GetAllFQMNs(), reg.GetAllFQENs()...))
mapping := resolveFullyQualifiedNameToSwaggerNames(append(reg.GetAllFQMNs(), reg.GetAllFQENs()...), reg.GetUseFQNForSwaggerName())
registriesSeen[reg] = mapping
return mapping[fqn]
}
Expand All @@ -544,7 +544,7 @@ var registriesSeenMutex sync.Mutex
// This likely could be made better. This will always generate the same names
// but may not always produce optimal names. This is a reasonably close
// approximation of what they should look like in most cases.
func resolveFullyQualifiedNameToSwaggerNames(messages []string) map[string]string {
func resolveFullyQualifiedNameToSwaggerNames(messages []string, useFQNForSwaggerName bool) map[string]string {
packagesByDepth := make(map[int][][]string)
uniqueNames := make(map[string]string)

Expand Down Expand Up @@ -573,14 +573,19 @@ func resolveFullyQualifiedNameToSwaggerNames(messages []string) map[string]strin
}

for _, p := range messages {
h := hierarchy(p)
for depth := 0; depth < len(h); depth++ {
if count(packagesByDepth[depth], h[len(h)-depth:]) == 1 {
uniqueNames[p] = strings.Join(h[len(h)-depth-1:], "")
break
}
if depth == len(h)-1 {
uniqueNames[p] = strings.Join(h, "")
if useFQNForSwaggerName {
// strip leading dot from proto fqn
uniqueNames[p] = p[1:]
} else {
h := hierarchy(p)
for depth := 0; depth < len(h); depth++ {
if count(packagesByDepth[depth], h[len(h)-depth:]) == 1 {
uniqueNames[p] = strings.Join(h[len(h)-depth-1:], "")
break
}
if depth == len(h)-1 {
uniqueNames[p] = strings.Join(h, "")
}
}
}
}
Expand Down
22 changes: 18 additions & 4 deletions protoc-gen-swagger/genswagger/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -853,16 +853,18 @@ func TestTemplateToSwaggerPath(t *testing.T) {

func TestResolveFullyQualifiedNameToSwaggerName(t *testing.T) {
var tests = []struct {
input string
output string
listOfFQMNs []string
input string
output string
listOfFQMNs []string
useFQNForSwaggerName bool
}{
{
".a.b.C",
"C",
[]string{
".a.b.C",
},
false,
},
{
".a.b.C",
Expand All @@ -871,6 +873,7 @@ func TestResolveFullyQualifiedNameToSwaggerName(t *testing.T) {
".a.C",
".a.b.C",
},
false,
},
{
".a.b.C",
Expand All @@ -880,11 +883,22 @@ func TestResolveFullyQualifiedNameToSwaggerName(t *testing.T) {
".a.C",
".a.b.C",
},
false,
},
{
".a.b.C",
"a.b.C",
[]string{
".C",
".a.C",
".a.b.C",
},
true,
},
}

for _, data := range tests {
names := resolveFullyQualifiedNameToSwaggerNames(data.listOfFQMNs)
names := resolveFullyQualifiedNameToSwaggerNames(data.listOfFQMNs, data.useFQNForSwaggerName)
output := names[data.input]
if output != data.output {
t.Errorf("Expected fullyQualifiedNameToSwaggerName(%v) to be %s but got %s",
Expand Down
2 changes: 2 additions & 0 deletions protoc-gen-swagger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var (
versionFlag = flag.Bool("version", false, "print the current verison")
allowRepeatedFieldsInBody = flag.Bool("allow_repeated_fields_in_body", false, "allows to use repeated field in `body` and `response_body` field of `google.api.http` annotation option")
includePackageInTags = flag.Bool("include_package_in_tags", false, "if unset, the gRPC service name is added to the `Tags` field of each operation. if set and the `package` directive is shown in the proto file, the package name will be prepended to the service name")
useFQNForSwaggerName = flag.Bool("fqn_for_swagger_name", false, "if set, the object's swagger names will use the fully qualify name from the proto definition (ie my.package.MyMessage.MyInnerMessage")
)

// Variables set by goreleaser at build time
Expand Down Expand Up @@ -76,6 +77,7 @@ func main() {
reg.SetUseJSONNamesForFields(*useJSONNamesForFields)
reg.SetAllowRepeatedFieldsInBody(*allowRepeatedFieldsInBody)
reg.SetIncludePackageInTags(*includePackageInTags)
reg.SetUseFQNForSwaggerName(*useFQNForSwaggerName)
if err := reg.SetRepeatedPathParamSeparator(*repeatedPathParamSeparator); err != nil {
emitError(err)
return
Expand Down
25 changes: 22 additions & 3 deletions protoc-gen-swagger/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func TestParseReqParam(t *testing.T) {
fileV string
importPathV string
mergeFileNameV string
useFQNForSwaggerNameV bool
}{
{
// this one must be first - with no leading clearFlags call it
Expand Down Expand Up @@ -99,6 +100,21 @@ func TestParseReqParam(t *testing.T) {
allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false,
fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
},
{
name: "Test 10",
expected: map[string]string{},
request: "fqn_for_swagger_name=3",
expectedError: errors.New(`Cannot set flag fqn_for_swagger_name=3: strconv.ParseBool: parsing "3": invalid syntax`),
allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false, useFQNForSwaggerNameV: false,
fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
},
{
name: "Test 11",
expected: map[string]string{},
request: "fqn_for_swagger_name=true",
allowDeleteBodyV: false, allowMergeV: false, allowRepeatedFieldsInBodyV: false, includePackageInTagsV: false, useFQNForSwaggerNameV: true,
fileV: "stdin", importPathV: "", mergeFileNameV: "apidocs",
},
}

for i, tc := range testcases {
Expand All @@ -124,15 +140,15 @@ func TestParseReqParam(t *testing.T) {
tt.Errorf("expected error malformed, expected %q, got %q", tc.expectedError.Error(), err.Error())
}
}
checkFlags(tc.allowDeleteBodyV, tc.allowMergeV, tc.allowRepeatedFieldsInBodyV, tc.includePackageInTagsV, tc.fileV, tc.importPathV, tc.mergeFileNameV, tt, i)
checkFlags(tc.allowDeleteBodyV, tc.allowMergeV, tc.allowRepeatedFieldsInBodyV, tc.includePackageInTagsV, tc.useFQNForSwaggerNameV, tc.fileV, tc.importPathV, tc.mergeFileNameV, tt, i)

clearFlags()
})
}

}

func checkFlags(allowDeleteV, allowMergeV, allowRepeatedFieldsInBodyV, includePackageInTagsV bool, fileV, importPathV, mergeFileNameV string, t *testing.T, tid int) {
func checkFlags(allowDeleteV, allowMergeV, allowRepeatedFieldsInBodyV, includePackageInTagsV bool, useFQNForSwaggerNameV bool, fileV, importPathV, mergeFileNameV string, t *testing.T, tid int) {
if *importPrefix != importPathV {
t.Errorf("Test %v: import_prefix misparsed, expected '%v', got '%v'", tid, importPathV, *importPrefix)
}
Expand All @@ -152,7 +168,10 @@ func checkFlags(allowDeleteV, allowMergeV, allowRepeatedFieldsInBodyV, includePa
t.Errorf("Test %v: allow_repeated_fields_in_body misparsed, expected '%v', got '%v'", tid, allowRepeatedFieldsInBodyV, *allowRepeatedFieldsInBody)
}
if *includePackageInTags != includePackageInTagsV {
t.Errorf("Test %v: allow_repeated_fields_in_body misparsed, expected '%v', got '%v'", tid, includePackageInTagsV, *includePackageInTags)
t.Errorf("Test %v: include_package_in_tags misparsed, expected '%v', got '%v'", tid, includePackageInTagsV, *includePackageInTags)
}
if *useFQNForSwaggerName != useFQNForSwaggerNameV {
t.Errorf("Test %v: fqn_for_swagger_name misparsed, expected '%v', got '%v'", tid, useFQNForSwaggerNameV, *useFQNForSwaggerName)
}
}

Expand Down

0 comments on commit 0c2b3b1

Please sign in to comment.