Skip to content

Commit

Permalink
Merge pull request #42731 from indalaterre/support-for-collection-dat…
Browse files Browse the repository at this point in the history
…es-parameter

Support for collection of java.time in query params
  • Loading branch information
FroMage authored Aug 27, 2024
2 parents e81202b + a7c18f1 commit 03b025c
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1355,21 +1355,24 @@ && isParameterContainerType(paramType.asClassType())) {
builder.setSingle(false);
elementType = toClassName(pt.arguments().get(0), currentClassInfo, actualEndpointInfo, index);
if (convertible) {
handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
}
} else if (pt.name().equals(SET)) {
typeHandled = true;
builder.setSingle(false);
elementType = toClassName(pt.arguments().get(0), currentClassInfo, actualEndpointInfo, index);
if (convertible) {
handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
}
} else if (pt.name().equals(SORTED_SET)) {
typeHandled = true;
builder.setSingle(false);
elementType = toClassName(pt.arguments().get(0), currentClassInfo, actualEndpointInfo, index);
if (convertible) {
handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
}
} else if (pt.name().equals(OPTIONAL)) {
typeHandled = true;
Expand All @@ -1388,7 +1391,8 @@ && isParameterContainerType(paramType.asClassType())) {
} else if (convertible) {
typeHandled = true;
elementType = toClassName(pt, currentClassInfo, actualEndpointInfo, index);
handleOtherParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleOtherParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
} else if (builder.getType() != ParameterType.BEAN) {
// the "element" type is not of importance as in this case the signature is used at runtime to determine the proper types
elementType = DUMMY_ELEMENT_TYPE.toString();
Expand All @@ -1411,21 +1415,24 @@ && isParameterContainerType(paramType.asClassType())) {
typeHandled = true;
builder.setSingle(false);
if (convertible) {
handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
}
} else if (paramType.name().equals(SET) && type == ParameterType.HEADER) { // RESTEasy Classic handles the non-generic Set type
elementType = String.class.getName();
typeHandled = true;
builder.setSingle(false);
if (convertible) {
handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
}
} else if (paramType.name().equals(SORTED_SET) && type == ParameterType.HEADER) { // RESTEasy Classic handles the non-generic SortedSet type
elementType = String.class.getName();
typeHandled = true;
builder.setSingle(false);
if (convertible) {
handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
}
} else if (paramType.kind() == Kind.ARRAY) {
ArrayType at = paramType.asArrayType();
Expand All @@ -1437,7 +1444,8 @@ && isParameterContainerType(paramType.asClassType())) {
}
elementType = toClassName(at.constituent(), currentClassInfo, actualEndpointInfo, index);
if (convertible) {
handleArrayParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleArrayParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
}
}

Expand All @@ -1447,7 +1455,8 @@ && isParameterContainerType(paramType.asClassType())) {

if (type != ParameterType.CONTEXT && type != ParameterType.BEAN && type != ParameterType.BODY
&& type != ParameterType.ASYNC_RESPONSE && type != ParameterType.MULTI_PART_FORM) {
handleOtherParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
handleOtherParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
currentMethodInfo);
}
if (type == ParameterType.CONTEXT && elementType.equals(SseEventSink.class.getName())) {
builder.setSse(true);
Expand Down Expand Up @@ -1507,11 +1516,11 @@ protected DeclaredTypes getDeclaredTypes(Type paramType, ClassInfo currentClassI
}

protected void handleOtherParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
PARAM builder, String elementType) {
PARAM builder, String elementType, MethodInfo currentMethodInfo) {
}

protected void handleSortedSetParam(Map<String, String> existingConverters, String errorLocation,
boolean hasRuntimeConverters, PARAM builder, String elementType) {
boolean hasRuntimeConverters, PARAM builder, String elementType, MethodInfo currentMethodInfo) {
}

protected void handleOptionalParam(Map<String, String> existingConverters,
Expand All @@ -1522,15 +1531,15 @@ protected void handleOptionalParam(Map<String, String> existingConverters,
}

protected void handleSetParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
PARAM builder, String elementType) {
PARAM builder, String elementType, MethodInfo currentMethodInfo) {
}

protected void handleListParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
PARAM builder, String elementType) {
PARAM builder, String elementType, MethodInfo currentMethodInfo) {
}

protected void handleArrayParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
PARAM builder, String elementType) {
PARAM builder, String elementType, MethodInfo currentMethodInfo) {
}

