Having a declarative client like:
@Client("/graphql")
static interface GraphQLClient {
@Post
@Header(name = CONTENT_TYPE, value = MULTIPART_FORM_DATA)
GraphQLResponseBody post(@Body MultipartBody body)
}
and invoking it with:
void "test post with multipart body"() {
given:
MultipartBody body = MultipartBody.builder()
.addPart("operations", '{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }')
.addPart("map", '{ "0": ["variables.file"] }')
.addPart("0", "a.txt", TEXT_PLAIN_TYPE, "Alpha file content.".bytes)
.build()
when:
GraphQLResponseBody response = graphQLClient.post(body)
then:
response != null
}
results in the following exception:
io.micronaut.http.codec.CodecException: Error encoding object [io.micronaut.http.client.multipart.MultipartBody@65da5a2b] to JSON: No serializer found for class io.micronaut.http.client.multipart.MultipartBody and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at io.micronaut.jackson.codec.JsonMediaTypeCodec.encode(JsonMediaTypeCodec.java:176)
at io.micronaut.jackson.codec.JsonMediaTypeCodec.encode(JsonMediaTypeCodec.java:182)
at io.micronaut.http.client.DefaultHttpClient.lambda$buildNettyRequest$39(DefaultHttpClient.java:1508)
at java.util.Optional.map(Optional.java:215)
at io.micronaut.http.client.DefaultHttpClient.buildNettyRequest(DefaultHttpClient.java:1508)
at io.micronaut.http.client.DefaultHttpClient.sendRequestThroughChannel(DefaultHttpClient.java:1546)
at io.micronaut.http.client.DefaultHttpClient.lambda$null$26(DefaultHttpClient.java:1024)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:500)
at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:493)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:472)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:413)
at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:538)
at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:527)
at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:98)
at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:302)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:337)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:685)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:632)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:549)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:511)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:918)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class io.micronaut.http.client.multipart.MultipartBody and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:313)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:71)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:33)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3905)
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsBytes(ObjectMapper.java:3243)
at io.micronaut.jackson.codec.JsonMediaTypeCodec.encode(JsonMediaTypeCodec.java:173)
... 24 more
When I create a RxHttpClient and try:
void "test post with multipart body 2"() {
given:
RxHttpClient httpClient = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL())
MultipartBody body = MultipartBody.builder()
.addPart("operations", '{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }')
.addPart("map", '{ "0": ["variables.file"] }')
.addPart("0", "a.txt", TEXT_PLAIN_TYPE, "Alpha file content.".bytes)
.build()
when:
def response = httpClient.exchange(
HttpRequest.POST("/graphql", body)
.contentType(MULTIPART_FORM_DATA_TYPE),
String).blockingFirst()
then:
response != null
}
it works.
It seems the declarative client is not supporting a MultipartBody as argument.
@Header(name = CONTENT_TYPE, value = MULTIPART_FORM_DATA) isn't how Micronaut determines what the content type is. We look for values on the @Produces and @Consumes annotations which are meta annotations of all of the http method annotations (@Put, etc).
@Post(produces = MULTIPART_FORM_DATA)
GraphQLResponseBody post(@Body MultipartBody body)
Most helpful comment
@Header(name = CONTENT_TYPE, value = MULTIPART_FORM_DATA)isn't how Micronaut determines what the content type is. We look for values on the@Producesand@Consumesannotations which are meta annotations of all of the http method annotations (@Put, etc).