Skip to content

Commit 62218b4

Browse files
authored
Fix PR #550 Optional type wrapping of query parameters (#555)
- Adds tests that were missing in HelloControllerTest that actually exercises the feature - Fixes the controller URLs removing incorrect {myOptional} from urls - Fixes PathTypeConversion to handle null values - Adds missing javadoc on new public methods of PathTypeConversion - Adds some useful unit tests for TypeMap.OptionalHandler
1 parent b5fe961 commit 62218b4

File tree

5 files changed

+152
-28
lines changed

5 files changed

+152
-28
lines changed

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

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,42 @@ private static void checkNull(String value) {
5353
}
5454
}
5555

56-
public static <T> List<T> list(Function<String, T> func, List<String> params) {
57-
return params.stream().map(func).collect(Collectors.toList());
56+
/**
57+
* Return the list of parameters using a type conversion function.
58+
*/
59+
public static <T> List<T> list(Function<String, T> typeConversion, List<String> params) {
60+
return params.stream().map(typeConversion).collect(Collectors.toList());
5861
}
5962

60-
public static <T> Set<T> set(Function<String, T> func, List<String> params) {
61-
return params.stream().map(func).collect(Collectors.toSet());
63+
/**
64+
* Return the parameters as a Set using a type conversion function.
65+
*/
66+
public static <T> Set<T> set(Function<String, T> typeConversion, List<String> params) {
67+
return params.stream().map(typeConversion).collect(Collectors.toSet());
6268
}
6369

64-
public static <T> Optional<T> optional(Function<String, T> func, String value) {
65-
return Optional.ofNullable(func.apply(value));
70+
/**
71+
* Return an Optional taking a type conversion function and string value.
72+
*
73+
* @param typeConversion The type conversion function
74+
* @param value The nullable value
75+
* @param <T> The ty
76+
* @return The Optional typed value
77+
*/
78+
public static <T> Optional<T> optional(Function<String, T> typeConversion, String value) {
79+
return value == null ? Optional.empty() : Optional.ofNullable(typeConversion.apply(value));
6680
}
6781

68-
/** Convert to int. */
82+
/**
83+
* Return an Optional for a nullable String value.
84+
*/
85+
public static Optional<String> optional(String value) {
86+
return Optional.ofNullable(value);
87+
}
88+
89+
/**
90+
* Convert to int.
91+
*/
6992
public static int asInt(String value) {
7093
checkNull(value);
7194
try {
@@ -75,7 +98,9 @@ public static int asInt(String value) {
7598
}
7699
}
77100

78-
/** Convert to enum. */
101+
/**
102+
* Convert to enum.
103+
*/
79104
@SuppressWarnings({"rawtypes"})
80105
public static <T> Enum asEnum(Class<T> clazz, String value) {
81106
checkNull(value);
@@ -86,6 +111,9 @@ public static <T> Enum asEnum(Class<T> clazz, String value) {
86111
}
87112
}
88113

114+
/**
115+
* Convert to an Enum of the given type.
116+
*/
89117
@SuppressWarnings({"unchecked", "rawtypes"})
90118
public static <T> Enum convertEnum(Class<T> clazz, String value) {
91119
try {
@@ -260,7 +288,9 @@ public static Integer asInteger(String value) {
260288
}
261289
}
262290

263-
/** Convert to enum. */
291+
/**
292+
* Convert to enum of the given type.
293+
*/
264294
@SuppressWarnings({"rawtypes"})
265295
public static <T> Enum toEnum(Class<T> clazz, String value) {
266296
if (isNullOrEmpty(value)) {

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,8 @@ static class CollectionHandler implements TypeHandler {
347347
private String toMethod;
348348

349349
CollectionHandler(TypeHandler handler, boolean set, boolean isEnum) {
350-
351350
this.importTypes = new ArrayList<>(handler.importTypes());
352-
importTypes.add("io.avaje.http.api.PathTypeConversion");
351+
this.importTypes.add("io.avaje.http.api.PathTypeConversion");
353352
this.shortName = handler.shortName();
354353
this.toMethod =
355354
(set ? "set" : "list")
@@ -369,7 +368,6 @@ public boolean isPrimitive() {
369368

370369
@Override
371370
public List<String> importTypes() {
372-
373371
return importTypes;
374372
}
375373

@@ -393,21 +391,24 @@ static class OptionalHandler implements TypeHandler {
393391

394392
private final List<String> importTypes;
395393
private final String shortName;
396-
private String toMethod;
394+
private final String toMethod;
397395

398396
OptionalHandler(TypeHandler handler, boolean isEnum) {
399-
400397
this.importTypes = new ArrayList<>(handler.importTypes());
401-
importTypes.add("io.avaje.http.api.PathTypeConversion");
398+
this.importTypes.add("io.avaje.http.api.PathTypeConversion");
402399
this.shortName = handler.shortName();
403-
this.toMethod =
404-
"optional("
405-
+ (isEnum
406-
? "qp -> " + handler.toMethod() + " qp)"
407-
: "PathTypeConversion::as" + shortName)
408-
+ ", ";
400+
this.toMethod = buildToMethod(handler, isEnum);
401+
}
409402

410-
this.toMethod = toMethod.replace("PathTypeConversion::asString", "Object::toString");
403+
static String buildToMethod(TypeHandler handler, boolean isEnum) {
404+
if (isEnum) {
405+
return "optional(qp -> " + handler.toMethod() + " qp), ";
406+
}
407+
if ("String".equals(handler.shortName())) {
408+
return "optional(";
409+
} else {
410+
return "optional(PathTypeConversion::as" + handler.shortName() + ", ";
411+
}
411412
}
412413

413414
@Override
@@ -417,7 +418,6 @@ public boolean isPrimitive() {
417418

418419
@Override
419420
public List<String> importTypes() {
420-
421421
return importTypes;
422422
}
423423

http-generator-core/src/test/java/io/avaje/http/generator/core/TypeMapTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,25 @@ void get_BigInt() {
6969
assertThat(handler.asMethod()).isEqualTo("asBigInteger(");
7070
assertFalse(handler.isPrimitive());
7171
}
72+
73+
@Test
74+
void get_OptionalInteger() {
75+
TypeHandler handler = TypeMap.get("java.lang.Integer");
76+
TypeMap.OptionalHandler optionalHandler = new TypeMap.OptionalHandler(handler, false);
77+
assertThat(optionalHandler.toMethod()).isEqualTo("optional(PathTypeConversion::asInteger, ");
78+
}
79+
80+
@Test
81+
void get_OptionalString() {
82+
TypeHandler handler = TypeMap.get("java.lang.String");
83+
TypeMap.OptionalHandler optionalHandler = new TypeMap.OptionalHandler(handler, false);
84+
assertThat(optionalHandler.toMethod()).isEqualTo("optional(");
85+
}
86+
87+
@Test
88+
void get_OptionalEnum() {
89+
TypeHandler handler = TypeMap.enumParamHandler(UType.parse("org.my.MyEnum"));
90+
TypeMap.OptionalHandler optionalHandler = new TypeMap.OptionalHandler(handler, true);
91+
assertThat(optionalHandler.toMethod()).isEqualTo("optional(qp -> (MyEnum) toEnum(MyEnum.class, qp), ");
92+
}
7293
}

tests/test-jex/src/main/java/org/example/web/myapp/WebController.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,21 +178,24 @@ String takesNestedEnum(Foo.NestedEnum myEnum) {
178178
return "takesNestedEnum-" + myEnum;
179179
}
180180

181+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
181182
@Produces(value = "text/plain")
182-
@Get("takesOptional{myOptional}")
183-
String takesOptional(@QueryParam("myOptional") Optional<Long> myOptional) {
183+
@Get("takesOptional")
184+
String takesOptional(Optional<Long> myOptional) {
184185
return "takesOptional-" + myOptional;
185186
}
186187

188+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
187189
@Produces(value = "text/plain")
188-
@Get("takesOptionalEnum{myOptional}")
190+
@Get("takesOptionalEnum")
189191
String takesOptionalEnum(@QueryParam("myOptional") Optional<Foo.NestedEnum> myOptional) {
190192
return "takesOptionalEnum-" + myOptional;
191193
}
192194

195+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
193196
@Produces(value = "text/plain")
194-
@Get("takesOptionalEnum{myOptional}")
195-
String takesOptionalString(@QueryParam("myOptional") Optional<String> myOptional) {
197+
@Get("takesOptionalString")
198+
String takesOptionalString(@QueryParam Optional<String> myOptional) {
196199
return "takesOptionalString-" + myOptional;
197200
}
198201
}

tests/test-jex/src/test/java/org/example/web/HelloControllerTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,74 @@ void validation_expect_HttpException() {
106106
final ErrorResponse errBean = ex.bean(ErrorResponse.class);
107107
assertThat(errBean.get("name")).isEqualTo("must not be null");
108108
}
109+
110+
@Test
111+
void optionalQueryParamLong() {
112+
HttpResponse<String> res = client.request()
113+
.path("hello/takesOptional")
114+
.GET()
115+
.asString();
116+
117+
assertThat(res.statusCode()).isEqualTo(200);
118+
assertThat(res.body()).isEqualTo("takesOptional-Optional.empty");
119+
}
120+
121+
@Test
122+
void optionalQueryParamLong_withValue() {
123+
HttpResponse<String> res = client.request()
124+
.path("hello/takesOptional")
125+
.queryParam("myOptional","42")
126+
.GET()
127+
.asString();
128+
129+
assertThat(res.statusCode()).isEqualTo(200);
130+
assertThat(res.body()).isEqualTo("takesOptional-Optional[42]");
131+
}
132+
133+
@Test
134+
void optionalQueryParamEnum() {
135+
HttpResponse<String> res = client.request()
136+
.path("hello/takesOptionalEnum")
137+
.GET()
138+
.asString();
139+
140+
assertThat(res.statusCode()).isEqualTo(200);
141+
assertThat(res.body()).isEqualTo("takesOptionalEnum-Optional.empty");
142+
}
143+
144+
@Test
145+
void optionalQueryParamEnum_withValue() {
146+
HttpResponse<String> res = client.request()
147+
.path("hello/takesOptionalEnum")
148+
.queryParam("myOptional","B")
149+
.GET()
150+
.asString();
151+
152+
assertThat(res.statusCode()).isEqualTo(200);
153+
assertThat(res.body()).isEqualTo("takesOptionalEnum-Optional[B]");
154+
}
155+
156+
@Test
157+
void optionalQueryParamString() {
158+
HttpResponse<String> res = client.request()
159+
.path("hello/takesOptionalString")
160+
.GET()
161+
.asString();
162+
163+
assertThat(res.statusCode()).isEqualTo(200);
164+
assertThat(res.body()).isEqualTo("takesOptionalString-Optional.empty");
165+
}
166+
167+
@Test
168+
void optionalQueryParamString_withValue() {
169+
HttpResponse<String> res = client.request()
170+
.path("hello/takesOptionalString")
171+
.queryParam("myOptional","foo")
172+
.GET()
173+
.asString();
174+
175+
assertThat(res.statusCode()).isEqualTo(200);
176+
assertThat(res.body()).isEqualTo("takesOptionalString-Optional[foo]");
177+
}
178+
109179
}

0 commit comments

Comments
 (0)