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

write query parameters to swagger definition #199

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions examples/clients/abe/ABitOfEverythingServiceApi.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,11 @@ func (a ABitOfEverythingServiceApi) Echo (value string) (SubStringMessage, error
/**
*
*
* @param value
* @return SubStringMessage
*/
//func (a ABitOfEverythingServiceApi) Echo_1 () (SubStringMessage, error) {
func (a ABitOfEverythingServiceApi) Echo_1 () (SubStringMessage, error) {
//func (a ABitOfEverythingServiceApi) Echo_1 (value string) (SubStringMessage, error) {
func (a ABitOfEverythingServiceApi) Echo_1 (value string) (SubStringMessage, error) {

_sling := sling.New().Get(a.basePath)

Expand All @@ -366,6 +367,11 @@ func (a ABitOfEverythingServiceApi) Echo_1 () (SubStringMessage, error) {

_sling = _sling.Path(path)

type QueryParams struct {
value string `url:"value,omitempty"`

}
_sling = _sling.QueryStruct(&QueryParams{ value: value })
// accept header
accepts := []string { "application/json" }
for key := range accepts {
Expand Down
8 changes: 8 additions & 0 deletions examples/examplepb/a_bit_of_everything.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,14 @@
}
}
},
"parameters": [
{
"name": "value",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"ABitOfEverythingService"
]
Expand Down
50 changes: 50 additions & 0 deletions protoc-gen-swagger/genswagger/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,49 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
)

// messageToQueryParameters converts a message to a list of swagger query parameters.
func messageToQueryParameters(message *descriptor.Message, reg *descriptor.Registry, pathParams []descriptor.Parameter) ([]swaggerParameterObject, error) {
var parameters []swaggerParameterObject
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was even better if you make variable names a bit shorter.
https://github.com/golang/go/wiki/CodeReviewComments#variable-names

var addParameterWithPrefix func(string, *descriptor.Field) error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addParameterWithPrefix := func(prefix string, field *descriptor.Field) error {

BTW, why do you need to keep this function nested?
IIUC, you could simply define a function like

func queryParams(prefix string, field *descriptor.Field) ([]swaggerParameterObject, error)

addParameterWithPrefix = func(prefix string, field *descriptor.Field) error {
// make sure the parameter is not already listed as a path parameter
for _, pathParam := range pathParams {
if pathParam.Target == field {
return nil
}
}
schema := schemaOfField(field, reg)
if schema.Type != "" {
// basic type, add a basic query parameter
parameters = append(parameters, swaggerParameterObject{
Name: prefix + field.GetName(),
Description: schema.Description,
In: "query",
Type: schema.Type,
})
return nil
}

// nested type, recurse
msg, err := reg.LookupMsg("", field.GetTypeName())
if err != nil {
return fmt.Errorf("unknown message type %s", field.GetTypeName())
}
for _, nestedField := range msg.Fields {
if err := addParameterWithPrefix(field.GetName()+".", nestedField); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work fine with deeply nested fields?

Also, it is fine if it is out of scope for now. But what do you think about that a protobuf message has the same message type as a field?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also interested in the answer to this. What happens for deeply nested fields?

return err
}
}
return nil
}
for _, field := range message.Fields {
if err := addParameterWithPrefix("", field); err != nil {
return parameters, err
}
}
return parameters, nil
}

// findServicesMessagesAndEnumerations discovers all messages and enums defined in the RPC methods of the service.
func findServicesMessagesAndEnumerations(s []*descriptor.Service, reg *descriptor.Registry, m messageMap, e enumMap) {
for _, svc := range s {
Expand Down Expand Up @@ -381,6 +424,13 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re
},
},
})
} else if b.HTTPMethod == "GET" {
// add the parameters to the query string
queryParams, err := messageToQueryParameters(meth.RequestType, reg, b.PathParams)
if err != nil {
return err
}
parameters = append(parameters, queryParams...)
}

pathItemObject, ok := paths[templateToSwaggerPath(b.PathTmpl.Template)]
Expand Down
118 changes: 118 additions & 0 deletions protoc-gen-swagger/genswagger/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/golang/protobuf/proto"
protodescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/httprule"
)
Expand All @@ -30,6 +31,123 @@ func crossLinkFixture(f *descriptor.File) *descriptor.File {
return f
}

func TestMessageToQueryParameters(t *testing.T) {
type test struct {
MsgDescs []*protodescriptor.DescriptorProto
Message string
Params []swaggerParameterObject
}

tests := []test{
{
MsgDescs: []*protodescriptor.DescriptorProto{
&protodescriptor.DescriptorProto{
Name: proto.String("ExampleMessage"),
Field: []*protodescriptor.FieldDescriptorProto{
{
Name: proto.String("a"),
Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
Number: proto.Int32(1),
},
{
Name: proto.String("b"),
Type: protodescriptor.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
Number: proto.Int32(2),
},
},
},
},
Message: "ExampleMessage",
Params: []swaggerParameterObject{
swaggerParameterObject{
Name: "a",
In: "query",
Required: false,
Type: "string",
},
swaggerParameterObject{
Name: "b",
In: "query",
Required: false,
Type: "number",
},
},
},
{
MsgDescs: []*protodescriptor.DescriptorProto{
&protodescriptor.DescriptorProto{
Name: proto.String("ExampleMessage"),
Field: []*protodescriptor.FieldDescriptorProto{
{
Name: proto.String("nested"),
Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
TypeName: proto.String(".example.Nested"),
Number: proto.Int32(1),
},
},
},
&protodescriptor.DescriptorProto{
Name: proto.String("Nested"),
Field: []*protodescriptor.FieldDescriptorProto{
{
Name: proto.String("a"),
Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
Number: proto.Int32(1),
},
},
},
},
Message: "ExampleMessage",
Params: []swaggerParameterObject{
swaggerParameterObject{
Name: "nested.a",
In: "query",
Required: false,
Type: "string",
},
},
},
}

for _, test := range tests {
reg := descriptor.NewRegistry()
msgs := []*descriptor.Message{}
for _, msgdesc := range test.MsgDescs {
msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
}
file := descriptor.File{
FileDescriptorProto: &protodescriptor.FileDescriptorProto{
SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
Name: proto.String("example.proto"),
Package: proto.String("example"),
Dependency: []string{},
MessageType: test.MsgDescs,
Service: []*protodescriptor.ServiceDescriptorProto{},
},
GoPkg: descriptor.GoPackage{
Path: "example.com/path/to/example/example.pb",
Name: "example_pb",
},
Messages: msgs,
}
reg.Load(&plugin.CodeGeneratorRequest{
ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto},
})

message, err := reg.LookupMsg("", ".example."+test.Message)
if err != nil {
t.Fatalf("failed to lookup message: %s", err)
}
params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{})
if err != nil {
t.Fatalf("failed to convert message to query parameters: %s", err)
}
if !reflect.DeepEqual(params, test.Params) {
t.Errorf("expected %v, got %v", test.Params, params)
}
}
}

func TestApplyTemplateSimple(t *testing.T) {
msgdesc := &protodescriptor.DescriptorProto{
Name: proto.String("ExampleMessage"),
Expand Down