Skip to content

Commit

Permalink
Properly use ParamConverter to converting the values of collection types
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand authored and evanchooly committed Sep 8, 2022
1 parent c8b3210 commit 095107d
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.quarkus.resteasy.reactive.server.test.providers;

import static io.restassured.RestAssured.get;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class ParamConverterTest {

private static final String STATIC_UUID = "42f425f1-5923-41ca-a43c-713888762c68";

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(UUIDResource.class, UUIDParamConverterProvider.class));

@Test
public void single() {
get("/uuid/single?id=whatever")
.then()
.statusCode(200)
.body(Matchers.equalTo(STATIC_UUID));
}

@Test
public void set() {
get("/uuid/set?id=whatever&id=whatever2")
.then()
.statusCode(200)
.body(Matchers.equalTo(STATIC_UUID));
}

@Test
public void list() {
get("/uuid/list?id=whatever&id=whatever2")
.then()
.statusCode(200)
.body(Matchers.equalTo(STATIC_UUID + "," + STATIC_UUID));
}

@Path("uuid")
public static class UUIDResource {

@Path("single")
@GET
public String single(@QueryParam("id") UUID uuid) {
return uuid.toString();
}

@Path("set")
@GET
public String set(@QueryParam("id") Set<UUID> uuids) {
return join(uuids.stream());
}

@Path("list")
@GET
public String list(@QueryParam("id") List<UUID> uuids) {
return join(uuids.stream());
}

private static String join(Stream<UUID> uuids) {
return uuids.map(UUID::toString).collect(Collectors.joining(","));
}
}

@Provider
public static class UUIDParamConverterProvider implements ParamConverterProvider {
@SuppressWarnings("unchecked")
@Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
if (rawType.equals(UUID.class)) {
return (ParamConverter<T>) new UUIDParamConverter();
}

return null;
}

public static class UUIDParamConverter implements ParamConverter<UUID> {
@Override
public UUID fromString(String value) {
return UUID.fromString(STATIC_UUID);
}

@Override
public String toString(UUID value) {
return value.toString();
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public void init(ParamConverterProviders deployment, Class<?> rawType, Type gene
delegate.init(deployment, rawType, genericType, annotations);
}

@Override
public boolean isForSingleObjectContainer() {
return true;
}

public ParameterConverter getDelegate() {
return delegate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ public interface ParameterConverter {
default void init(ParamConverterProviders deployment, Class<?> rawType, Type genericType, Annotation[] annotations) {

}

// TODO: this API method may be too limiting
default boolean isForSingleObjectContainer() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ public void init(ParamConverterProviders deployment, Class<?> rawType, Type gene
delegate.init(deployment, rawType, genericType, annotations);
}

@Override
public boolean isForSingleObjectContainer() {
return true;
}

public static class SetSupplier implements DelegatingParameterConverterSupplier {
private ParameterConverterSupplier delegate;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public void init(ParamConverterProviders deployment, Class<?> rawType, Type gene
delegate.init(deployment, rawType, genericType, annotations);
}

@Override
public boolean isForSingleObjectContainer() {
return true;
}

public static class SortedSetSupplier implements DelegatingParameterConverterSupplier {
private ParameterConverterSupplier delegate;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
Expand Down Expand Up @@ -345,8 +346,9 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz,
Class<?>[] parameterTypes = javaMethod.getParameterTypes();
Type[] genericParameterTypes = javaMethod.getGenericParameterTypes();
Annotation[][] parameterAnnotations = javaMethod.getParameterAnnotations();
converter.init(paramConverterProviders, parameterTypes[i], genericParameterTypes[i],
parameterAnnotations[i]);
smartInitParameterConverter(i, converter, paramConverterProviders, parameterTypes, genericParameterTypes,
parameterAnnotations);

// make sure we give the user provided resolvers the chance to convert
converter = new RuntimeResolvedConverter(converter);
converter.init(paramConverterProviders, parameterTypes[i], genericParameterTypes[i],
Expand Down Expand Up @@ -487,6 +489,34 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz,
clazz.resourceExceptionMapper());
}

/**
* This method takes into account the case where a parameter is for example List<UUID>
* and we want to allow users to be able to use their implementation of
* ParamConverter<UUID>.
*/
private static void smartInitParameterConverter(int i, ParameterConverter quarkusConverter,
ParamConverterProviders paramConverterProviders,
Class<?>[] parameterTypes, Type[] genericParameterTypes,
Annotation[][] parameterAnnotations) {
if (quarkusConverter.isForSingleObjectContainer()) {

if (genericParameterTypes[i] instanceof ParameterizedType) {
Type[] genericArguments = ((ParameterizedType) genericParameterTypes[i]).getActualTypeArguments();
if (genericArguments.length == 1) {
quarkusConverter.init(paramConverterProviders, loadClass(genericArguments[0].getTypeName()),
genericArguments[0],
parameterAnnotations[i]);
return;
}
}
}

// TODO: this is almost certainly wrong when genericParameterTypes[i] is a ParameterizedType not handle above,
// but there is no obvious way to handle it...
quarkusConverter.init(paramConverterProviders, parameterTypes[i], genericParameterTypes[i],
parameterAnnotations[i]);
}

private static boolean isNotVoid(Class<?> rawEffectiveReturnType) {
return rawEffectiveReturnType != Void.class
&& rawEffectiveReturnType != void.class;
Expand Down

0 comments on commit 095107d

Please sign in to comment.