diff --git a/protoc-gen-swagger/genswagger/BUILD.bazel b/protoc-gen-swagger/genswagger/BUILD.bazel index 18f72b9e37c..7798e9b99c5 100644 --- a/protoc-gen-swagger/genswagger/BUILD.bazel +++ b/protoc-gen-swagger/genswagger/BUILD.bazel @@ -19,6 +19,7 @@ go_library( "@com_github_golang_glog//:go_default_library", "@com_github_golang_protobuf//descriptor:go_default_library_gen", "@com_github_golang_protobuf//proto:go_default_library", + "@com_github_golang_protobuf//protoc-gen-go/generator:go_default_library_gen", "@io_bazel_rules_go//proto/wkt:any_go_proto", "@io_bazel_rules_go//proto/wkt:compiler_plugin_go_proto", "@io_bazel_rules_go//proto/wkt:descriptor_go_proto", diff --git a/protoc-gen-swagger/genswagger/template.go b/protoc-gen-swagger/genswagger/template.go index b02ed6aa2c7..3b1e8348c20 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -13,6 +13,7 @@ import ( "github.com/golang/glog" "github.com/golang/protobuf/proto" pbdescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" + gogen "github.com/golang/protobuf/protoc-gen-go/generator" "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor" swagger_options "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options" ) @@ -597,7 +598,7 @@ func resolveFullyQualifiedNameToSwaggerNames(messages []string, useFQNForSwagger } // Swagger expects paths of the form /path/{string_value} but grpc-gateway paths are expected to be of the form /path/{string_value=strprefix/*}. This should reformat it correctly. -func templateToSwaggerPath(path string) string { +func templateToSwaggerPath(path string, reg *descriptor.Registry) string { // It seems like the right thing to do here is to just use // strings.Split(path, "/") but that breaks badly when you hit a url like // /{my_field=prefix/*}/ and end up with 2 sections representing my_field. @@ -606,12 +607,15 @@ func templateToSwaggerPath(path string) string { var parts []string depth := 0 buffer := "" + jsonBuffer := "" for _, char := range path { switch char { case '{': // Push on the stack depth++ buffer += string(char) + jsonBuffer = "" + jsonBuffer += string(char) break case '}': if depth == 0 { @@ -620,6 +624,14 @@ func templateToSwaggerPath(path string) string { // Pop from the stack depth-- buffer += string(char) + if reg.GetUseJSONNamesForFields() && + len(jsonBuffer) > 1 { + jsonSnakeCaseName := string(jsonBuffer[1 : len(buffer)-1]) + jsonCamelCaseName := string(lowerCamelCase(jsonSnakeCaseName)) + prev := string(buffer[:len(buffer)-len(jsonSnakeCaseName)-2]) + buffer = strings.Join([]string{prev, "{", jsonCamelCaseName, "}"}, "") + jsonBuffer = "" + } case '/': if depth == 0 { parts = append(parts, buffer) @@ -631,6 +643,7 @@ func templateToSwaggerPath(path string) string { buffer += string(char) default: buffer += string(char) + jsonBuffer += string(char) break } } @@ -731,9 +744,12 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re if desc == "" { desc = fieldProtoComments(reg, parameter.Target.Message, parameter.Target) } - + parameterString := parameter.String() + if reg.GetUseJSONNamesForFields() { + parameterString = lowerCamelCase(parameterString) + } parameters = append(parameters, swaggerParameterObject{ - Name: parameter.String(), + Name: parameterString, Description: desc, In: "path", Required: true, @@ -797,7 +813,7 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re parameters = append(parameters, queryParams...) } - pathItemObject, ok := paths[templateToSwaggerPath(b.PathTmpl.Template)] + pathItemObject, ok := paths[templateToSwaggerPath(b.PathTmpl.Template, reg)] if !ok { pathItemObject = swaggerPathItemObject{} } @@ -947,7 +963,7 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re pathItemObject.Patch = operationObject break } - paths[templateToSwaggerPath(b.PathTmpl.Template)] = pathItemObject + paths[templateToSwaggerPath(b.PathTmpl.Template, reg)] = pathItemObject } } } @@ -1661,3 +1677,11 @@ func addCustomRefs(d swaggerDefinitionsObject, reg *descriptor.Registry, refs re // Run again in case any new refs were added addCustomRefs(d, reg, refs) } + +func lowerCamelCase(parameter string) string { + parameterString := gogen.CamelCase(parameter) + builder := &strings.Builder{} + builder.WriteString(strings.ToLower(string(parameterString[0]))) + builder.WriteString(parameterString[1:]) + return builder.String() +} diff --git a/protoc-gen-swagger/genswagger/template_test.go b/protoc-gen-swagger/genswagger/template_test.go index 4853a4370d3..200f563d544 100644 --- a/protoc-gen-swagger/genswagger/template_test.go +++ b/protoc-gen-swagger/genswagger/template_test.go @@ -838,6 +838,59 @@ func TestApplyTemplateRequestWithUnusedReferences(t *testing.T) { } } +func TestTemplateWithJsonCamelCase(t *testing.T) { + var tests = []struct { + input string + expected string + }{ + {"/test/{test_id}", "/test/{testId}"}, + {"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1Id}/test2/{test2Id}"}, + {"/test1/{test1_id}/{test2_id}", "/test1/{test1Id}/{test2Id}"}, + {"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1Id}/{test2Id}"}, + {"/test1/{test1_id1_id2}", "/test1/{test1Id1Id2}"}, + {"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1Id1Id2}/test2/{test2Id3Id4}"}, + {"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1Id1Id2}/{test2Id3Id4}"}, + {"test/{a}", "test/{a}"}, + {"test/{ab}", "test/{ab}"}, + {"test/{a_a}", "test/{aA}"}, + {"test/{ab_c}", "test/{abC}"}, + } + reg := descriptor.NewRegistry() + reg.SetUseJSONNamesForFields(true) + for _, data := range tests { + actual := templateToSwaggerPath(data.input, reg) + if data.expected != actual { + t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual) + } + } +} + +func TestTemplateWithoutJsonCamelCase(t *testing.T) { + var tests = []struct { + input string + expected string + }{ + {"/test/{test_id}", "/test/{test_id}"}, + {"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1_id}/test2/{test2_id}"}, + {"/test1/{test1_id}/{test2_id}", "/test1/{test1_id}/{test2_id}"}, + {"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1_id}/{test2_id}"}, + {"/test1/{test1_id1_id2}", "/test1/{test1_id1_id2}"}, + {"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1_id1_id2}/test2/{test2_id3_id4}"}, + {"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1_id1_id2}/{test2_id3_id4}"}, + {"test/{a}", "test/{a}"}, + {"test/{ab}", "test/{ab}"}, + {"test/{a_a}", "test/{a_a}"}, + } + reg := descriptor.NewRegistry() + reg.SetUseJSONNamesForFields(false) + for _, data := range tests { + actual := templateToSwaggerPath(data.input, reg) + if data.expected != actual { + t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual) + } + } +} + func TestTemplateToSwaggerPath(t *testing.T) { var tests = []struct { input string @@ -860,9 +913,9 @@ func TestTemplateToSwaggerPath(t *testing.T) { {"/{user.name=prefix1/*/prefix2/*}:customMethod", "/{user.name=prefix1/*/prefix2/*}:customMethod"}, {"/{parent=prefix/*}/children:customMethod", "/{parent=prefix/*}/children:customMethod"}, } - + reg := descriptor.NewRegistry() for _, data := range tests { - actual := templateToSwaggerPath(data.input) + actual := templateToSwaggerPath(data.input, reg) if data.expected != actual { t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual) } @@ -937,9 +990,9 @@ func TestFQMNtoSwaggerName(t *testing.T) { {"/{test1}/{test2}", "/{test1}/{test2}"}, {"/{test1}/{test2}/", "/{test1}/{test2}/"}, } - + reg := descriptor.NewRegistry() for _, data := range tests { - actual := templateToSwaggerPath(data.input) + actual := templateToSwaggerPath(data.input, reg) if data.expected != actual { t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual) }