From 0c2b3b165f486a2c49b627e71880172d27607a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20JACQUES?= Date: Fri, 1 Mar 2019 10:19:03 +0100 Subject: [PATCH] protoc-gen-swagger: add fqn_for_swagger_name option Adds a new option fqn_for_swagger_name. This uses the protobuf FQN in the generated swagger specification. --- .../descriptor/registry.go | 16 ++++++++++++ protoc-gen-swagger/genswagger/template.go | 25 +++++++++++-------- .../genswagger/template_test.go | 22 +++++++++++++--- protoc-gen-swagger/main.go | 2 ++ protoc-gen-swagger/main_test.go | 25 ++++++++++++++++--- 5 files changed, 73 insertions(+), 17 deletions(-) diff --git a/protoc-gen-grpc-gateway/descriptor/registry.go b/protoc-gen-grpc-gateway/descriptor/registry.go index 510ab253830..1131ca453f9 100644 --- a/protoc-gen-grpc-gateway/descriptor/registry.go +++ b/protoc-gen-grpc-gateway/descriptor/registry.go @@ -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 { @@ -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 diff --git a/protoc-gen-swagger/genswagger/template.go b/protoc-gen-swagger/genswagger/template.go index 2b74d5f164f..74f53394cae 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -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] } @@ -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) @@ -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, "") + } } } } diff --git a/protoc-gen-swagger/genswagger/template_test.go b/protoc-gen-swagger/genswagger/template_test.go index 76b1e830e0e..f11246bf1a0 100644 --- a/protoc-gen-swagger/genswagger/template_test.go +++ b/protoc-gen-swagger/genswagger/template_test.go @@ -853,9 +853,10 @@ 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", @@ -863,6 +864,7 @@ func TestResolveFullyQualifiedNameToSwaggerName(t *testing.T) { []string{ ".a.b.C", }, + false, }, { ".a.b.C", @@ -871,6 +873,7 @@ func TestResolveFullyQualifiedNameToSwaggerName(t *testing.T) { ".a.C", ".a.b.C", }, + false, }, { ".a.b.C", @@ -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", diff --git a/protoc-gen-swagger/main.go b/protoc-gen-swagger/main.go index 16ce528b709..237e4604983 100644 --- a/protoc-gen-swagger/main.go +++ b/protoc-gen-swagger/main.go @@ -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 @@ -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 diff --git a/protoc-gen-swagger/main_test.go b/protoc-gen-swagger/main_test.go index b5ebda041a2..2c6eec129d4 100644 --- a/protoc-gen-swagger/main_test.go +++ b/protoc-gen-swagger/main_test.go @@ -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 @@ -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 { @@ -124,7 +140,7 @@ 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() }) @@ -132,7 +148,7 @@ func TestParseReqParam(t *testing.T) { } -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) } @@ -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) } }