Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit 4495da5

Browse files
authored
Support for Optional enums (#375)
1 parent 297d93a commit 4495da5

File tree

7 files changed

+83
-36
lines changed

7 files changed

+83
-36
lines changed

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/constraints/ConstraintReaderImpl.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static capital.scalable.restdocs.constraints.ConstraintAndGroupDescriptionResolver.VALUE;
2323
import static capital.scalable.restdocs.constraints.MethodParameterValidatorConstraintResolver.CONSTRAINT_CLASS;
2424
import static capital.scalable.restdocs.util.FormatUtil.collectionToString;
25+
import static capital.scalable.restdocs.util.TypeUtil.firstGenericType;
2526
import static java.util.Collections.emptyList;
2627
import static java.util.Collections.emptyMap;
2728
import static java.util.Collections.singletonList;
@@ -31,6 +32,7 @@
3132
import static org.springframework.util.ReflectionUtils.findField;
3233

3334
import java.lang.reflect.Field;
35+
import java.lang.reflect.Type;
3436
import java.util.ArrayList;
3537
import java.util.Collections;
3638
import java.util.List;
@@ -130,11 +132,28 @@ private List<String> getEnumConstraintMessage(Class<?> javaBaseClass, String jav
130132
return emptyList();
131133
}
132134

133-
return getEnumConstraintMessage(field.getType());
135+
if (field.getType().isEnum()) {
136+
return getEnumConstraintMessage(field.getType());
137+
} else {
138+
return getEnumConstraintMessage(firstGenericType(field.getGenericType(), javaBaseClass));
139+
}
134140
}
135141

136142
private List<String> getEnumConstraintMessage(MethodParameter param) {
137-
return getEnumConstraintMessage(param.getParameterType());
143+
if (param.getParameterType().isEnum()) {
144+
return getEnumConstraintMessage(param.getParameterType());
145+
} else {
146+
return getEnumConstraintMessage(firstGenericType(param));
147+
}
148+
}
149+
150+
private List<String> getEnumConstraintMessage(Type type) {
151+
if (type instanceof Class) {
152+
Class<?> clazz = (Class<?>) type;
153+
return getEnumConstraintMessage(clazz);
154+
} else {
155+
return emptyList();
156+
}
138157
}
139158

140159
private List<String> getEnumConstraintMessage(Class<?> rawClass) {

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/AbstractJacksonFieldSnippet.java

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -88,37 +88,6 @@ protected FieldDescriptors createFieldDescriptors(Operation operation,
8888
}
8989
}
9090

91-
protected Type firstGenericType(MethodParameter param) {
92-
Type type = param.getGenericParameterType();
93-
if (type instanceof TypeVariable) {
94-
TypeVariable tv = (TypeVariable) type;
95-
return findTypeFromTypeVariable(tv, param.getContainingClass());
96-
} else if (type instanceof ParameterizedType) {
97-
ParameterizedType parameterizedType = (ParameterizedType) type;
98-
Type actualArgument = parameterizedType.getActualTypeArguments()[0];
99-
if (actualArgument instanceof Class) {
100-
return actualArgument;
101-
} else if (actualArgument instanceof TypeVariable) {
102-
TypeVariable typeVariable = (TypeVariable)actualArgument;
103-
return findTypeFromTypeVariable(typeVariable, param.getContainingClass());
104-
}
105-
return ((ParameterizedType) type).getActualTypeArguments()[0];
106-
} else {
107-
return Object.class;
108-
}
109-
}
110-
111-
protected Type findTypeFromTypeVariable(TypeVariable typeVariable, Class<?> clazz) {
112-
String variableName = typeVariable.getName();
113-
Map<TypeVariable, Type> typeMap = GenericTypeResolver.getTypeVariableMap(clazz);
114-
for (TypeVariable tv : typeMap.keySet()) {
115-
if (StringUtils.equals(tv.getName(), variableName)) {
116-
return typeMap.get(tv);
117-
}
118-
}
119-
return Object.class;
120-
}
121-
12291
protected abstract Type getType(HandlerMethod method);
12392

