diff --git a/http-client/src/test/groovy/io/micronaut/http/client/HttpUriMappingSpec.groovy b/http-client/src/test/groovy/io/micronaut/http/client/HttpUriMappingSpec.groovy new file mode 100644 index 00000000000..f652b98681e --- /dev/null +++ b/http-client/src/test/groovy/io/micronaut/http/client/HttpUriMappingSpec.groovy @@ -0,0 +1,60 @@ +package io.micronaut.http.client + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.annotation.Client +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 = 'HttpUriMappingSpec') +class HttpUriMappingSpec extends Specification { + + @Inject + @Client("/") + HttpClient client; + + @Issue("https://github.com/micronaut-projects/micronaut-core/issues/10957") + void "test a path with default uri mapping and a path with uris custom"() { + when: + def root = client.toBlocking().exchange("/") + def foo = client.toBlocking().exchange("/foo") + def bar = client.toBlocking().exchange("/bar") + def baz = client.toBlocking().exchange("/baz") + + then: + root.status() == HttpStatus.OK + foo.status() == HttpStatus.OK + bar.status() == HttpStatus.OK + baz.status() == HttpStatus.OK + root.getBody(String).get() == "root" + foo.getBody(String).get() == "foo" + bar.getBody(String).get() == "bar" + baz.getBody(String).get() == "bar" + } + + @Controller + @Requires(property = 'spec.name', value = 'HttpUriMappingSpec') + static class AnyController { + + @Get + String root() { + return "root"; + } + + @Get(uris = ["/foo"]) + String foo() { + return "foo"; + } + + @Get(uri = "baz", uris = ["/bar"]) + String bar() { + return "bar"; + } + } +} diff --git a/router/src/main/java/io/micronaut/web/router/AnnotatedMethodRouteBuilder.java b/router/src/main/java/io/micronaut/web/router/AnnotatedMethodRouteBuilder.java index a35d9959ae5..e75650fea27 100644 --- a/router/src/main/java/io/micronaut/web/router/AnnotatedMethodRouteBuilder.java +++ b/router/src/main/java/io/micronaut/web/router/AnnotatedMethodRouteBuilder.java @@ -76,8 +76,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator httpMethodsHandlers.put(Get.class, (RouteDefinition definition) -> { final BeanDefinition bean = definition.beanDefinition; final ExecutableMethod method = definition.executableMethod; - Set uris = CollectionUtils.setOf(method.stringValues(Get.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(Get.class, method); for (String uri: uris) { MediaType[] produces = resolveProduces(method); UriRoute route = GET(resolveUri(bean, uri, @@ -111,8 +110,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator httpMethodsHandlers.put(Post.class, (RouteDefinition definition) -> { final ExecutableMethod method = definition.executableMethod; final BeanDefinition bean = definition.beanDefinition; - Set uris = CollectionUtils.setOf(method.stringValues(Post.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(Post.class, method); for (String uri: uris) { MediaType[] consumes = resolveConsumes(method); MediaType[] produces = resolveProduces(method); @@ -135,8 +133,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator final ExecutableMethod method = definition.executableMethod; final BeanDefinition bean = definition.beanDefinition; - Set uris = CollectionUtils.setOf(method.stringValues(CustomHttpMethod.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(CustomHttpMethod.class, method); for (String uri: uris) { MediaType[] consumes = resolveConsumes(method); MediaType[] produces = resolveProduces(method); @@ -160,8 +157,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator final ExecutableMethod method = definition.executableMethod; final BeanDefinition bean = definition.beanDefinition; - Set uris = CollectionUtils.setOf(method.stringValues(Put.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(Put.class, method); for (String uri: uris) { MediaType[] consumes = resolveConsumes(method); MediaType[] produces = resolveProduces(method); @@ -184,8 +180,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator final ExecutableMethod method = definition.executableMethod; final BeanDefinition bean = definition.beanDefinition; - Set uris = CollectionUtils.setOf(method.stringValues(Patch.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(Patch.class, method); for (String uri: uris) { MediaType[] consumes = resolveConsumes(method); MediaType[] produces = resolveProduces(method); @@ -208,8 +203,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator final ExecutableMethod method = definition.executableMethod; final BeanDefinition bean = definition.beanDefinition; - Set uris = CollectionUtils.setOf(method.stringValues(Delete.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(Delete.class, method); for (String uri: uris) { MediaType[] consumes = resolveConsumes(method); MediaType[] produces = resolveProduces(method); @@ -233,8 +227,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator final ExecutableMethod method = definition.executableMethod; final BeanDefinition bean = definition.beanDefinition; - Set uris = CollectionUtils.setOf(method.stringValues(Head.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(Head.class, method); for (String uri: uris) { UriRoute route = HEAD(resolveUri(bean, uri, method, @@ -254,8 +247,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator final ExecutableMethod method = definition.executableMethod; final BeanDefinition bean = definition.beanDefinition; - Set uris = CollectionUtils.setOf(method.stringValues(Options.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(Options.class, method); for (String uri: uris) { MediaType[] consumes = resolveConsumes(method); MediaType[] produces = resolveProduces(method); @@ -278,8 +270,7 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator final ExecutableMethod method = definition.executableMethod; final BeanDefinition bean = definition.beanDefinition; - Set uris = CollectionUtils.setOf(method.stringValues(Trace.class, "uris")); - uris.add(method.stringValue(HttpMethodMapping.class).orElse(UriMapping.DEFAULT_URI)); + Set uris = this.resolveUrisMapping(Trace.class, method); for (String uri: uris) { UriRoute route = TRACE(resolveUri(bean, uri, method, @@ -335,6 +326,18 @@ public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator ); } + private Set resolveUrisMapping(Class httpMethod, ExecutableMethod method) { + Set uris = CollectionUtils.setOf(method.stringValues(httpMethod, "uris")); + Optional uri = method.stringValue(HttpMethodMapping.class); + if (uris.isEmpty()) { + uris.add(uri.orElse(UriMapping.DEFAULT_URI)); + } else { + uri.ifPresent(uris::add); + } + + return uris; + } + private MediaType[] resolveConsumes(ExecutableMethod method) { MediaType[] consumes = MediaType.of(method.stringValues(Consumes.class)); if (ArrayUtils.isEmpty(consumes)) {