Skip to content

Commit

Permalink
Resteasy Reactive: Support use of optional with list/set/sortedset
Browse files Browse the repository at this point in the history
Support use when using query params with some collections. Example:

```java
@path("/list")
    @get
    public String sayHelloToList(@QueryParam("name") final Optional<List<String>> names) {
        return doSayHelloToCollection(names);
    }
```

fix quarkusio#23898
  • Loading branch information
Sgitario committed Mar 2, 2022
1 parent b966f1d commit 6e22f6e
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.quarkus.resteasy.test;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;

public class QueryParamTest {

private static final String HELLO = "hello ";
private static final String NOBODY = "nobody";
private static final String ALBERT = "albert";
private static final String AND = " and ";
private static final String JOSE = "jose";
private static final String NAME = "name";

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar.addClasses(MyResource.class));

@Test
public void testWithSomeNames() {
Assertions.assertEquals(HELLO + ALBERT + AND + JOSE, RestAssured.given().queryParam(NAME, ALBERT, JOSE).get("/greetings").asString());
}

@Path("/greetings")
public static class MyResource {

@GET
public String sayHello(@QueryParam("name") final Optional<List<String>> names) {
return HELLO + names.map(l -> l.stream().collect(Collectors.joining(AND)))
.orElse(NOBODY);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.quarkus.resteasy.reactive.server.test.simple;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.stream.Collectors;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

@Path("/optional-query/greetings")
public class OptionalQueryParamResource {

public static final String HELLO = "hello ";
public static final String NOBODY = "nobody";
public static final String AND = " and ";

@Path("/one")
@GET
public String sayHelloToValue(@QueryParam("name") final Optional<String> name) {
return HELLO + name.orElse(NOBODY);
}

@Path("/list")
@GET
public String sayHelloToList(@QueryParam("name") final Optional<List<String>> names) {
return doSayHelloToCollection(names);
}

@Path("/set")
@GET
public String sayHelloToSet(@QueryParam("name") final Optional<Set<String>> names) {
return doSayHelloToCollection(names);
}

@Path("/sortedset")
@GET
public String sayHelloToSortedSet(@QueryParam("name") final Optional<SortedSet<String>> names) {
return doSayHelloToCollection(names);
}

private String doSayHelloToCollection(final Optional<? extends Collection<String>> names) {
return HELLO + names.map(l -> l.stream().collect(Collectors.joining(AND)))
.orElse(NOBODY);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.quarkus.resteasy.reactive.server.test.simple;

import static io.quarkus.resteasy.reactive.server.test.simple.OptionalQueryParamResource.AND;
import static io.quarkus.resteasy.reactive.server.test.simple.OptionalQueryParamResource.HELLO;
import static io.quarkus.resteasy.reactive.server.test.simple.OptionalQueryParamResource.NOBODY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.emptyString;

Expand All @@ -8,6 +11,7 @@
import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
Expand Down Expand Up @@ -38,7 +42,7 @@ public JavaArchive get() {
FeatureResponseFilter.class, DynamicFeatureRequestFilterWithLowPriority.class,
TestFeature.class, TestDynamicFeature.class,
SubResource.class, RootAResource.class, RootBResource.class,
QueryParamResource.class, HeaderParamResource.class,
QueryParamResource.class, HeaderParamResource.class, OptionalQueryParamResource.class,
TestWriter.class, TestClass.class,
SimpleBeanParam.class, OtherBeanParam.class, FieldInjectedResource.class,
ParameterWithFromString.class, BeanParamSubClass.class, FieldInjectedSubClassResource.class,
Expand Down Expand Up @@ -432,4 +436,40 @@ public void testInterfaceResource() {
RestAssured.get("/iface")
.then().statusCode(200).body(Matchers.equalTo("Hello"));
}

@Test
public void testQueryParamWithOptionalSingleValue() {
// verify with empty
Assertions.assertEquals(HELLO + NOBODY, RestAssured.get("/optional-query/greetings/one").asString());
// verify with values
Assertions.assertEquals(HELLO + "albert", RestAssured.given().queryParam("name", "albert")
.get("/optional-query/greetings/one").asString());
}

@Test
public void testQueryParamWithOptionalList() {
// verify with empty
Assertions.assertEquals(HELLO + NOBODY, RestAssured.get("/optional-query/greetings/list").asString());
// verify with values
Assertions.assertEquals(HELLO + "albert" + AND + "jose", RestAssured.given().queryParam("name", "albert", "jose")
.get("/optional-query/greetings/list").asString());
}

@Test
public void testQueryParamWithOptionalSet() {
// verify with empty
Assertions.assertEquals(HELLO + NOBODY, RestAssured.get("/optional-query/greetings/set").asString());
// verify with values
Assertions.assertEquals(HELLO + "albert" + AND + "jose", RestAssured.given().queryParam("name", "albert", "jose")
.get("/optional-query/greetings/set").asString());
}

@Test
public void testQueryParamWithOptionalSortedSet() {
// verify with empty
Assertions.assertEquals(HELLO + NOBODY, RestAssured.get("/optional-query/greetings/sortedset").asString());
// verify with values
Assertions.assertEquals(HELLO + "albert" + AND + "jose", RestAssured.given().queryParam("name", "albert", "jose")
.get("/optional-query/greetings/sortedset").asString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,14 @@ && isContextType(paramType.asClassType())) {
typeHandled = true;
elementType = toClassName(pt.arguments().get(0), currentClassInfo, actualEndpointInfo, index);
if (convertible) {
handleOptionalParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
String genericElementType = null;
if (pt.arguments().get(0).kind() == Kind.PARAMETERIZED_TYPE) {
genericElementType = toClassName(pt.arguments().get(0).asParameterizedType().arguments().get(0),
currentClassInfo, actualEndpointInfo, index);
}

handleOptionalParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType,
genericElementType);
}
builder.setOptional(true);
} else if (convertible) {
Expand Down Expand Up @@ -1227,7 +1234,7 @@ protected void handleSortedSetParam(Map<String, String> existingConverters, Stri
}

protected void handleOptionalParam(Map<String, String> existingConverters, String errorLocation,
boolean hasRuntimeConverters, PARAM builder, String elementType) {
boolean hasRuntimeConverters, PARAM builder, String elementType, String genericElementType) {
}

protected void handleSetParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.JSONP_JSON_STRING;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.JSONP_JSON_STRUCTURE;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.JSONP_JSON_VALUE;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.LIST;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.LOCAL_DATE;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.LOCAL_DATE_TIME;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.LOCAL_TIME;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI_VALUED_MAP;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OFFSET_DATE_TIME;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OFFSET_TIME;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.SET;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.SORTED_SET;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.ZONED_DATE_TIME;

import java.util.ArrayList;
Expand Down Expand Up @@ -326,9 +329,29 @@ protected void handleSortedSetParam(Map<String, String> existingConverters, Stri
}

protected void handleOptionalParam(Map<String, String> existingConverters, String errorLocation,
boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) {
ParameterConverterSupplier converter = extractConverter(elementType, index,
existingConverters, errorLocation, hasRuntimeConverters);
boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType, String genericElementType) {
ParameterConverterSupplier converter = null;

if (genericElementType != null) {
ParameterConverterSupplier genericTypeConverter = extractConverter(genericElementType, index, existingConverters,
errorLocation, hasRuntimeConverters);
if (LIST.toString().equals(elementType)) {
converter = new ListConverter.ListSupplier(genericTypeConverter);
builder.setSingle(false);
} else if (SET.toString().equals(elementType)) {
converter = new SetConverter.SetSupplier(genericTypeConverter);
builder.setSingle(false);
} else if (SORTED_SET.toString().equals(elementType)) {
converter = new SortedSetConverter.SortedSetSupplier(genericTypeConverter);
builder.setSingle(false);
}
}

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.setConverter(new OptionalConverter.OptionalSupplier(converter));
}

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

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Optional;
import org.jboss.resteasy.reactive.server.model.ParamConverterProviders;

Expand All @@ -18,7 +19,14 @@ public Object convert(Object parameter) {
if (parameter == null) {
return Optional.empty();
} else if (delegate != null) {
return Optional.ofNullable(delegate.convert(parameter));
Object converted = delegate.convert(parameter);
if (converted != null
&& converted instanceof Collection
&& ((Collection) converted).isEmpty()) {
return Optional.empty();
} else {
return Optional.ofNullable(converted);
}
} else {
return Optional.of(parameter);
}
Expand Down

0 comments on commit 6e22f6e

Please sign in to comment.