grpc-gateway icon indicating copy to clipboard operation
grpc-gateway copied to clipboard

Support nested messages in GET request queries

Open DmitriyMV opened this issue 7 years ago • 12 comments

Given the

service MyService {
    rpc myMethod (myMethodRequest) returns (myMethodResponse) {
        option (google.api.http) = {
            get: "/method/all"
        };
    }
}

message myMethodRequest {
    repeated Scoped scope = 1;

    message Scoped {
        oneof condition {
            ById by_id = 1;
        }
        message ById {
            uint32 id = 1;
        }
    }
}

message myMethodResponse {
    repeated uint32 ids = 1;
}

with GET request

localhost:8080/method/all?scope.by_id.id=3

returns an error

unexpected repeated field in scope.by_id.id

DmitriyMV avatar Dec 01 '17 18:12 DmitriyMV

As far as I can tell, there isn't a way to specify a repeated message field using GET query parameters alone. Only repeated primitive fields (strings, ints, bools, floats, etc) are supported in query parameters. See runtime/query.go.

Can you use POST instead?

rogerhub avatar Dec 02 '17 07:12 rogerhub

I think @rogerhub's answer is probably the best way to go here. I'm not sure what the standard syntax for repeated fields in a query string would be. I guess the PHP format? I'm not sure though and think you would probably be happier with this as a different verb.

achew22 avatar Dec 14 '17 07:12 achew22

I think there's a valid use case for this. In my case, I have a ListResource method as a GET request. This method takes a parameter called filter, which is a structured message that defines the various fields that can be filtered by.

A simplified example:

service ResourceService {
    rpc ListResource (ListResourceRequest) returns (ListResourceResponse) {
        option (google.api.http) = {
            get: "/api/resource"
        };
    }
}

message ListResourceRequest {
    ResourceFilter filter = 1;
}

message ResourceFilter {
    repeated string ID = 1;
    repeated RangeFilter range = 2;
}

message RangeFilter {
    int64 minimum = 1;
    int64 maximum = 2;
}

message myMethodResponse {
    repeated Resource resources = 1;
}

Trying a request:

/api/resource?filter.resources.ranges.minimum=4
{"error":"unexpected repeated field in filter.resources.ranges.minimum","message":"unexpected repeated field in filter.resources.ranges.minimum","code":3,"details":[]}

In this case, the method really shouldn't be POST. It's ok as a temporary workaround, but I wouldn't consider this issue fixed just because I can use POST instead. Doing so makes the API less consistent and weakens the meaning of the request verbs.

I also understand the syntax isn't obvious, but there are a few alternatives that could work, and the benefit of supporting a use case like the one above is quite significant.

For me, being able to specify the structure of the filters in the protos format helps make sense of an otherwise complex part of the API. The commonly seen alternative of using a string field as generic filter (e.g. /api/resource?filter=blahblahblah...) ignores that complexity, and leads to a fragile, implicit contract between the caller and the server, throwing away some of the benefits that drove me to choose gRPC in the first place.

So, for the use case I've laid out above (and I could conceive of others), I'd be grateful if you could reconsider supporting repeated message fields in query strings.

kwoodhouse93 avatar Aug 21 '18 11:08 kwoodhouse93

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Sep 09 '19 17:09 stale[bot]

*Bump

I agree with @kwoodhouse93 there is valid use case for this.

craigberry1 avatar Sep 10 '19 15:09 craigberry1

I agree with the proposal and willing to solve this, I am new to this repo so what will be a good starting point to solve this?

sum2000 avatar Sep 10 '19 16:09 sum2000

Hi @sum2000, thanks for showing an interest in contributing! I think all the query parameter parsing happens in https://github.com/grpc-ecosystem/grpc-gateway/blob/5ebe3a8e01b56f67d5f0c6da3a274639e5e1a376/runtime/query.go#L47, so that would be the place to start looking. The first thing to do would be to add a new test case to https://github.com/grpc-ecosystem/grpc-gateway/blob/master/runtime/query_test.go#L85 and see if you can make it pass. Let me know if you need anymore pointers!

johanbrandhorst avatar Sep 10 '19 16:09 johanbrandhorst

Hi @sum2000, thanks for showing an interest in contributing! I think all the query parameter parsing happens in

https://github.com/grpc-ecosystem/grpc-gateway/blob/5ebe3a8e01b56f67d5f0c6da3a274639e5e1a376/runtime/query.go#L47 , so that would be the place to start looking. The first thing to do would be to add a new test case to /runtime/query_test.go@master#L85 and see if you can make it pass. Let me know if you need anymore pointers!

Thanks, will do! Is there a discord or chat for grpc-gateway ?

sum2000 avatar Sep 10 '19 19:09 sum2000

We have a channel on the gophers slack: #grpc-gateway after joining https://invite.slack.golangbridge.org/.

johanbrandhorst avatar Sep 10 '19 19:09 johanbrandhorst

any updates? quite a good feature to support

npuichigo avatar Jan 14 '20 07:01 npuichigo

I don't think there's been any work on this recently unfortunately, but if you'd like to see this feature in the gateway, please feel free to have a go at implementing it. My advice above is still relevant.

johanbrandhorst avatar Jan 14 '20 11:01 johanbrandhorst