Passing comma separated lists of strings as query params results in a list of length one (with the commas in the string) when binding and the high-level HttpClient seems to do this by default with List<String> parameters.
Make a controller that should echo a list of strings back:
@CompileStatic
@Controller("/api")
class EchoController {
@Get("/echo-optional{?list}")
HttpResponse echo(Optional<List<String>> list) {
return HttpResponse.ok(list.get())
}
@Get("/echo-nullable{?list}")
HttpResponse echo(@Nullable List<String> list) {
return HttpResponse.ok(list)
}
}
Make a client for testing that controller:
@CompileStatic
@Client("/api")
interface EchoClient {
@Get("/echo-optional{?list}")
HttpResponse echo(Optional<List<String>> list)
@Get("/echo-nullable{?list}")
HttpResponse echo(@Nullable List<String> list)
}
Make tests that test the controllers using a low level client and high-level client with comma separated string list query params:
@MicronautTest
class EchoControllerSpec extends Specification {
@Inject
@Client('/')
RxHttpClient client
@Inject
EchoClient echoClient
void "should pass lists correctly comma separated but doesn't with optional"() {
given:
List result = client.toBlocking().retrieve(HttpRequest.GET('/api/echo-optional?list=string1,string2'), List)
expect:
result.size() == 2
}
void "should pass lists correctly comma separated but doesn't with nullable"() {
given:
List result = client.toBlocking().retrieve(HttpRequest.GET('/api/echo-nullable?list=string1,string2'), List)
expect:
result.size() == 2
}
void "should pass lists correctly comma separated but doesn't with client optional"() {
given:
List result = echoClient.echo(Optional.of(["string1","string2"])).body() as List
expect:
result.size() == 2
}
void "should pass lists correctly comma separated but doesn't with client nullable"() {
given:
List result = echoClient.echo(["string1","string2"]).body() as List
expect:
result.size() == 2
}
}
Lists passed as comma separated should parse into lists of length 2 and be echoed back that way (and show up in debug that way)
Lists are size 1 and contain the commas both in debug and on return from the response both for low-level and high-level http clients
BTW query param list handling DOES work for List<Long> which are also passed comma separated by the high-level client I believe
09:37:17.618 [nioEventLoopGroup-1-12] DEBUG i.m.http.client.DefaultHttpClient - Sending HTTP Request: GET /api/echo-optional?list=string1,string2
09:37:17.618 [nioEventLoopGroup-1-12] DEBUG i.m.http.client.DefaultHttpClient - Chosen Server: localhost(46774)
09:37:17.618 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - Accept: application/json
09:37:17.618 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - host: localhost:46774
09:37:17.618 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - connection: close
09:37:17.619 [nioEventLoopGroup-1-13] DEBUG i.m.h.server.netty.NettyHttpServer - Server localhost:46774 Received Request: GET /api/echo-optional?list=string1,string2
09:37:17.619 [nioEventLoopGroup-1-13] DEBUG i.m.h.s.netty.RoutingInBoundHandler - Matching route GET - /api/echo-optional
09:37:17.619 [nioEventLoopGroup-1-13] DEBUG i.m.h.s.netty.RoutingInBoundHandler - Matched route GET - /api/echo-optional to controller class com.ch.smartsearch.controllers.EchoController
09:37:17.620 [pool-1-thread-3] DEBUG i.m.h.s.netty.RoutingInBoundHandler - Encoding emitted response object [[string1,string2]] using codec: io.micronaut.jackson.codec.JsonMediaTypeCodec@3ac8cf9b
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - HTTP Client Response Received for Request: GET http://localhost:46774/api/echo-optional?list=string1,string2
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - Status Code: 200 OK
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - Date: Wed, 1 May 2019 13:37:17 GMT
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - content-type: application/json
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - content-length: 19
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - connection: close
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - Response Body
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - ----
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - ["string1,string2"]
09:37:17.622 [nioEventLoopGroup-1-12] TRACE i.m.http.client.DefaultHttpClient - ----
Condition not satisfied:
result.size() == 2
| | |
| 1 false
[string1,string2]
This is working as designed as far as I can tell.
Server side:
@Get("/echo-optional{?list}")
HttpResponse echo(Optional<List<String>> list) {
Should never bind to the list with more than 1 item if there is only a single query param. We can't assume comma separated means to split it. What if it was | separated, or tab separated?
Client side:
@Get("/echo-optional{?list}")
HttpResponse echo(Optional<List<String>> list)
You have informed micronaut here to create a single query parameter from your list, which by default results in a comma separated string for a list argument. If you want the server to read it as a list, you need to change the URI template to @Get("/echo-optional{?list*}"). The * means to explode the list into multiple parameters.
If you included a sample application I would have submitted a PR to fix it
Closing this for now. If it turns out I was mistaken and there is an issue here I will reopen.
@jameskleeh
There is still an issue present: even if * is added both on client and controller side, it won't work, since the client ignores exploded operator and merges incoming list into coma separated string, which is then not exploded on the server side, as you described in #2157
Effectively this means, that there is no possibility to pass list of values as GET query value using micronaut on both sides. It is especially critical when common interface for both controller and client is used.
Most helpful comment
@jameskleeh
There is still an issue present: even if
*is added both on client and controller side, it won't work, since the client ignores exploded operator and merges incoming list into coma separated string, which is then not exploded on the server side, as you described in #2157Effectively this means, that there is no possibility to pass list of values as GET query value using micronaut on both sides. It is especially critical when common interface for both controller and client is used.