Skip to content

Commit

Permalink
Introduce @Separator annotation for query parameters
Browse files Browse the repository at this point in the history
This annotation allows to actually turn a String into a List<String>
automatically for each query param value

Resolves: quarkusio#29528
  • Loading branch information
geoand committed Nov 29, 2022
1 parent 6a2b1b4 commit f695369
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 10 deletions.
4 changes: 4 additions & 0 deletions docs/src/main/asciidoc/resteasy-reactive-migration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ The following table matches the legacy RESTEasy annotations with the new RESTEas
|`org.jboss.resteasy.reactive.RestStreamElementType`
|

|`org.jboss.resteasy.annotations.Separator`
|`org.jboss.resteasy.reactive.Separator`
|

|===

NOTE: The previous table does not include the `org.jboss.resteasy.annotations.Form` annotation because there is no RESTEasy Reactive specific replacement for it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ public static void generate(Map<DotName, MethodInfo> resourcesThatNeedCustomProd
ResultHandle quarkusRestContextHandle = m.invokeVirtualMethod(getContextMethodCreator.getMethodDescriptor(),
m.getThis());
ResultHandle extractorHandle = m.newInstance(
MethodDescriptor.ofConstructor(QueryParamExtractor.class, String.class, boolean.class, boolean.class),
m.getMethodParam(0), m.load(true), m.load(false));
MethodDescriptor.ofConstructor(QueryParamExtractor.class, String.class, boolean.class, boolean.class,
String.class),
m.getMethodParam(0), m.load(true), m.load(false), m.loadNull());
ResultHandle resultHandle = m.invokeVirtualMethod(MethodDescriptor.ofMethod(QueryParamExtractor.class,
"extractParameter", Object.class, ResteasyReactiveRequestContext.class), extractorHandle,
quarkusRestContextHandle);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.quarkus.resteasy.reactive.server.test.simple;

import static io.restassured.RestAssured.*;

import java.util.List;

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

import org.hamcrest.Matchers;
import org.jboss.resteasy.reactive.RestQuery;
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.Separator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class SeparatorQueryParamTest {

@RegisterExtension
static QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(HelloResource.class));

@Test
public void noQueryParams() {
get("/hello")
.then()
.statusCode(200)
.body(Matchers.equalTo("hello world"))
.header("x-size", "0");
}

@Test
public void singleQueryParam() {
get("/hello?name=foo")
.then()
.statusCode(200)
.body(Matchers.equalTo("hello foo"))
.header("x-size", "1");
}

@Test
public void multipleQueryParams() {
get("/hello?name=foo,bar&name=one,two,three&name=yolo")
.then()
.statusCode(200)
.body(Matchers.equalTo("hello foo bar one two three yolo"))
.header("x-size", "6");
}

@Path("hello")
public static class HelloResource {

@GET
public RestResponse<String> hello(@RestQuery("name") @Separator(",") List<String> names) {
int size = names.size();
String body = "";
if (names.isEmpty()) {
body = "hello world";
} else {
body = "hello " + String.join(" ", names);
}
return RestResponse.ResponseBuilder.ok(body).header("x-size", size).build();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas
elementType, declaredTypes.getDeclaredType(), declaredTypes.getDeclaredUnresolvedType(), signature, type,
single,
defaultValue, parameterResult.isObtainedAsCollection(), parameterResult.isOptional(), encoded,
mimePart, partFileName);
mimePart, partFileName, null);
}

