Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce @Separator annotation for query parameters #29550

Merged
merged 1 commit into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
geoand marked this conversation as resolved.
Show resolved Hide resolved

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