Skip to content

Commit 783dd1d

Browse files
committed
Add support for multi-value query parameters
Signed-off-by: Daeho Kwon <trewq231@naver.com>
1 parent 153029d commit 783dd1d

File tree

3 files changed

+43
-17
lines changed

3 files changed

+43
-17
lines changed

core/src/main/java/com/linecorp/armeria/internal/server/annotation/AnnotatedValueResolver.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import com.google.common.base.MoreObjects;
6565
import com.google.common.base.Splitter;
6666
import com.google.common.collect.ImmutableList;
67+
import com.google.common.collect.ImmutableSet;
6768
import com.google.common.collect.Iterables;
6869
import com.google.common.primitives.Primitives;
6970

@@ -607,10 +608,12 @@ private static AnnotatedValueResolver ofQueryParamMap(String name,
607608
final Class<?> rawValueType = ClassUtil.typeToClass(valueType);
608609
assert rawValueType != null;
609610

610-
if (valueType instanceof ParameterizedType && !List.class.isAssignableFrom(rawValueType)) {
611+
if (valueType instanceof ParameterizedType && !(rawValueType == Iterable.class ||
612+
rawValueType == List.class ||
613+
rawValueType == Collection.class ||
614+
rawValueType == Set.class)) {
611615
throw new IllegalArgumentException(
612-
"Invalid Map value type: " + rawValueType +
613-
". Only List<?> is supported for multi-value query parameters.");
616+
"Invalid Map value type: " + rawValueType);
614617
}
615618

616619
final BiFunction<AnnotatedValueResolver, ResolverContext, Object> biFunction;
@@ -626,6 +629,17 @@ private static AnnotatedValueResolver ofQueryParamMap(String name,
626629
.addAll(replacement)
627630
.build()
628631
));
632+
} else if (Set.class.isAssignableFrom(rawValueType)) {
633+
biFunction = (resolver, ctx) -> ctx.queryParams().stream()
634+
.collect(toImmutableMap(
635+
Entry::getKey,
636+
e -> ImmutableSet.of(e.getValue()),
637+
(existing, replacement) ->
638+
ImmutableSet.<String>builder()
639+
.addAll(existing)
640+
.addAll(replacement)
641+
.build()
642+
));
629643
} else {
630644
biFunction = (resolver, ctx) -> ctx.queryParams().stream()
631645
.collect(toImmutableMap(

core/src/test/java/com/linecorp/armeria/internal/server/annotation/AnnotatedServiceTest.java

+18-5
Original file line numberDiff line numberDiff line change
@@ -640,8 +640,16 @@ public String map(RequestContext ctx, @Param Map<String, Object> map) {
640640
.collect(Collectors.joining(", "));
641641
}
642642

643-
@Get("/param/multiValueMap")
644-
public String multiValueMap(RequestContext ctx, @Param Map<String, List<Object>> map) {
643+
@Get("/param/listMap")
644+
public String listMap(RequestContext ctx, @Param Map<String, List<Object>> map) {
645+
validateContext(ctx);
646+
return map.isEmpty() ? "empty" : map.entrySet().stream()
647+
.map(entry -> entry.getKey() + '=' + entry.getValue())
648+
.collect(Collectors.joining(", "));
649+
}
650+
651+
@Get("/param/setMap")
652+
public String setMap(RequestContext ctx, @Param Map<String, Set<Object>> map) {
645653
validateContext(ctx);
646654
return map.isEmpty() ? "empty" : map.entrySet().stream()
647655
.map(entry -> entry.getKey() + '=' + entry.getValue())
@@ -1089,10 +1097,15 @@ void testParam() throws Exception {
10891097
"key1=value1, key2=value2");
10901098
testBody(hc, get("/7/param/map"), "empty");
10911099

1092-
// Case all query parameters test multi value map
1093-
testBody(hc, get("/7/param/multiValueMap?key1=value1&key1=value2&key2=value1&key2=value2"),
1100+
// Case all query parameters test multi value map of List
1101+
testBody(hc, get("/7/param/listMap?key1=value1&key1=value2&key2=value1&key2=value2"),
10941102
"key1=[value1, value2], key2=[value1, value2]");
1095-
testBody(hc, get("/7/param/multiValueMap"), "empty");
1103+
testBody(hc, get("/7/param/listMap"), "empty");
1104+
1105+
// Case all query parameters test multi value map of Set
1106+
testBody(hc, get("/7/param/setMap?key1=value1&key1=value1&key2=value2&key2=value2"),
1107+
"key1=[value1], key2=[value2]");
1108+
testBody(hc, get("/7/param/setMap"), "empty");
10961109
}
10971110
}
10981111

core/src/test/java/com/linecorp/armeria/internal/server/annotation/AnnotatedValueResolverTest.java

+8-9
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,14 @@ class AnnotatedValueResolverTest {
108108
"value3",
109109
"value2");
110110

111+
static final Set<String> queryParamMaps = ImmutableSet.of("queryParamMap",
112+
"queryParamListMap",
113+
"queryParamSetMap");
114+
111115
static final ResolverContext resolverContext;
112116
static final ServiceRequestContext context;
113117
static final HttpRequest request;
114118
static final RequestHeaders originalHeaders;
115-
static final String QUERY_PARAM_MAP = "queryParamMap";
116-
static final String QUERY_PARAM_MULTI_VALUE_MAP = "queryParamMultiValueMap";
117119
static Map<String, AttributeKey<?>> successExpectAttrKeys;
118120
static Map<String, AttributeKey<?>> failExpectAttrKeys;
119121

@@ -374,8 +376,7 @@ private static void testResolver(AnnotatedValueResolver resolver) {
374376
}
375377
}
376378
} else {
377-
if (QUERY_PARAM_MAP.equals(resolver.httpElementName()) ||
378-
QUERY_PARAM_MULTI_VALUE_MAP.equals(resolver.httpElementName())) {
379+
if (queryParamMaps.contains(resolver.httpElementName())) {
379380
assertThat(resolver.defaultValue()).isNull();
380381
} else {
381382
assertThat(resolver.defaultValue()).isNotNull();
@@ -387,8 +388,7 @@ private static void testResolver(AnnotatedValueResolver resolver) {
387388
.isEqualTo(resolver.elementType());
388389
} else if (resolver.shouldWrapValueAsOptional()) {
389390
assertThat(value).isEqualTo(Optional.of(resolver.defaultValue()));
390-
} else if (QUERY_PARAM_MAP.equals(resolver.httpElementName()) ||
391-
QUERY_PARAM_MULTI_VALUE_MAP.equals(resolver.httpElementName())) {
391+
} else if (queryParamMaps.contains(resolver.httpElementName())) {
392392
assertThat(value).isNotNull();
393393
assertThat(value).isInstanceOf(Map.class);
394394
assertThat((Map<?, ?>) value).size()
@@ -471,7 +471,8 @@ void method1(@Param String var1,
471471
@Param @Default List<String> emptyParam3,
472472
@Param @Default List<Integer> emptyParam4,
473473
@Param Map<String, Object> queryParamMap,
474-
@Param Map<String, List<Object>> queryParamMultiValueMap,
474+
@Param Map<String, List<Object>> queryParamListMap,
475+
@Param Map<String, Set<Object>> queryParamSetMap,
475476
@Header List<String> header1,
476477
@Header("header1") Optional<List<ValueEnum>> optionalHeader1,
477478
@Header String header2,
@@ -548,8 +549,6 @@ void time(@Param @Default("PT20.345S") Duration duration,
548549
}
549550

550551
static class InvalidMultiValueMapService {
551-
void invalidParamWithMapOfSet(@Param Map<String, Set<String>> param) {}
552-
553552
void invalidParamWithMapOfMap(@Param Map<String, Map<String, String>> param) {}
554553
}
555554

0 commit comments

Comments
 (0)