final boolean isContextType(ClassType klass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,10 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas

@Override
protected void handleOtherParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
ServerIndexedParameter builder, String elementType) {
ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) {
try {
builder.setConverter(extractConverter(elementType, index,
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns()));
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo));
} catch (Throwable throwable) {
throw new RuntimeException("Could not create converter for " + elementType + " for " + builder.getErrorLocation()
+ " of type " + builder.getType(), throwable);
Expand All @@ -481,9 +481,9 @@ protected void handleOtherParam(Map<String, String> existingConverters, String e

@Override
protected void handleSortedSetParam(Map<String, String> existingConverters, String errorLocation,
boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) {
boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) {
ParameterConverterSupplier converter = extractConverter(elementType, index,
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns());
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo);
builder.setConverter(new SortedSetConverter.SortedSetSupplier(converter));
}

Expand All @@ -497,7 +497,7 @@ protected void handleOptionalParam(Map<String, String> existingConverters,

if (genericElementType != null) {
ParameterConverterSupplier genericTypeConverter = extractConverter(genericElementType, index, existingConverters,
errorLocation, hasRuntimeConverters, builder.getAnns());
errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo);
if (LIST.toString().equals(elementType)) {
converter = new ListConverter.ListSupplier(genericTypeConverter);
builder.setSingle(false);
Expand All @@ -516,33 +516,33 @@ protected void handleOptionalParam(Map<String, String> existingConverters,
if (converter == null) {
// If no generic type provided or element type is not supported, then we try to use a custom runtime converter:
converter = extractConverter(elementType, index, existingConverters, errorLocation, hasRuntimeConverters,
builder.getAnns());
builder.getAnns(), currentMethodInfo);
}

builder.setConverter(new OptionalConverter.OptionalSupplier(converter));
}

@Override
protected void handleSetParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
ServerIndexedParameter builder, String elementType) {
ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) {
ParameterConverterSupplier converter = extractConverter(elementType, index,
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns());
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo);
builder.setConverter(new SetConverter.SetSupplier(converter));
}

@Override
protected void handleListParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
ServerIndexedParameter builder, String elementType) {
ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) {
ParameterConverterSupplier converter = extractConverter(elementType, index,
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns());
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo);
builder.setConverter(new ListConverter.ListSupplier(converter));
}

@Override
protected void handleArrayParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
ServerIndexedParameter builder, String elementType) {
ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) {
ParameterConverterSupplier converter = extractConverter(elementType, index,
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns());
existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo);
builder.setConverter(new ArrayConverter.ArraySupplier(converter, elementType));
}

