diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java index baa2e8d3771d5..2dcdb5617aca8 100644 --- a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java +++ b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java @@ -5,6 +5,7 @@ import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -23,4 +24,8 @@ public interface HelloClient { @GET @Path("/{name}") public String helloWithPathParam(@PathParam("name") String name); + + @GET + @Path("/query") + public String helloWithQueryParam(@QueryParam("foo") String foo); } diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java index 1a544e2ab878e..1e530c7ce1b25 100644 --- a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java +++ b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java @@ -5,6 +5,7 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Request; @@ -26,6 +27,12 @@ public String invoke(@PathParam("name") String name) { return "Hello, " + name; } + @GET + @Path("/query") + public String helloWithQueryParam(@QueryParam("foo") String foo) { + return "Hello, this is your query parameter: " + foo; + } + @POST public String echo(String name, @Context Request request) { return "hello, " + name; diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java index 51f11c1b539ca..942914bd10198 100644 --- a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java +++ b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java @@ -5,6 +5,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RestClient; @@ -29,6 +30,12 @@ public String invokeClientWithPathParamContainingSlash(@PathParam("name") String return client.helloWithPathParam(name + "/" + name); } + @Path("/v2/query") + @GET + public String invokeClientWithQueryParam(@QueryParam("foo") String foo) { + return client.helloWithQueryParam(foo); + } + @Path("/{name}") @GET public String invokeClientWithPathParam(@PathParam("name") String name) { diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java index 5a12b520c497b..f9dd214c4e9cb 100644 --- a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java +++ b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java @@ -79,6 +79,16 @@ void shouldSayHelloNameWithSlash() { } + @Test + void shouldEncodeQueryCorrectly() { + when() + .get("/helper/v2/query?foo=cigüeña") + .then() + .statusCode(200) + .body(equalTo("Hello, this is your query parameter: cigüeña")); + + } + @Test void shouldSayHelloNameWithBlank() { when() diff --git a/independent-projects/resteasy-reactive/client/runtime/pom.xml b/independent-projects/resteasy-reactive/client/runtime/pom.xml index 92e68aeaa6f23..098b3bb4ebb5d 100644 --- a/independent-projects/resteasy-reactive/client/runtime/pom.xml +++ b/independent-projects/resteasy-reactive/client/runtime/pom.xml @@ -63,6 +63,11 @@ junit-jupiter test + + org.mockito + mockito-core + test + org.jboss.logging diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java index ea966459e817a..f28e673cca20b 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java @@ -90,8 +90,8 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) { //To avoid the path double encoding we create uri with path=null and set the path after URI newUri = new URI(scheme, uri.getUserInfo(), host, port, - null, uri.getQuery(), uri.getFragment()); - URI build = UriBuilder.fromUri(newUri).path(actualPath).build(); + null, null, uri.getFragment()); + URI build = UriBuilder.fromUri(newUri).path(actualPath).replaceQuery(uri.getRawQuery()).build(); requestContext.setUri(build); if (measureTime && instance.gatherStatistics()) { requestContext.setCallStatsCollector(instance); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/test/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilterTest.java b/independent-projects/resteasy-reactive/client/runtime/src/test/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilterTest.java new file mode 100644 index 0000000000000..55127c0b249af --- /dev/null +++ b/independent-projects/resteasy-reactive/client/runtime/src/test/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilterTest.java @@ -0,0 +1,56 @@ +package org.jboss.resteasy.reactive.client.impl; + +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; + +import jakarta.ws.rs.core.GenericType; + +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import io.smallrye.stork.Stork; +import io.smallrye.stork.api.Service; +import io.smallrye.stork.api.ServiceInstance; + +class StorkClientRequestFilterTest { + @Test + void testQueryEncoding() { + // Given + Stork mockStork = mock(Stork.class); + try (MockedStatic storkStatic = mockStatic(Stork.class)) { + storkStatic.when(Stork::getInstance).thenReturn(mockStork); + Service mockedService = mock(Service.class); + ServiceInstance mockedServiceInstance = mock(ServiceInstance.class); + when(mockStork.getService(anyString())).thenReturn(mockedService); + when(mockedService.selectInstanceAndRecordStart(anyBoolean())) + .thenReturn(Uni.createFrom().item(mockedServiceInstance)); + StorkClientRequestFilter filter = new StorkClientRequestFilter(); + ResteasyReactiveClientRequestContext context = mock(ResteasyReactiveClientRequestContext.class); + + URI originalUri = URI + .create("stork://my-service/some/path?foo=bar%20baz&special=%26%3D&ae=%C3%A4&oe=%C3%B6&ue=%C3%BC"); + when(context.getUri()).thenReturn(originalUri); + when(context.getResponseType()).thenReturn(new GenericType<>(Multi.class)); + + // When + filter.filter(context); + + // Then + verify(context).setUri(argThat(uri -> { + // Query should be preserved and properly encoded + return "http://localhost:80/some/path?foo=bar%20baz&special=%26%3D&ae=%C3%A4&oe=%C3%B6&ue=%C3%BC" + .equals(uri.toString()); + })); + } + } +}