diff --git a/http-client/src/test/groovy/io/micronaut/http/body/MessageBodyReaderForPrimitivesSpec.groovy b/http-client/src/test/groovy/io/micronaut/http/body/MessageBodyReaderForPrimitivesSpec.groovy new file mode 100644 index 00000000000..289b1e16cf5 --- /dev/null +++ b/http-client/src/test/groovy/io/micronaut/http/body/MessageBodyReaderForPrimitivesSpec.groovy @@ -0,0 +1,155 @@ +package io.micronaut.http.body + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.core.convert.TypeConverterRegistrar +import io.micronaut.core.type.Argument +import io.micronaut.http.MediaType +import io.micronaut.http.annotation.Body +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.annotation.Post +import io.micronaut.http.client.annotation.Client +import io.micronaut.runtime.server.EmbeddedServer +import io.micronaut.test.annotation.MockBean +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Issue +import spock.lang.Specification + +@MicronautTest +@Property(name = "spec.name", value = "ConversionPrimitiveHandlerSpec") +@Issue('https://github.com/micronaut-projects/micronaut-core/issues/10698') +class MessageBodyReaderForPrimitivesSpec extends Specification { + + @Inject EmbeddedServer server + @Inject MessageBodyHandlerRegistry messageBodyHandlerRegistry + @Inject TestClient client + + @MockBean(bean = TypeConverterRegistrar, named = "NettyConverters") + TypeConverterRegistrar nettyConverters() { + // block loading the Netty converters, which is analogous to http-server-netty not being loaded + Mock(TypeConverterRegistrar.class) + } + + def 'test boolean output'() { + expect: + client.testBoolean() + } + + def 'test boolean input and output'() { + expect: + !client.testBoolean(false) + client.testBoolean(true) + } + + def 'test long output'() { + expect: + client.testLong() == 1L + } + + def 'test long input and output'() { + expect: + client.testLong(2L) == 2L + } + + def 'test int output'() { + expect: + client.testInt() == 3 + } + + def 'test int input and output'() { + expect: + client.testInt(4) == 4 + } + + def 'test MessageBodyReader for boolean exists'() { + when: + def reader = + messageBodyHandlerRegistry + .findReader(Argument.of(boolean.class), [MediaType.APPLICATION_JSON_TYPE]) + + then: + reader.isPresent() + } + + def 'test MessageBodyReader for long exists'() { + when: + def reader = + messageBodyHandlerRegistry + .findReader(Argument.of(long.class), [MediaType.APPLICATION_JSON_TYPE]) + + then: + reader.isPresent() + } + + def 'test MessageBodyReader for int exists'() { + when: + def reader = + messageBodyHandlerRegistry + .findReader(Argument.of(int.class), [MediaType.APPLICATION_JSON_TYPE]) + + then: + reader.isPresent() + } + + @Client("/primitives") + @Requires(property = "spec.name", value = "ConversionPrimitiveHandlerSpec") + static interface TestClient { + + @Get("boolean") + boolean testBoolean(); + + @Post("boolean") + boolean testBoolean(@Body boolean value); + + @Get("long") + long testLong(); + + @Post("long") + long testLong(@Body long value); + + @Get("int") + int testInt(); + + @Post("int") + int testInt(@Body int value); + + } + + @Controller("/primitives") + @Requires(property = "spec.name", value = "ConversionPrimitiveHandlerSpec") + static class TestController { + + @Get("boolean") + boolean testBoolean() { + return true; + } + + @Post("boolean") + boolean testBoolean(@Body boolean value) { + return value; + } + + @Get("long") + long testLong() { + return 1L; + } + + @Post("long") + long testLong(@Body long value) { + return value; + } + + @Get("int") + int testInt() { + return 3; + } + + @Post("int") + int testInt(@Body int value) { + return value; + } + + } +} diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/ContentNegotiationSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/ContentNegotiationSpec.groovy index 16793e3df40..5951205488c 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/ContentNegotiationSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/ContentNegotiationSpec.groovy @@ -1,6 +1,8 @@ package io.micronaut.http.server.netty import groovy.transform.InheritConstructors +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires import io.micronaut.core.type.Argument import io.micronaut.http.HttpRequest import io.micronaut.http.HttpResponse @@ -28,6 +30,7 @@ import static io.micronaut.http.server.netty.ContentNegotiationSpec.NegotiatingC import static io.micronaut.http.server.netty.ContentNegotiationSpec.NegotiatingController.XML @MicronautTest +@Property(name = "spec.name", value = "ContentNegotiationSpec") class ContentNegotiationSpec extends Specification { @Inject @@ -171,6 +174,7 @@ class ContentNegotiationSpec extends Specification { } @Controller("/negotiate") + @Requires(property = "spec.name", value = "ContentNegotiationSpec") static class NegotiatingController { public static final String XML = "world" diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/MaxRequestSizeSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/MaxRequestSizeSpec.groovy index 3ffd672a006..912ccdfeb14 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/MaxRequestSizeSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/MaxRequestSizeSpec.groovy @@ -1,6 +1,7 @@ package io.micronaut.http.server.netty import io.micronaut.context.ApplicationContext +import io.micronaut.context.annotation.Requires import io.micronaut.core.annotation.NonNull import io.micronaut.core.async.annotation.SingleResult import io.micronaut.http.HttpRequest @@ -61,8 +62,13 @@ import java.util.concurrent.CopyOnWriteArrayList class MaxRequestSizeSpec extends Specification { + static final String SPEC_NAME = 'MaxRequestSizeSpec' + void "test max request size default processor"() { - EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, ['micronaut.server.maxRequestSize': '10KB']) + EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ + 'micronaut.server.maxRequestSize': '10KB', + 'spec.name': SPEC_NAME + ]) HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) when: @@ -86,7 +92,10 @@ class MaxRequestSizeSpec extends Specification { } void "test max request size json processor"() { - EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, ['micronaut.server.maxRequestSize': '10KB']) + EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ + 'micronaut.server.maxRequestSize': '10KB', + 'spec.name': SPEC_NAME + ]) HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) when: @@ -111,7 +120,10 @@ class MaxRequestSizeSpec extends Specification { @Ignore("Whether or not the exception is thrown is inconsistent. I don't think there is anything we can do to ensure its consistency") void "test max request size multipart processor"() { - EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, ['micronaut.server.maxRequestSize': '10KB']) + EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ + 'micronaut.server.maxRequestSize': '10KB', + 'spec.name': SPEC_NAME + ]) HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) when: @@ -149,7 +161,8 @@ class MaxRequestSizeSpec extends Specification { void "test max part size multipart processor"() { EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ - 'micronaut.server.multipart.maxFileSize': '1KB' + 'micronaut.server.multipart.maxFileSize': '1KB', + 'spec.name': SPEC_NAME ]) HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) @@ -188,7 +201,8 @@ class MaxRequestSizeSpec extends Specification { void "test max part size multipart body binder"() { EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ - 'micronaut.server.multipart.maxFileSize': '1KB' + 'micronaut.server.multipart.maxFileSize': '1KB', + 'spec.name': SPEC_NAME ]) HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) @@ -228,7 +242,8 @@ class MaxRequestSizeSpec extends Specification { void "test content length exceeded with different disk storage"() { EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ 'micronaut.server.maxRequestSize': '10KB', - 'micronaut.server.multipart.disk': true + 'micronaut.server.multipart.disk': true, + 'spec.name': SPEC_NAME ]) HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) @@ -254,7 +269,8 @@ class MaxRequestSizeSpec extends Specification { given: EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ 'micronaut.server.maxRequestSize': '10KB', - 'micronaut.server.port': -1 + 'micronaut.server.port': -1, + 'spec.name': SPEC_NAME ]) def responses = new CopyOnWriteArrayList() @@ -334,7 +350,8 @@ class MaxRequestSizeSpec extends Specification { 'micronaut.server.ssl.enabled': true, 'micronaut.server.netty.log-level': 'TRACE', 'micronaut.server.ssl.port': -1, - 'micronaut.server.ssl.buildSelfSigned': true + 'micronaut.server.ssl.buildSelfSigned': true, + 'spec.name': SPEC_NAME ]) def request1 = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, '/test-max-size/multipart-body') @@ -433,6 +450,7 @@ class MaxRequestSizeSpec extends Specification { } @Controller("/test-max-size") + @Requires(property = "spec.name", value = "MaxRequestSizeSpec") static class TestController { @Post(uri = "/text", consumes = MediaType.TEXT_PLAIN) diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/DefaultJsonErrorHandlingSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/DefaultJsonErrorHandlingSpec.groovy index a13a64e4723..8e02a106daf 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/DefaultJsonErrorHandlingSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/DefaultJsonErrorHandlingSpec.groovy @@ -1,6 +1,7 @@ package io.micronaut.http.server.netty.binding import groovy.json.JsonSlurper +import io.micronaut.context.annotation.Requires import io.micronaut.http.HttpRequest import io.micronaut.http.HttpStatus import io.micronaut.http.annotation.Body @@ -36,6 +37,7 @@ class DefaultJsonErrorHandlingSpec extends AbstractMicronautSpec { } @Controller("/errors") + @Requires(property = "spec.name", value = "DefaultJsonErrorHandlingSpec") static class ErrorsController { @Post("/string") String string(@Body String text) { diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/FormDataBindingSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/FormDataBindingSpec.groovy index 427161f8c4c..ba04f10f075 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/FormDataBindingSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/FormDataBindingSpec.groovy @@ -16,6 +16,7 @@ package io.micronaut.http.server.netty.binding import groovy.transform.EqualsAndHashCode +import io.micronaut.context.annotation.Requires import io.micronaut.core.async.annotation.SingleResult import io.micronaut.http.HttpRequest import io.micronaut.http.HttpResponse @@ -260,6 +261,7 @@ class FormDataBindingSpec extends AbstractMicronautSpec { } @Controller(value = '/form', consumes = MediaType.APPLICATION_FORM_URLENCODED) + @Requires(property = "spec.name", value = "FormDataBindingSpec") static class FormController { @Post('/string') @@ -296,6 +298,7 @@ class FormDataBindingSpec extends AbstractMicronautSpec { } @Controller('/form/saml/test') + @Requires(property = "spec.name", value = "FormDataBindingSpec") static class MainController { @Post(consumes = MediaType.APPLICATION_FORM_URLENCODED) diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/FormDataDiskSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/FormDataDiskSpec.groovy index 94140ffe608..c2900264977 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/FormDataDiskSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/FormDataDiskSpec.groovy @@ -1,6 +1,7 @@ package io.micronaut.http.server.netty.binding import io.micronaut.context.ApplicationContext +import io.micronaut.context.annotation.Requires import io.micronaut.http.HttpRequest import io.micronaut.http.HttpResponse import io.micronaut.http.HttpStatus @@ -22,7 +23,8 @@ class FormDataDiskSpec extends Specification { 'micronaut.server.multipart.disk': true, 'micronaut.server.multipart.mixed': true, 'micronaut.server.thread-selection': 'IO', - 'netty.resource-leak-detector-level': 'paranoid' + 'netty.resource-leak-detector-level': 'paranoid', + "spec.name": "FormDataDiskSpec" ]) def client = server.applicationContext.createBean(HttpClient, server.URI) @@ -47,7 +49,8 @@ class FormDataDiskSpec extends Specification { def server = (EmbeddedServer) ApplicationContext.run(EmbeddedServer, [ 'micronaut.server.multipart.mixed': true, 'micronaut.server.thread-selection': 'IO', - 'netty.resource-leak-detector-level': 'paranoid' + 'netty.resource-leak-detector-level': 'paranoid', + "spec.name": "FormDataDiskSpec" ]) def client = server.applicationContext.createBean(HttpClient, server.URI) @@ -67,6 +70,7 @@ class FormDataDiskSpec extends Specification { } @Controller(value = '/form-disk', consumes = MediaType.APPLICATION_FORM_URLENCODED) + @Requires(property = "spec.name", value = "FormDataDiskSpec") static class FormController { @Post('/object') Object object(@Body Object object) { diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/JsonBodyBindingSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/JsonBodyBindingSpec.groovy index f63126bda55..cea5ada6aad 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/JsonBodyBindingSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/JsonBodyBindingSpec.groovy @@ -2,6 +2,7 @@ package io.micronaut.http.server.netty.binding import com.fasterxml.jackson.annotation.JsonCreator import groovy.json.JsonSlurper +import io.micronaut.context.annotation.Requires import io.micronaut.core.annotation.Introspected import io.micronaut.core.async.annotation.SingleResult import io.micronaut.http.HttpHeaders @@ -26,6 +27,11 @@ import java.util.concurrent.CompletableFuture class JsonBodyBindingSpec extends AbstractMicronautSpec { + @Override + Map getConfiguration() { + return super.getConfiguration() + ['test.controller': 'JsonController'] + } + void "test JSON is not parsed when the body is a raw body type"() { when: String json = '{"title":"The Stand"' @@ -304,6 +310,7 @@ class JsonBodyBindingSpec extends AbstractMicronautSpec { } @Controller(value = "/json", produces = io.micronaut.http.MediaType.APPLICATION_JSON) + @Requires(property = "test.controller", value = "JsonController") static class JsonController { @Post("/params") diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/http2/H2cSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/http2/H2cSpec.groovy index 2ce3c3b33ab..c50824e51ed 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/http2/H2cSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/http2/H2cSpec.groovy @@ -1,6 +1,7 @@ package io.micronaut.http.server.netty.http2 import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires import io.micronaut.core.annotation.NonNull import io.micronaut.core.io.buffer.ByteBuffer import io.micronaut.http.HttpRequest @@ -52,6 +53,7 @@ import java.util.concurrent.TimeUnit //@Property(name = "micronaut.server.port", value = "8912") @Property(name = "micronaut.http.client.plaintext-mode", value = "h2c") @Property(name = "micronaut.server.ssl.enabled", value = "false") +@Property(name = "spec.name", value = "H2cSpec") @Issue('https://github.com/micronaut-projects/micronaut-core/issues/5005') class H2cSpec extends Specification { @Inject @@ -228,6 +230,7 @@ class H2cSpec extends Specification { } @Controller("/h2c") + @Requires(property = "spec.name", value = "H2cSpec") static class TestController { @Get("/test") String test(HttpRequest request) { diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/jackson/JsonViewController.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/jackson/JsonViewController.groovy index 8c7b13ef91b..e5e44ac417c 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/jackson/JsonViewController.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/jackson/JsonViewController.groovy @@ -16,6 +16,7 @@ package io.micronaut.http.server.netty.jackson import com.fasterxml.jackson.annotation.JsonView +import io.micronaut.context.annotation.Requires import io.micronaut.http.HttpResponse import io.micronaut.http.HttpStatus import io.micronaut.http.annotation.Body @@ -28,6 +29,7 @@ import reactor.core.publisher.Flux import reactor.core.publisher.Mono @Controller("/jsonview") +@Requires(property = "spec.name", value = "JsonViewServerFilterSpec") class JsonViewController { static TestModel TEST_MODEL = new TestModel(firstName: "Bob", lastName: "Jones", birthdate: "08/01/1980", password: "secret") diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/jackson/JsonViewServerFilterSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/jackson/JsonViewServerFilterSpec.groovy index 8cb1a8bb1e0..567192832de 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/jackson/JsonViewServerFilterSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/jackson/JsonViewServerFilterSpec.groovy @@ -33,7 +33,8 @@ class JsonViewServerFilterSpec extends Specification { @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ - 'jackson.json-view.enabled': true + 'jackson.json-view.enabled': true, + 'spec.name': 'JsonViewServerFilterSpec' ], "test") diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/stream/JsonStreamSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/stream/JsonStreamSpec.groovy index c0120802323..a7a43f64fea 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/stream/JsonStreamSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/stream/JsonStreamSpec.groovy @@ -16,6 +16,7 @@ package io.micronaut.http.server.netty.stream import io.micronaut.context.ApplicationContext +import io.micronaut.context.annotation.Requires import io.micronaut.http.HttpRequest import io.micronaut.http.HttpResponse import io.micronaut.http.MediaType @@ -37,7 +38,10 @@ import io.micronaut.core.async.annotation.SingleResult */ class JsonStreamSpec extends Specification { - @Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, ['micronaut.server.serverHeader': 'JsonStreamSpec']) + @Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [ + 'micronaut.server.serverHeader': 'JsonStreamSpec', + 'spec.name': 'JsonStreamSpec', + ]) void "test json stream response content type"() { given: @@ -97,6 +101,7 @@ class JsonStreamSpec extends Specification { } @Controller("/json/stream") + @Requires(property = "spec.name", value = "JsonStreamSpec") static class StreamController { @Get(produces = MediaType.APPLICATION_JSON_STREAM) diff --git a/http/src/main/java/io/micronaut/http/body/DefaultMessageBodyHandlerRegistry.java b/http/src/main/java/io/micronaut/http/body/DefaultMessageBodyHandlerRegistry.java index ad5a121948b..03c8d745efb 100644 --- a/http/src/main/java/io/micronaut/http/body/DefaultMessageBodyHandlerRegistry.java +++ b/http/src/main/java/io/micronaut/http/body/DefaultMessageBodyHandlerRegistry.java @@ -75,8 +75,16 @@ public final class DefaultMessageBodyHandlerRegistry extends RawMessageBodyHandl @SuppressWarnings({"rawtypes", "unchecked"}) @Override protected MessageBodyReader findReaderImpl(Argument type, List mediaTypes) { + final Argument finalType; + if (type.isPrimitive()) { + Class wrapperType = type.getWrapperType(); + finalType = (Argument) Argument.of(wrapperType, type.getAnnotationMetadata()); + } else { + finalType = type; + } + Collection> beanDefinitions = beanLocator.getBeanDefinitions( - Argument.of(MessageBodyReader.class, type), + Argument.of(MessageBodyReader.class, finalType), newMediaTypeQualifier(Argument.of(MessageBodyReader.class), mediaTypes, Consumes.class) ); if (beanDefinitions.size() == 1) { @@ -88,7 +96,7 @@ protected MessageBodyReader findReaderImpl(Argument type, List