diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/converters/RuntimeParamConverterTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/converters/RuntimeParamConverterTest.java new file mode 100644 index 00000000000000..1f2c14b52aaf8c --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/converters/RuntimeParamConverterTest.java @@ -0,0 +1,140 @@ +package io.quarkus.resteasy.reactive.server.test.converters; + +import io.quarkus.test.QuarkusUnitTest; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ParamConverter; +import jakarta.ws.rs.ext.ParamConverterProvider; +import jakarta.ws.rs.ext.Provider; +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Optional; +import java.util.function.Supplier; + +import static io.restassured.RestAssured.given; + +class RuntimeParamConverterTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(ParamConverterEndpoint.class, OptionalIntegerParamConverterProvider.class, OptionalIntegerParamConverter.class); + } + }); + + + @Test + void sendParameters() { + given().queryParam("number", 22) + .when().get("/param-converter") + .then() + .statusCode(200) + .body(Matchers.is("Hello, 22!")); + } + + @Test + void doNotSendParameters() { + given().when().get("/param-converter") + .then() + .statusCode(200) + .body(Matchers.is("Hello, world! No number was provided.")); + } + + @Test + void sendEmptyParameter() { + given().queryParam("number", "") + .when().get("/param-converter") + .then() + .statusCode(200) + .body(Matchers.is("Hello! You provided an empty number.")); + } + + + + @ApplicationScoped + @Path("/param-converter") + public static class ParamConverterEndpoint { + + @GET + public Response greet(@QueryParam("number") Optional numberOpt) { + if (numberOpt != null) { + if (numberOpt.isPresent()) { + return Response.ok(String.format("Hello, %s!", numberOpt.get())).build(); + } else { + return Response.ok("Hello! You provided an empty number.").build(); + } + } else { + return Response.ok("Hello, world! No number was provided.").build(); + } + } + } + @Provider + @ApplicationScoped + public static class OptionalIntegerParamConverterProvider implements ParamConverterProvider { + + @SuppressWarnings("unchecked") + @Override + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + if (rawType.equals(Optional.class)) { + if (genericType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericType; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length == 1 && typeArguments[0].equals(Integer.class)) { + return (ParamConverter) new OptionalIntegerParamConverter(); + } + } + } + + return null; + } + } + public static class OptionalIntegerParamConverter implements ParamConverter> { + + @Override + public Optional fromString(String value) { + if (value == null) { + return null; + } + + if (value.trim().isEmpty()) { + return Optional.empty(); + } + + try { + int parsedInt = Integer.parseInt(value); + return Optional.of(parsedInt); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid integer value"); + } + } + + @Override + public String toString(Optional value) { + if (!value.isPresent()) { + return null; + } + + Integer intValue = value.get(); + if (intValue == null) { + return null; + } else { + return intValue.toString(); + } + } + + } + +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/RuntimeResolvedConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/RuntimeResolvedConverter.java index 84f9e793affced..983e4fafb0ac16 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/RuntimeResolvedConverter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/RuntimeResolvedConverter.java @@ -21,7 +21,7 @@ public RuntimeResolvedConverter(ParameterConverter quarkusConverter) { @Override public Object convert(Object parameter) { if (runtimeConverter != null) - return runtimeConverter.fromString(parameter.toString()); + return runtimeConverter.fromString(parameter != null ? parameter.toString() : null); return quarkusConverter.convert(parameter); }