Expand Down Expand Up @@ -652,7 +652,7 @@ private String contextualizeErrorMessage(String errorMessage, MethodInfo current

private ParameterConverterSupplier extractConverter(String elementType, IndexView indexView,
Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
Map<DotName, AnnotationInstance> annotations) {
Map<DotName, AnnotationInstance> annotations, MethodInfo currentMethodInfo) {
// no converter if we have a RestForm mime type: this goes via message body readers in MultipartFormParamExtractor
if (getPartMime(annotations) != null)
return null;
Expand Down Expand Up @@ -685,7 +685,14 @@ private ParameterConverterSupplier extractConverter(String elementType, IndexVie
|| elementType.equals(InputStream.class.getName())) {
// this is handled by MultipartFormParamExtractor
return null;
} else {
DotName typeName = DotName.createSimple(elementType);
if (SUPPORT_TEMPORAL_PARAMS.contains(typeName)) {
//It might be a LocalDate[Time] object
return determineTemporalConverter(typeName, annotations, currentMethodInfo);
}
}

return converterSupplierIndexerExtension.extractConverterImpl(elementType, indexView, existingConverters, errorLocation,
hasRuntimeConverters);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.FormParam;
Expand All @@ -14,6 +16,7 @@

import org.hamcrest.Matchers;
import org.jboss.resteasy.reactive.DateFormat;
import org.jboss.resteasy.reactive.Separator;
import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
Expand All @@ -33,6 +36,12 @@ public void localDateAsQueryParam() {
.then().statusCode(200).body(Matchers.equalTo("hello#1984-08-08"));
}

@Test
public void localDateCollectionAsQueryParam() {
RestAssured.get("/hello/set?dates=08-08-1984,25-04-1992")
.then().statusCode(200).body(Matchers.equalTo("hello#08-08-1984,25-04-1992"));
}

@Test
public void localDateAsPathParam() {
RestAssured.get("/hello/1995-09-21")
Expand All @@ -42,7 +51,13 @@ public void localDateAsPathParam() {
@Test
public void localDateAsFormParam() {
RestAssured.given().formParam("date", "1995/09/22").post("/hello")
.then().statusCode(200).body(Matchers.equalTo("hello:1995-09-22"));
.then().statusCode(200).body(Matchers.equalTo("hello:1995/09/22"));
}

@Test
public void localDateCollectionAsFormParam() {
RestAssured.given().formParam("date", "1995/09/22", "1992/04/25").post("/hello")
.then().statusCode(200).body(Matchers.equalTo("hello:1992/04/25,1995/09/22"));
}

@Test
Expand All @@ -67,6 +82,16 @@ public String helloQuery(@QueryParam("date") @DateFormat(pattern = "dd-MM-yyyy")
return "hello#" + date;
}

@GET
@Path("/set")
public String helloQuerySet(
@Separator(",") @QueryParam("dates") @DateFormat(pattern = "dd-MM-yyyy") Set<LocalDate> dates) {
String formattedDates = dates.stream()
.map(date -> date.format(DateTimeFormatter.ofPattern("dd-MM-yyyy")))
.collect(Collectors.joining(","));
return "hello#" + formattedDates;
}

@GET
@Path("{date}")
public String helloPath(@PathParam("date") LocalDate date) {
Expand All @@ -79,6 +104,15 @@ public String helloForm(
return "hello:" + date;
}

@POST
public String helloFormSet(
@FormParam("date") @DateFormat(dateTimeFormatterProvider = CustomDateTimeFormatterProvider.class) Set<LocalDate> dates) {
String formattedDates = dates.stream()
.map(date -> date.format(CustomDateTimeFormatterProvider.FORMATTER))
.collect(Collectors.joining(","));
return "hello:" + formattedDates;
}

@GET
@Path("cookie")
public String helloCookie(
Expand All @@ -95,9 +129,12 @@ public String helloHeader(
}

public static class CustomDateTimeFormatterProvider implements DateFormat.DateTimeFormatterProvider {

static DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd");

@Override
public DateTimeFormatter get() {
return DateTimeFormatter.ofPattern("yyyy/MM/dd");
return FORMATTER;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.GET;
Expand All @@ -15,6 +17,7 @@
import org.jboss.resteasy.reactive.RestHeader;
import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestQuery;
import org.jboss.resteasy.reactive.Separator;
import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
Expand All @@ -36,6 +39,12 @@ public void localDateTimeAsQueryParam() {
.then().statusCode(200).body(Matchers.equalTo("hello#1984"));
}

@Test
public void localDateTimeCollectionAsQueryParam() {
RestAssured.get("/hello?date=1984-08-08T01:02:03,1992-04-25T01:02:03")
.then().statusCode(200).body(Matchers.equalTo("hello#1984,1992"));
}

@Test
public void localDateTimeAsOptionalQueryParam() {
RestAssured.get("/hello/optional?date=1984-08-08T01:02:03")
Expand All @@ -57,6 +66,12 @@ public void localDateTimeAsFormParam() {
.then().statusCode(200).body(Matchers.equalTo("hello:22"));
}

@Test
public void localDateTimeCollectionAsFormParam() {
RestAssured.given().formParam("date", "1995/09/22 01:02", "1992/04/25 01:02").post("/hello")
.then().statusCode(200).body(Matchers.equalTo("hello:22,25"));
}

@Test
public void localDateTimeAsHeader() {
RestAssured.with().header("date", "1984-08-08 01:02:03")
Expand All @@ -79,6 +94,16 @@ public String helloQuery(@RestQuery LocalDateTime date) {
return "hello#" + date.getYear();
}

@GET
public String helloQuerySet(@RestQuery @Separator(",") Set<LocalDateTime> date) {
String joinedYears = date.stream()
.map(LocalDateTime::getYear)
.sorted()
.map(Object::toString)
.collect(Collectors.joining(","));
return "hello#" + joinedYears;
}

@Path("optional")
@GET
public String helloOptionalQuery(@RestQuery Optional<LocalDateTime> date) {
Expand All @@ -97,6 +122,17 @@ public String helloForm(
return "hello:" + date.getDayOfMonth();
}

@POST
public String helloFormSet(
@FormParam("date") @DateFormat(dateTimeFormatterProvider = CustomDateTimeFormatterProvider.class) Set<LocalDateTime> dates) {
String joinedDays = dates.stream()
.map(LocalDateTime::getDayOfMonth)
.sorted()
.map(Object::toString)
.collect(Collectors.joining(","));
return "hello:" + joinedDays;
}

@Path("cookie")
@GET
public String helloCookie(@RestCookie @DateFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime date) {
Expand Down

0 comments on commit 03b025c

Please sign in to comment.