Skip to content

Commit 7ce0727

Browse files
kdavisk6velo
authored andcommitted
Fixes Map Based Parameter Checking for Generic Subclasses (OpenFeign#689)
Fixes OpenFeign#665 When verifying that any of th `@*Map` annotations are in fact `Map` instances, we were assumping that all values are direct extension of a `Map` with generic type information intact. When using frameworks like Spring, it is possible to have `Map` objects that do not expose type information, like `HttpHeaders`, which directly extend from a `Map` with the type information static. This added additional checking to the `checkMapKeys` function to accomodate for `Map` subclasses without type information. If the map key information cannot be validated, we simply pass it through.
1 parent 5e2fdee commit 7ce0727

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

core/src/main/java/feign/Contract.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,35 @@ private static void checkMapString(String name, Class<?> type, Type genericType)
138138
}
139139

140140
private static void checkMapKeys(String name, Type genericType) {
141-
Type[] parameterTypes = ((ParameterizedType) genericType).getActualTypeArguments();
142-
Class<?> keyClass = (Class<?>) parameterTypes[0];
143-
checkState(String.class.equals(keyClass),
144-
"%s key must be a String: %s", name, keyClass.getSimpleName());
141+
Class<?> keyClass = null;
142+
143+
// assume our type parameterized
144+
if (ParameterizedType.class.isAssignableFrom(genericType.getClass())) {
145+
Type[] parameterTypes = ((ParameterizedType) genericType).getActualTypeArguments();
146+
keyClass = (Class<?>) parameterTypes[0];
147+
} else if (genericType instanceof Class<?>) {
148+
// raw class, type parameters cannot be inferred directly, but we can scan any extended
149+
// interfaces looking for any explict types
150+
Type[] interfaces = ((Class) genericType).getGenericInterfaces();
151+
if (interfaces != null) {
152+
for (Type extended : interfaces) {
153+
if (ParameterizedType.class.isAssignableFrom(extended.getClass())) {
154+
// use the first extended interface we find.
155+
Type[] parameterTypes = ((ParameterizedType) extended).getActualTypeArguments();
156+
keyClass = (Class<?>) parameterTypes[0];
157+
break;
158+
}
159+
}
160+
}
161+
}
162+
163+
if (keyClass != null) {
164+
checkState(String.class.equals(keyClass),
165+
"%s key must be a String: %s", name, keyClass.getSimpleName());
166+
}
145167
}
146168

169+
147170
/**
148171
* Called by parseAndValidateMetadata twice, first on the declaring class, then on the
149172
* target type (unless they are the same).

core/src/test/java/feign/DefaultContractTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@ public void onlyOneHeaderMapAnnotationPermitted() throws Exception {
371371
}
372372
}
373373

374+
@Test
375+
public void headerMapSubclass() throws Exception {
376+
MethodMetadata md = parseAndValidateMetadata(HeaderMapInterface.class, "headerMapSubClass", SubClassHeaders.class);
377+
assertThat(md.headerMapIndex()).isEqualTo(0);
378+
}
379+
374380
interface Methods {
375381

376382
@RequestLine("POST /")
@@ -470,6 +476,9 @@ interface HeaderMapInterface {
470476

471477
@RequestLine("POST /")
472478
void multipleHeaderMap(@HeaderMap Map<String, String> headers, @HeaderMap Map<String,String> queries);
479+
480+
@RequestLine("POST /")
481+
void headerMapSubClass(@HeaderMap SubClassHeaders httpHeaders);
473482
}
474483

475484
interface HeaderParams {
@@ -627,6 +636,12 @@ static class Entities<K, M> {
627636
private List<Entity<K, M>> entities;
628637
}
629638

639+
640+
interface SubClassHeaders extends Map<String, String> {
641+
642+
}
643+
644+
630645
@Headers("Version: 1")
631646
interface ParameterizedApi extends ParameterizedBaseApi<String, Long> {
632647

0 commit comments

Comments
 (0)