Skip to content

Commit

Permalink
Support of generic collections in RESTEasy Reactive server and client
Browse files Browse the repository at this point in the history
There were some issues when supporting List, Set and SortedSet:
- In the server, List, Set and SortedSet types without the element type was not working
- In the client, it was unsopported as it was always trying to cast the header to string (and hence failing).

Fix #31866

(cherry picked from commit 41aba27)
  • Loading branch information
Sgitario authored and gsmet committed Mar 20, 2023
1 parent e89ccd2 commit 764f8d6
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ A more full example of generated client (with sub-resource) can is at the bottom
MethodDescriptor handleHeaderDescriptor = MethodDescriptor.ofMethod(name,
method.getName() + "$$" + methodIndex + "$$handleHeader$$" + paramIdx,
Invocation.Builder.class,
Invocation.Builder.class, param.type);
Invocation.Builder.class, param.declaredType);
MethodCreator handleHeaderMethod = classContext.classCreator.getMethodCreator(
handleHeaderDescriptor).setModifiers(Modifier.PRIVATE);

Expand Down Expand Up @@ -1358,7 +1358,7 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
subMethod.getName() + "$$" + subMethodIndex + "$$handleHeader$$param"
+ inheritedParamIndex + "$" + subParamField.paramIndex,
Invocation.Builder.class,
Invocation.Builder.class, param.type);
Invocation.Builder.class, param.declaredType);
MethodCreator handleHeaderMethod = subContext.classCreator.getMethodCreator(
handleHeaderDescriptor).setModifiers(Modifier.PRIVATE);

Expand Down Expand Up @@ -1464,7 +1464,7 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
MethodDescriptor handleHeaderDescriptor = MethodDescriptor.ofMethod(subName,
subMethod.getName() + "$$" + subMethodIndex + "$$handleHeader$$" + paramIdx,
Invocation.Builder.class,
Invocation.Builder.class, param.type);
Invocation.Builder.class, param.declaredType);
MethodCreator handleHeaderMethod = subContext.classCreator.getMethodCreator(
handleHeaderDescriptor).setModifiers(Modifier.PRIVATE);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;

