diff --git a/protoc-gen-swagger/genswagger/template.go b/protoc-gen-swagger/genswagger/template.go index 114252d0865..a5548f3e46e 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/url" "os" "reflect" "regexp" @@ -17,9 +18,9 @@ import ( "github.com/golang/glog" "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" - structpb "github.com/golang/protobuf/ptypes/struct" pbdescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" gogen "github.com/golang/protobuf/protoc-gen-go/generator" + structpb "github.com/golang/protobuf/ptypes/struct" "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor" swagger_options "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options" ) @@ -678,13 +679,18 @@ func templateToSwaggerPath(path string, reg *descriptor.Registry) string { // Parts is now an array of segments of the path. Interestingly, since the // syntax for this subsection CAN be handled by a regexp since it has no // memory. + keyre := regexp.MustCompile("{(.*)}") for index, part := range parts { // If part is a resource name such as "parent", "name", "user.name", the format info must be retained. prefix := canRegexp.ReplaceAllString(part, "$1") if isResourceName(prefix) { - continue + sm := keyre.FindStringSubmatch(part) + key := sm[1] + esckey := url.PathEscape(key) + parts[index] = keyre.ReplaceAllString(part, fmt.Sprintf("{%s}", esckey)) + } else { + parts[index] = canRegexp.ReplaceAllString(part, "{$1}") } - parts[index] = canRegexp.ReplaceAllString(part, "{$1}") } return strings.Join(parts, "/") @@ -699,11 +705,31 @@ func isResourceName(prefix string) bool { return field == "parent" || field == "name" } +func extractResourceName(path string) map[string]string { + m := map[string]string{} + keyre := regexp.MustCompile("{(.*)}") + sm := keyre.FindStringSubmatch(path) + count := len(sm) + for i := 0; i < count; i++ { + key := sm[1] + parts := strings.Split(key, "=") + label := parts[0] + parts = strings.Split(label, ".") + l := len(parts) + field := parts[l-1] + if field == "parent" || field == "name" { + m[label] = key + } + } + return m +} + func renderServices(services []*descriptor.Service, paths swaggerPathsObject, reg *descriptor.Registry, requestResponseRefs, customRefs refMap) error { // Correctness of svcIdx and methIdx depends on 'services' containing the services in the same order as the 'file.Service' array. for svcIdx, svc := range services { for methIdx, meth := range svc.Methods { for bIdx, b := range meth.Bindings { + pathParamMap := extractResourceName(templateToSwaggerPath(b.PathTmpl.Template, reg)) // Iterate over all the swagger parameters parameters := swaggerParametersObject{} for _, parameter := range b.PathParams { @@ -771,6 +797,9 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re if reg.GetUseJSONNamesForFields() { parameterString = lowerCamelCase(parameterString) } + if esckey, ok := pathParamMap[parameterString]; ok { + parameterString = esckey + } parameters = append(parameters, swaggerParameterObject{ Name: parameterString, Description: desc, diff --git a/protoc-gen-swagger/genswagger/template_test.go b/protoc-gen-swagger/genswagger/template_test.go index 27031742f87..8768830a8d0 100644 --- a/protoc-gen-swagger/genswagger/template_test.go +++ b/protoc-gen-swagger/genswagger/template_test.go @@ -1050,16 +1050,17 @@ func TestTemplateToSwaggerPath(t *testing.T) { {"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"}, {"/{test1}/{test2}", "/{test1}/{test2}"}, {"/{test1}/{test2}/", "/{test1}/{test2}/"}, - {"/{name=prefix/*}", "/{name=prefix/*}"}, - {"/{name=prefix1/*/prefix2/*}", "/{name=prefix1/*/prefix2/*}"}, - {"/{user.name=prefix/*}", "/{user.name=prefix/*}"}, - {"/{user.name=prefix1/*/prefix2/*}", "/{user.name=prefix1/*/prefix2/*}"}, - {"/{parent=prefix/*}/children", "/{parent=prefix/*}/children"}, - {"/{name=prefix/*}:customMethod", "/{name=prefix/*}:customMethod"}, - {"/{name=prefix1/*/prefix2/*}:customMethod", "/{name=prefix1/*/prefix2/*}:customMethod"}, - {"/{user.name=prefix/*}:customMethod", "/{user.name=prefix/*}:customMethod"}, - {"/{user.name=prefix1/*/prefix2/*}:customMethod", "/{user.name=prefix1/*/prefix2/*}:customMethod"}, - {"/{parent=prefix/*}/children:customMethod", "/{parent=prefix/*}/children:customMethod"}, + {"/{name=prefix/*}", "/{name=prefix%2F%2A}"}, + {"/{name=prefix1/*/prefix2/*}", "/{name=prefix1%2F%2A%2Fprefix2%2F%2A}"}, + {"/{parent=prefix1/*}/{name=prefix2/*}", "/{parent=prefix1%2F%2A}/{name=prefix2%2F%2A}"}, + {"/{user.name=prefix/*}", "/{user.name=prefix%2F%2A}"}, + {"/{user.name=prefix1/*/prefix2/*}", "/{user.name=prefix1%2F%2A%2Fprefix2%2F%2A}"}, + {"/{parent=prefix/*}/children", "/{parent=prefix%2F%2A}/children"}, + {"/{name=prefix/*}:customMethod", "/{name=prefix%2F%2A}:customMethod"}, + {"/{name=prefix1/*/prefix2/*}:customMethod", "/{name=prefix1%2F%2A%2Fprefix2%2F%2A}:customMethod"}, + {"/{user.name=prefix/*}:customMethod", "/{user.name=prefix%2F%2A}:customMethod"}, + {"/{user.name=prefix1/*/prefix2/*}:customMethod", "/{user.name=prefix1%2F%2A%2Fprefix2%2F%2A}:customMethod"}, + {"/{parent=prefix/*}/children:customMethod", "/{parent=prefix%2F%2A}/children:customMethod"}, } reg := descriptor.NewRegistry() reg.SetUseJSONNamesForFields(false)