Skip to content

Commit 6efb809

Browse files
authored
Merge pull request #167 from SentryMan/multival
Support Multi-Value QueryParam/Headers/Cookies
2 parents 797ec6c + ea3ebb1 commit 6efb809

File tree

20 files changed

+534
-52
lines changed

20 files changed

+534
-52
lines changed

http-api/src/main/java/io/avaje/http/api/Default.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525
@Retention(value = RUNTIME)
2626
public @interface Default {
2727

28-
/**
29-
* The default value.
30-
*/
31-
String value();
32-
28+
/** The default values. */
29+
String[] value();
3330
}

http-api/src/main/java/io/avaje/http/api/PathTypeConversion.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import java.math.BigDecimal;
44
import java.time.*;
5+
import java.util.List;
6+
import java.util.Set;
57
import java.util.UUID;
8+
import java.util.function.Function;
9+
import java.util.stream.Collectors;
610

711
/**
812
* Helper type conversion methods.
@@ -11,6 +15,8 @@
1115
*/
1216
public final class PathTypeConversion {
1317

18+
private PathTypeConversion() {}
19+
1420
/**
1521
* Return the value if non-null and otherwise the default value.
1622
*
@@ -22,6 +28,10 @@ public static String withDefault(String value, String defaultValue) {
2228
return value != null ? value : defaultValue;
2329
}
2430

31+
public static List<String> withDefault(List<String> value, List<String> defaultValue) {
32+
return value != null && !value.isEmpty() ? value : defaultValue;
33+
}
34+
2535
/**
2636
* Check for null for a required property throwing RequiredArgumentException
2737
* if the value is null.
@@ -41,9 +51,15 @@ private static void checkNull(String value) {
4151
}
4252
}
4353

44-
/**
45-
* Convert to int.
46-
*/
54+
public static <T> List<T> list(Function<String, T> func, List<String> params) {
55+
return params.stream().map(func).collect(Collectors.toList());
56+
}
57+
58+
public static <T> Set<T> set(Function<String, T> func, List<String> params) {
59+
return params.stream().map(func).collect(Collectors.toSet());
60+
}
61+
62+
/** Convert to int. */
4763
public static int asInt(String value) {
4864
checkNull(value);
4965
try {

http-api/src/test/java/io/avaje/http/api/PathTypeConversionTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.time.LocalDateTime;
88
import java.time.LocalTime;
99
import java.time.OffsetDateTime;
10+
import java.util.List;
1011
import java.util.UUID;
1112

1213
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
@@ -19,7 +20,9 @@ class PathTypeConversionTest {
1920
void withDefault() {
2021
assertEquals("a", PathTypeConversion.withDefault("a", "myVal"));
2122
assertEquals("", PathTypeConversion.withDefault("", "myVal"));
22-
assertEquals("myVal", PathTypeConversion.withDefault(null, "myVal"));
23+
String nully = null;
24+
assertEquals("myVal", PathTypeConversion.withDefault(nully, "myVal"));
25+
assertThat(PathTypeConversion.withDefault(List.of(), List.of("myVal"))).anyMatch(s -> "myVal".equals(s));
2326
}
2427

2528
@Test

http-generator-core/src/main/java/io/avaje/http/generator/core/ElementReader.java

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static io.avaje.http.generator.core.ParamType.RESPONSE_HANDLER;
44

5+
import java.util.List;
6+
import java.util.Objects;
57
import java.util.Optional;
68

79
import javax.lang.model.element.Element;
@@ -24,15 +26,18 @@ public class ElementReader {
2426
private final boolean formMarker;
2527
private final boolean contextType;
2628
private final boolean useValidation;
29+
private final boolean specialParam;
2730

2831
private String paramName;
2932
private ParamType paramType;
3033
private String matrixParamName;
3134
private boolean impliedParamType;
32-
private String paramDefault;
35+
private List<String> paramDefault;
3336

3437
private boolean notNullKotlin;
35-
//private boolean notNullJavax;
38+
private boolean isParamCollection;
39+
private boolean isParamMap;
40+
// private boolean notNullJavax;
3641

3742
ElementReader(Element element, ProcessingContext ctx, ParamType defaultType, boolean formMarker) {
3843
this(element, null, Util.typeDef(element.asType()), ctx, defaultType, formMarker);
@@ -46,18 +51,14 @@ public class ElementReader {
4651
this.shortType = Util.shortName(rawType);
4752
this.contextType = ctx.platform().isContextType(rawType);
4853

49-
if (type != null
50-
&& (defaultType == ParamType.FORMPARAM || defaultType == ParamType.QUERYPARAM)
51-
&& Optional.ofNullable(ctx.typeElement(type.mainType()))
52-
.map(TypeElement::getKind)
53-
.filter(ElementKind.ENUM::equals)
54-
.isPresent()) {
54+
this.specialParam =
55+
defaultType == ParamType.FORMPARAM
56+
|| defaultType == ParamType.QUERYPARAM
57+
|| defaultType == ParamType.HEADER
58+
|| defaultType == ParamType.COOKIE;
5559

56-
this.typeHandler = TypeMap.enumParamHandler(type);
57-
} else {
60+
typeHandler = initTypeHandler();
5861

59-
this.typeHandler = TypeMap.get(rawType);
60-
}
6162
this.formMarker = formMarker;
6263
this.varName = element.getSimpleName().toString();
6364
this.snakeName = Util.snakeCase(varName);
@@ -71,6 +72,50 @@ public class ElementReader {
7172
}
7273
}
7374

75+
TypeHandler initTypeHandler() {
76+
77+
if (specialParam) {
78+
79+
final var typeOp =
80+
Optional.ofNullable(type).or(() -> Optional.of(UType.parse(element.asType())));
81+
82+
final var mainTypeEnum =
83+
typeOp
84+
.flatMap(t -> Optional.ofNullable(ctx.typeElement(t.mainType())))
85+
.map(TypeElement::getKind)
86+
.filter(ElementKind.ENUM::equals)
87+
.isPresent();
88+
89+
final var isCollection =
90+
typeOp
91+
.filter(t -> t.isGeneric() && !t.mainType().startsWith("java.util.Map"))
92+
.isPresent();
93+
94+
final var isMap =
95+
!isCollection && typeOp.filter(t -> t.mainType().startsWith("java.util.Map")).isPresent();
96+
97+
if (mainTypeEnum) {
98+
return TypeMap.enumParamHandler(type);
99+
} else if (isCollection) {
100+
this.isParamCollection = true;
101+
final var isEnumCollection =
102+
typeOp
103+
.flatMap(t -> Optional.ofNullable(ctx.typeElement(t.param0())))
104+
.map(TypeElement::getKind)
105+
.filter(ElementKind.ENUM::equals)
106+
.isPresent();
107+
108+
return TypeMap.collectionHandler(typeOp.orElseThrow(), isEnumCollection);
109+
} else if (isMap) {
110+
this.isParamMap = true;
111+
112+
return new TypeMap.StringHandler();
113+
}
114+
}
115+
116+
return TypeMap.get(rawType);
117+
}
118+
74119
private boolean useValidation() {
75120
if (typeHandler != null) {
76121
return false;
@@ -182,10 +227,8 @@ private String handlerShortType() {
182227

183228
void addImports(ControllerReader bean) {
184229
if (typeHandler != null) {
185-
String importType = typeHandler.importType();
186-
if (importType != null) {
187-
bean.addImportType(rawType);
188-
}
230+
typeHandler.importTypes().stream().filter(Objects::nonNull).forEach(bean::addImportType);
231+
189232
} else {
190233
bean.addImportType(rawType);
191234
}
@@ -203,7 +246,7 @@ void writeParamName(Append writer) {
203246
* Build the OpenAPI documentation for this parameter.
204247
*/
205248
void buildApiDocumentation(MethodDocBuilder methodDoc) {
206-
if (!isPlatformContext()) {
249+
if (!isPlatformContext() && !isParamMap) {
207250
new MethodParamDocBuilder(methodDoc, this).build();
208251
}
209252
}
@@ -285,19 +328,26 @@ private boolean setValue(Append writer, PathSegments segments, String shortType)
285328
// this is a body (POST, PATCH)
286329
writer.append(ctx.platform().bodyAsClass(type));
287330

288-
} else {
331+
} else if (isParamCollection && specialParam) {
289332
if (hasParamDefault()) {
290-
ctx.platform().writeReadParameter(writer, paramType, paramName, paramDefault);
333+
ctx.platform().writeReadCollectionParameter(writer, paramType, paramName, paramDefault);
291334
} else {
292-
boolean checkNull = notNullKotlin || (paramType == ParamType.FORMPARAM && typeHandler.isPrimitive());
293-
if (checkNull) {
294-
writer.append("checkNull(");
295-
}
296-
ctx.platform().writeReadParameter(writer, paramType, paramName);
297-
//writer.append("ctx.%s(\"%s\")", paramType, paramName);
298-
if (checkNull) {
299-
writer.append(", \"%s\")", paramName);
300-
}
335+
ctx.platform().writeReadCollectionParameter(writer, paramType, paramName);
336+
}
337+
} else if (isParamMap) {
338+
ctx.platform().writeReadMapParameter(writer, paramType);
339+
} else if (hasParamDefault()) {
340+
ctx.platform().writeReadParameter(writer, paramType, paramName, paramDefault.get(0));
341+
} else {
342+
final var checkNull =
343+
notNullKotlin || (paramType == ParamType.FORMPARAM && typeHandler.isPrimitive());
344+
if (checkNull) {
345+
writer.append("checkNull(");
346+
}
347+
ctx.platform().writeReadParameter(writer, paramType, paramName);
348+
// writer.append("ctx.%s(\"%s\")", paramType, paramName);
349+
if (checkNull) {
350+
writer.append(", \"%s\")", paramName);
301351
}
302352
}
303353

http-generator-core/src/main/java/io/avaje/http/generator/core/PlatformAdapter.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,19 @@ public interface PlatformAdapter {
4545

4646
void writeReadParameter(Append writer, ParamType paramType, String paramName);
4747

48-
void writeReadParameter(Append writer, ParamType paramType, String paramName, String paramDefault);
48+
void writeReadParameter(
49+
Append writer, ParamType paramType, String paramName, String paramDefault);
50+
51+
default void writeReadMapParameter(Append writer, ParamType paramType) {
52+
throw new UnsupportedOperationException("Unsupported Map Parameter");
53+
}
54+
55+
default void writeReadCollectionParameter(Append writer, ParamType paramType, String paramName) {
56+
throw new UnsupportedOperationException("Unsupported MultiValue Parameter");
57+
}
58+
59+
default void writeReadCollectionParameter(
60+
Append writer, ParamType paramType, String paramName, List<String> paramDefault) {
61+
throw new UnsupportedOperationException("Unsupported MultiValue Parameter");
62+
}
4963
}

http-generator-core/src/main/java/io/avaje/http/generator/core/TypeHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.avaje.http.generator.core;
22

3+
import java.util.List;
4+
35
/**
46
* Handles type conversion for path and query parameters.
57
*/
@@ -18,7 +20,7 @@ interface TypeHandler {
1820
/**
1921
* The type for adding to imports.
2022
*/
21-
String importType();
23+
List<String> importTypes();
2224

2325
/**
2426
* The short name.

0 commit comments

Comments
 (0)