import javax.inject.Inject;
import javax.json.JsonArray;
Expand Down Expand Up @@ -143,6 +147,42 @@ public Response filters(@Context HttpHeaders headers, @RestHeader("filter-reques
return Response.ok().header("filter-request", header).build();
}

@GET
@Path("header-param-list")
public Object headerParamWithList(@HeaderParam("header") List list) {
return collectionToString(list);
}

@GET
@Path("rest-header-list")
public Object restHeaderWithList(@RestHeader List header) {
return collectionToString(header);
}

@GET
@Path("header-param-set")
public Object headerParamWithSet(@HeaderParam("header") Set list) {
return collectionToString(list);
}

@GET
@Path("rest-header-set")
public Object restHeaderWithSet(@RestHeader Set header) {
return collectionToString(header);
}

@GET
@Path("header-param-sorted-set")
public Object headerParamWithSortedSet(@HeaderParam("header") SortedSet list) {
return collectionToString(list);
}

@GET
@Path("rest-header-sorted-set")
public String restHeaderWithSortedSet(@RestHeader SortedSet header) {
return collectionToString(header);
}

@GET
@Path("feature-filters")
public Response featureFilters(@Context HttpHeaders headers) {
Expand Down Expand Up @@ -380,4 +420,8 @@ public String simplifiedResourceInfo(@Context SimpleResourceInfo simplifiedResou
public String bigDecimalConverter(BigDecimal val) {
return val.toString();
}

private String collectionToString(Collection list) {
return (String) list.stream().map(Object::toString).collect(Collectors.joining(", "));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
Expand Down Expand Up @@ -313,6 +315,34 @@ public void testHeaderParamInCtor() {
.then().body(Matchers.is(emptyString()));
}

@ParameterizedTest
@ValueSource(strings = {
"rest-header-list",
"rest-header-set",
"rest-header-sorted-set"
})
public void testRestHeaderUsingCollection(String path) {
RestAssured.with().header("header", "a", "b")
.get("/simple/" + path)
.then()
.statusCode(HttpStatus.SC_OK)
.body(Matchers.equalTo("a, b"));
}

@ParameterizedTest
@ValueSource(strings = {
"header-param-list",
"header-param-set",
"header-param-sorted-set"
})
public void testHeaderParamUsingCollection(String path) {
RestAssured.with().header("header", "a", "b")
.get("/simple/" + path)
.then()
.statusCode(HttpStatus.SC_OK)
.body(Matchers.equalTo("a, b"));
}

@Test
public void testFormMap() {
RestAssured
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
import static org.assertj.core.api.Assertions.assertThat;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
Expand Down Expand Up @@ -40,6 +47,15 @@ void testNullHeaders() {
assertThat(client.cookieSub("bar", null).send(null, "bar4", "dummy")).isEqualTo("bar:null:null:bar4:X-My-Header/dummy");
}

@Test
void testHeadersWithCollections() {
String expected = "a, b, c, d";
Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class);
assertThat(client.headersList(List.of("a", "b"), List.of("c", "d"))).isEqualTo(expected);
assertThat(client.headersSet(Set.of("a", "b"), Set.of("c", "d"))).isEqualTo(expected);
assertThat(client.headersSet(new TreeSet(List.of("a", "b")), new TreeSet(List.of("c", "d")))).isEqualTo(expected);
}

@Path("/")
@ApplicationScoped
public static class Resource {
Expand All @@ -51,12 +67,50 @@ public String returnHeaders(@HeaderParam("foo") String header, @HeaderParam("foo
return header + ":" + header2 + ":" + header3 + ":" + header4 + ":" + myHeaderName + "/"
+ headers.getHeaderString("X-My-Header");
}

@GET
@Path("/headers-list")
public String headersList(@HeaderParam("foo") List foo, @RestHeader List header) {
return joiningCollections(foo, header);
}

@GET
@Path("/headers-set")
public String headersSet(@HeaderParam("foo") Set foo, @RestHeader Set header) {
return joiningCollections(foo, header);
}

@GET
@Path("/headers-sorted-set")
public String headersSortedSet(@HeaderParam("foo") SortedSet foo, @RestHeader SortedSet header) {
return joiningCollections(foo, header);
}

private String joiningCollections(Collection... collections) {
List<String> allHeaders = new ArrayList<>();
for (Collection collection : collections) {
collection.forEach(v -> allHeaders.add((String) v));
}
return allHeaders.stream().collect(Collectors.joining(", "));
}
}

public interface Client {

@Path("/")
SubClient cookieSub(@HeaderParam("foo") String cookie, @HeaderParam("foo2") String cookie2);

@GET
@Path("/headers-list")
String headersList(@HeaderParam("foo") List foo, @RestHeader List header);

@GET
@Path("/headers-set")
String headersSet(@HeaderParam("foo") Set foo, @RestHeader Set header);

@GET
@Path("/headers-sorted-set")
String headersSortedSet(@HeaderParam("foo") SortedSet foo, @RestHeader SortedSet header);
}

public interface SubClient {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.rest.client.reactive.runtime;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -81,11 +82,13 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) {
}
}

private static List<String> castToListOfStrings(List<Object> values) {
private static List<String> castToListOfStrings(Collection<Object> values) {
List<String> result = new ArrayList<>();
for (Object value : values) {
if (value instanceof String) {
result.add((String) value);
} else if (value instanceof Collection) {
result.addAll(castToListOfStrings((Collection<Object>) value));
} else {
result.add(String.valueOf(value));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1362,13 +1362,28 @@ && isParameterContainerType(paramType.asClassType())) {
elementType = paramType.name().toString();
handleTemporalParam(builder, paramType.name(), anns, currentMethodInfo);
typeHandled = true;
} else if (paramType.name().equals(LIST) && (type == ParameterType.QUERY)) { // RESTEasy Classic handles the non-generic List type
} else if (paramType.name().equals(LIST) && (type == ParameterType.QUERY
|| type == ParameterType.HEADER)) { // RESTEasy Classic handles the non-generic List type
elementType = String.class.getName();
typeHandled = true;
builder.setSingle(false);
if (convertible) {
handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
}
} 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);
}
} 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);
}
} else if (paramType.kind() == Kind.ARRAY) {
ArrayType at = paramType.asArrayType();
typeHandled = true;
Expand Down

0 comments on commit 764f8d6

Please sign in to comment.