private String getPartFileName(Map<DotName, AnnotationInstance> annotations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1500,6 +1500,15 @@ protected String getPartMime(Map<DotName, AnnotationInstance> annotations) {
return mimeType;
}

protected String getSeparator(Map<DotName, AnnotationInstance> annotations) {
AnnotationInstance separator = annotations.get(ResteasyReactiveDotNames.SEPARATOR);
String result = null;
if (separator != null && separator.value() != null) {
result = separator.value().asString();
}
return result;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public static abstract class Builder<T extends EndpointIndexer<T, ?, METHOD>, B extends Builder<T, B, METHOD>, METHOD extends ResourceMethod> {
private Function<String, BeanFactory<Object>> factoryCreator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.RestSseElementType;
import org.jboss.resteasy.reactive.RestStreamElementType;
import org.jboss.resteasy.reactive.Separator;
import org.reactivestreams.Publisher;

import io.smallrye.common.annotation.Blocking;
Expand Down Expand Up @@ -136,6 +137,7 @@ public final class ResteasyReactiveDotNames {
public static final DotName MULTI_PART_FORM_PARAM = DotName.createSimple(MultipartForm.class.getName());
public static final DotName PART_TYPE_NAME = DotName.createSimple(PartType.class.getName());
public static final DotName PART_FILE_NAME = DotName.createSimple(PartFilename.class.getName());
public static final DotName SEPARATOR = DotName.createSimple(Separator.class.getName());
public static final DotName REST_MATRIX_PARAM = DotName.createSimple(RestMatrix.class.getName());
public static final DotName REST_COOKIE_PARAM = DotName.createSimple(RestCookie.class.getName());
public static final DotName GET = DotName.createSimple(javax.ws.rs.GET.class.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.jboss.resteasy.reactive;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;

/**
* When used on a {@link List} parameter annotated with {@link RestQuery}, RESTEasy Reactive will split the value of the
* parameter (using the value of the annotation) and populate the list with those values.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER })
public @interface Separator {

String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class MethodParameter {
private boolean isObtainedAsCollection;
public String mimeType;
public String partFileName;
public String separator;

public MethodParameter() {
}
Expand All @@ -33,7 +34,7 @@ public MethodParameter(String name, String type, String declaredType, String dec
ParameterType parameterType,
boolean single,
String defaultValue, boolean isObtainedAsCollection, boolean optional, boolean encoded,
String mimeType, String partFileName) {
String mimeType, String partFileName, String separator) {
this.name = name;
this.type = type;
this.declaredType = declaredType;
Expand All @@ -47,6 +48,7 @@ public MethodParameter(String name, String type, String declaredType, String dec
this.encoded = encoded;
this.mimeType = mimeType;
this.partFileName = partFileName;
this.separator = separator;
}

public String getName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,12 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas
ParameterConverterSupplier converter = parameterResult.getConverter();
DeclaredTypes declaredTypes = getDeclaredTypes(paramType, currentClassInfo, actualEndpointInfo);
String mimeType = getPartMime(parameterResult.getAnns());
String separator = getSeparator(parameterResult.getAnns());
return new ServerMethodParameter(name,
elementType, declaredTypes.getDeclaredType(), declaredTypes.getDeclaredUnresolvedType(),
type, single, signature,
converter, defaultValue, parameterResult.isObtainedAsCollection(), parameterResult.isOptional(), encoded,
parameterResult.getCustomParameterExtractor(), mimeType);
parameterResult.getCustomParameterExtractor(), mimeType, separator);
}

protected void handleOtherParam(Map<String, String> existingConverters, String errorLocation, boolean hasRuntimeConverters,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,47 @@
package org.jboss.resteasy.reactive.server.core.parameters;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;

public class QueryParamExtractor implements ParameterExtractor {

private final String name;
private final boolean single;
private final boolean encoded;
private final String separator;

public QueryParamExtractor(String name, boolean single, boolean encoded) {
public QueryParamExtractor(String name, boolean single, boolean encoded, String separator) {
this.name = name;
this.single = single;
this.encoded = encoded;
this.separator = separator;
}

@Override
@SuppressWarnings("unchecked")
public Object extractParameter(ResteasyReactiveRequestContext context) {
return context.getQueryParameter(name, single, encoded);
Object queryParameter = context.getQueryParameter(name, single, encoded);
if (separator != null) {
if (queryParameter instanceof List) { // it's List<String>
List<String> list = (List<String>) queryParameter;
List<String> result = new ArrayList<>(list.size());
for (int i = 0; i < list.size(); i++) {
String[] parts = list.get(i).split(separator);
result.addAll(Arrays.asList(parts));
}
queryParameter = result;
} else if (queryParameter instanceof String) {
List<String> result = new ArrayList<>(1);
String[] parts = ((String) queryParameter).split(separator);
result.addAll(Arrays.asList(parts));
queryParameter = result;
} else {
// can't really happen
}
}
return queryParameter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ public ParameterExtractor parameterExtractor(Map<String, Integer> pathParameterI
case ASYNC_RESPONSE:
return new AsyncResponseExtractor();
case QUERY:
extractor = new QueryParamExtractor(param.name, param.isSingle(), param.encoded);
extractor = new QueryParamExtractor(param.name, param.isSingle(), param.encoded, param.separator);
return extractor;
case BODY:
return new BodyParamExtractor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ public ServerMethodParameter(String name, String type, String declaredType, Stri
ParameterConverterSupplier converter, String defaultValue, boolean obtainedAsCollection, boolean optional,
boolean encoded,
ParameterExtractor customParameterExtractor,
String mimeType) {
String mimeType, String separator) {
super(name, type, declaredType, declaredUnresolvedType, signature, parameterType, single, defaultValue,
obtainedAsCollection, optional,
encoded, mimeType, null /* not useful for server params */);
encoded, mimeType, null /* not useful for server params */, separator);
this.converter = converter;
this.customParameterExtractor = customParameterExtractor;
}
Expand Down

0 comments on commit f695369

Please sign in to comment.