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 b732109
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 10 deletions.
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 b732109

Please sign in to comment.