12493
protected abstract boolean shouldFailOnUndocumentedFields();

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/JacksonModelAttributeSnippet.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import capital.scalable.restdocs.util.HandlerMethodUtil;
4444

4545
import static capital.scalable.restdocs.SnippetRegistry.AUTO_MODELATTRIBUTE;
46+
import static capital.scalable.restdocs.util.TypeUtil.firstGenericType;
4647

4748
public class JacksonModelAttributeSnippet extends AbstractJacksonFieldSnippet {
4849
private final boolean failOnUndocumentedFields;

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/JacksonRequestFieldSnippet.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package capital.scalable.restdocs.payload;
2121

2222
import static capital.scalable.restdocs.SnippetRegistry.AUTO_REQUEST_FIELDS;
23+
import static capital.scalable.restdocs.util.TypeUtil.firstGenericType;
2324

2425
import java.lang.reflect.GenericArrayType;
2526
import java.lang.reflect.Type;

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippet.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package capital.scalable.restdocs.payload;
2121

2222
import static capital.scalable.restdocs.SnippetRegistry.AUTO_RESPONSE_FIELDS;
23+
import static capital.scalable.restdocs.util.TypeUtil.firstGenericType;
2324

2425
import java.lang.reflect.GenericArrayType;
2526
import java.lang.reflect.ParameterizedType;

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/util/TypeUtil.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,20 @@
2727
import java.lang.annotation.Annotation;
2828
import java.lang.reflect.Field;
2929
import java.lang.reflect.Method;
30+
import java.lang.reflect.ParameterizedType;
31+
import java.lang.reflect.Type;
32+
import java.lang.reflect.TypeVariable;
3033
import java.util.ArrayList;
3134
import java.util.List;
35+
import java.util.Map;
3236

3337
import capital.scalable.restdocs.jackson.TypeMapping;
3438
import com.fasterxml.jackson.annotation.JsonSubTypes;
3539
import com.fasterxml.jackson.databind.JavaType;
3640
import com.fasterxml.jackson.databind.type.TypeFactory;
41+
import org.apache.commons.lang3.StringUtils;
42+
import org.springframework.core.GenericTypeResolver;
43+
import org.springframework.core.MethodParameter;
3744
import org.springframework.util.ReflectionUtils;
3845

3946
public class TypeUtil {
@@ -192,4 +199,38 @@ private static List<JavaType> resolveNonArrayTypes(JavaType javaType, TypeFactor
192199

193200
return types;
194201
}
202+
203+
public static Type firstGenericType(MethodParameter param) {
204+
return firstGenericType(param.getGenericParameterType(), param.getContainingClass());
205+
}
206+
207+
public static Type firstGenericType(Type type, Class<?> containingClass) {
208+
if (type instanceof TypeVariable) {
209+
TypeVariable tv = (TypeVariable) type;
210+
return findTypeFromTypeVariable(tv, containingClass);
211+
} else if (type instanceof ParameterizedType) {
212+
ParameterizedType parameterizedType = (ParameterizedType) type;
213+
Type actualArgument = parameterizedType.getActualTypeArguments()[0];
214+
if (actualArgument instanceof Class) {
215+
return actualArgument;
216+
} else if (actualArgument instanceof TypeVariable) {
217+
TypeVariable typeVariable = (TypeVariable)actualArgument;
218+
return findTypeFromTypeVariable(typeVariable, containingClass);
219+
}
220+
return ((ParameterizedType) type).getActualTypeArguments()[0];
221+
} else {
222+
return Object.class;
223+
}
224+
}
225+
226+
public static Type findTypeFromTypeVariable(TypeVariable typeVariable, Class<?> clazz) {
227+
String variableName = typeVariable.getName();
228+
Map<TypeVariable, Type> typeMap = GenericTypeResolver.getTypeVariableMap(clazz);
229+
for (TypeVariable tv : typeMap.keySet()) {
230+
if (StringUtils.equals(tv.getName(), variableName)) {
231+
return typeMap.get(tv);
232+
}
233+
}
234+
return Object.class;
235+
}
195236
}

spring-auto-restdocs-core/src/test/java/capital/scalable/restdocs/constraints/ConstraintReaderImplTest.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.HashMap;
4040
import java.util.List;
4141
import java.util.Map;
42+
import java.util.Optional;
4243

4344
import capital.scalable.restdocs.i18n.SnippetTranslationManager;
4445
import com.fasterxml.jackson.annotation.JsonValue;
@@ -107,6 +108,10 @@ public void getConstraintMessages() {
107108
messages = reader.getConstraintMessages(Constraintz.class, "enum3");
108109
assertThat(messages.size(), is(1));
109110
assertThat(messages.get(0), is("Must be one of [A first, B second]"));
111+
112+
messages = reader.getConstraintMessages(Constraintz.class, "optionalEnum");
113+
assertThat(messages.size(), is(1));
114+
assertThat(messages.get(0), is("Must be one of [ONE, TWO]"));
110115
}
111116

112117
@Test
@@ -153,14 +158,17 @@ public void getOptionalMessages() {
153158
messages = reader.getOptionalMessages(Constraintz.class, "enum2");
154159
assertThat(messages.size(), is(1));
155160
assertThat(messages.get(0), is("false"));
161+
162+
messages = reader.getOptionalMessages(Constraintz.class, "optionalEnum");
163+
assertThat(messages.size(), is(0));
156164
}
157165

158166
@Test
159167
public void getParameterConstraintMessages() throws NoSuchMethodException {
160168
ConstraintReader reader = createWithValidation(new ObjectMapper(), SnippetTranslationManager.getDefaultResolver(), new ResourceBundleConstraintDescriptionResolver());
161169

162170
Method method = MethodTest.class.getMethod("exec", Integer.class, String.class,
163-
Enum1.class);
171+
Enum1.class, Optional.class);
164172

165173
List<String> messages = reader.getConstraintMessages(new MethodParameter(method, 0));
166174
assertThat(messages.size(), is(2));
@@ -174,6 +182,10 @@ public void getParameterConstraintMessages() throws NoSuchMethodException {
174182
messages = reader.getConstraintMessages(new MethodParameter(method, 2));
175183
assertThat(messages.size(), is(1));
176184
assertThat(messages.get(0), is("Must be one of [ONE, TWO]"));
185+
186+
messages = reader.getConstraintMessages(new MethodParameter(method, 3));
187+
assertThat(messages.size(), is(1));
188+
assertThat(messages.get(0), is("Must be one of [ONE, TWO]"));
177189
}
178190

179191
@Test
@@ -192,7 +204,7 @@ public void getOptionalMessages_validationNotPresent() {
192204
public void getParameterConstraintMessages_validationNotPresent() throws NoSuchMethodException {
193205
ConstraintReaderImpl reader = createWithoutValidation(new ObjectMapper(), SnippetTranslationManager.getDefaultResolver(), new ResourceBundleConstraintDescriptionResolver());
194206
Method method = MethodTest.class.getMethod("exec", Integer.class, String.class,
195-
Enum1.class);
207+
Enum1.class, Optional.class);
196208
assertThat(reader.getConstraintMessages(new MethodParameter(method, 0)).size(), is(0));
197209
}
198210

@@ -303,6 +315,8 @@ static class Constraintz {
303315
private Enum2 enum2;
304316

305317
private Enum3 enum3;
318+
319+
private Optional<Enum1> optionalEnum;
306320
}
307321

308322
enum Enum1 {ONE, TWO}
@@ -335,7 +349,8 @@ public void exec(
335349
@Max(value = 2, groups = Update.class) Integer count,
336350
@NotBlank
337351
@OneOf({ "all", "single" }) String type,
338-
Enum1 enumeration) {
352+
Enum1 enumeration,
353+
Optional<Enum1> optionalEnum) {
339354
}
340355
}
341356

0 commit comments

Comments
 (0)