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

Use json camel case when GetUseJSONNamesForFields is enabled #985

Merged
merged 10 commits into from
Aug 9, 2019
1 change: 1 addition & 0 deletions protoc-gen-swagger/genswagger/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
34 changes: 29 additions & 5 deletions protoc-gen-swagger/genswagger/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -631,6 +643,7 @@ func templateToSwaggerPath(path string) string {
buffer += string(char)
default:
buffer += string(char)
jsonBuffer += string(char)
break
}
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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{}
}
Expand Down Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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()
}
61 changes: 57 additions & 4 deletions protoc-gen-swagger/genswagger/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down