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

Commit 3d78a2d

Browse files
authored
json subtype support (#54)
1 parent fb270a8 commit 3d78a2d

File tree

3 files changed

+122
-18
lines changed

3 files changed

+122
-18
lines changed

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

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,25 @@
1919
import static capital.scalable.restdocs.OperationAttributeHelper.getConstraintReader;
2020
import static capital.scalable.restdocs.OperationAttributeHelper.getJavadocReader;
2121
import static capital.scalable.restdocs.OperationAttributeHelper.getObjectMapper;
22+
import static java.util.Collections.singletonList;
2223

2324
import java.lang.reflect.ParameterizedType;
2425
import java.lang.reflect.Type;
2526
import java.util.ArrayList;
27+
import java.util.Collection;
28+
import java.util.LinkedHashMap;
2629
import java.util.List;
2730
import java.util.Map;
2831

2932
import capital.scalable.restdocs.constraints.ConstraintReader;
3033
import capital.scalable.restdocs.jackson.FieldDocumentationGenerator;
3134
import capital.scalable.restdocs.javadoc.JavadocReader;
3235
import capital.scalable.restdocs.snippet.StandardTableSnippet;
36+
import com.fasterxml.jackson.annotation.JsonSubTypes;
3337
import com.fasterxml.jackson.databind.JsonMappingException;
3438
import com.fasterxml.jackson.databind.ObjectMapper;
39+
import com.fasterxml.jackson.databind.ObjectWriter;
40+
import com.fasterxml.jackson.databind.type.TypeFactory;
3541
import org.springframework.core.MethodParameter;
3642
import org.springframework.restdocs.operation.Operation;
3743
import org.springframework.restdocs.payload.FieldDescriptor;
@@ -47,35 +53,66 @@ protected AbstractJacksonFieldSnippet(String type, Map<String, Object> attribute
4753
super(type + "-fields", attributes);
4854
}
4955

50-
protected List<FieldDescriptor> createFieldDescriptors(Operation operation,
56+
protected Collection<FieldDescriptor> createFieldDescriptors(Operation operation,
5157
HandlerMethod handlerMethod) {
52-
List<FieldDescriptor> fieldDescriptors = new ArrayList<>();
58+
ObjectMapper objectMapper = getObjectMapper(operation);
59+
ObjectWriter writer = objectMapper.writer();
60+
TypeFactory typeFactory = objectMapper.getTypeFactory();
5361

54-
Type type = getType(handlerMethod);
55-
if (type != null) {
56-
ObjectMapper objectMapper = getObjectMapper(operation);
57-
JavadocReader javadocReader = getJavadocReader(operation);
58-
ConstraintReader constraintReader = getConstraintReader(operation);
62+
JavadocReader javadocReader = getJavadocReader(operation);
63+
ConstraintReader constraintReader = getConstraintReader(operation);
5964

60-
try {
61-
FieldDocumentationGenerator generator = new FieldDocumentationGenerator(
62-
objectMapper.writer(), javadocReader, constraintReader);
63-
64-
List<FieldDescriptor> descriptors = generator
65-
.generateDocumentation(type, objectMapper.getTypeFactory());
65+
Map<String, FieldDescriptor> fieldDescriptors = new LinkedHashMap<>();
6666

67-
fieldDescriptors.addAll(descriptors);
67+
Type signatureType = getType(handlerMethod);
68+
if (signatureType != null) {
69+
try {
70+
for (Type type : resolveActualTypes(signatureType)) {
71+
resolveFieldDescriptors(fieldDescriptors, type, writer, typeFactory,
72+
javadocReader, constraintReader);
73+
}
6874
} catch (JsonMappingException e) {
6975
throw new JacksonFieldProcessingException("Error while parsing fields", e);
7076
}
7177
}
7278

73-
return fieldDescriptors;
79+
return fieldDescriptors.values();
7480
}
7581

7682
protected Type firstGenericType(MethodParameter param) {
7783
return ((ParameterizedType) param.getGenericParameterType()).getActualTypeArguments()[0];
7884
}
7985

8086
protected abstract Type getType(HandlerMethod method);
87+
88+
private Collection<Type> resolveActualTypes(Type type) {
89+
90+
if (type instanceof Class) {
91+
JsonSubTypes jsonSubTypes = (JsonSubTypes) ((Class) type).getAnnotation(
92+
JsonSubTypes.class);
93+
if (jsonSubTypes != null) {
94+
Collection<Type> types = new ArrayList<>();
95+
for (JsonSubTypes.Type subType : jsonSubTypes.value()) {
96+
types.add(subType.value());
97+
}
98+
return types;
99+
}
100+
}
101+
102+
return singletonList(type);
103+
}
104+
105+
private void resolveFieldDescriptors(Map<String, FieldDescriptor> fieldDescriptors,
106+
Type type, ObjectWriter writer, TypeFactory typeFactory, JavadocReader javadocReader,
107+
ConstraintReader constraintReader)
108+
throws JsonMappingException {
109+
FieldDocumentationGenerator generator = new FieldDocumentationGenerator(writer,
110+
javadocReader, constraintReader);
111+
List<FieldDescriptor> descriptors = generator.generateDocumentation(type, typeFactory);
112+
for (FieldDescriptor descriptor : descriptors) {
113+
if (fieldDescriptors.get(descriptor.getPath()) == null) {
114+
fieldDescriptors.put(descriptor.getPath(), descriptor);
115+
}
116+
}
117+
}
81118
}

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/snippet/StandardTableSnippet.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static org.apache.commons.lang3.StringUtils.join;
2424

2525
import java.util.ArrayList;
26+
import java.util.Collection;
2627
import java.util.HashMap;
2728
import java.util.List;
2829
import java.util.Map;
@@ -44,23 +45,23 @@ protected StandardTableSnippet(String snippetName, Map<String, Object> attribute
4445
protected Map<String, Object> createModel(Operation operation) {
4546
HandlerMethod handlerMethod = getHandlerMethod(operation);
4647

47-
List<FieldDescriptor> fieldDescriptors = emptyList();
48+
Collection<FieldDescriptor> fieldDescriptors = emptyList();
4849
if (handlerMethod != null) {
4950
fieldDescriptors = createFieldDescriptors(operation, handlerMethod);
5051
}
5152

5253
return createModel(handlerMethod, fieldDescriptors);
5354
}
5455

55-
protected abstract List<FieldDescriptor> createFieldDescriptors(Operation operation,
56+
protected abstract Collection<FieldDescriptor> createFieldDescriptors(Operation operation,
5657
HandlerMethod handlerMethod);
5758

5859
protected void enrichModel(Map<String, Object> model, HandlerMethod handlerMethod) {
5960
// can be used to add additional fields
6061
}
6162

6263
private Map<String, Object> createModel(HandlerMethod handlerMethod,
63-
List<FieldDescriptor> fieldDescriptors) {
64+
Collection<FieldDescriptor> fieldDescriptors) {
6465
Map<String, Object> model = new HashMap<>();
6566
enrichModel(model, handlerMethod);
6667

spring-auto-restdocs-core/src/test/java/capital/scalable/restdocs/payload/JacksonRequestFieldSnippetTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package capital.scalable.restdocs.payload;
1818

19+
import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY;
20+
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME;
1921
import static java.util.Collections.singletonList;
2022
import static org.hamcrest.CoreMatchers.equalTo;
2123
import static org.mockito.Mockito.mock;
@@ -27,6 +29,8 @@
2729
import capital.scalable.restdocs.constraints.ConstraintReader;
2830
import capital.scalable.restdocs.javadoc.JavadocReader;
2931
import com.fasterxml.jackson.annotation.JsonAutoDetect;
32+
import com.fasterxml.jackson.annotation.JsonSubTypes;
33+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
3034
import com.fasterxml.jackson.databind.ObjectMapper;
3135
import org.hibernate.validator.constraints.NotBlank;
3236
import org.junit.Test;
@@ -122,6 +126,44 @@ public void listRequest() throws Exception {
122126
.build());
123127
}
124128

129+
@Test
130+
public void jsonSubTypesRequest() throws Exception {
131+
ObjectMapper mapper = new ObjectMapper();
132+
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
133+
.withFieldVisibility(JsonAutoDetect.Visibility.ANY));
134+
135+
HandlerMethod handlerMethod = new HandlerMethod(new TestResource(), "addSubItem",
136+
ParentItem.class);
137+
JavadocReader javadocReader = mock(JavadocReader.class);
138+
when(javadocReader.resolveFieldComment(ParentItem.class, "type"))
139+
.thenReturn("A type");
140+
when(javadocReader.resolveFieldComment(ParentItem.class, "commonField"))
141+
.thenReturn("A common field");
142+
when(javadocReader.resolveFieldComment(SubItem1.class, "subItem1Field"))
143+
.thenReturn("A sub item 1 field");
144+
when(javadocReader.resolveFieldComment(SubItem2.class, "subItem2Field"))
145+
.thenReturn("A sub item 2 field");
146+
147+
ConstraintReader constraintReader = mock(ConstraintReader.class);
148+
149+
this.snippet.expectRequestFields().withContents(
150+
tableWithHeader("Path", "Type", "Optional", "Description")
151+
.row("type", "String", "true", "A type")
152+
.row("commonField", "String", "true", "A common field")
153+
.row("subItem1Field", "Boolean", "true", "A sub item 1 field")
154+
.row("subItem2Field", "Integer", "true", "A sub item 2 field"));
155+
156+
new JacksonRequestFieldSnippet().document(operationBuilder
157+
.attribute(HandlerMethod.class.getName(), handlerMethod)
158+
.attribute(ObjectMapper.class.getName(), mapper)
159+
.attribute(JavadocReader.class.getName(), javadocReader)
160+
.attribute(ConstraintReader.class.getName(), constraintReader)
161+
.request("http://localhost")
162+
.content("{\"type\":\"1\"}")
163+
.build());
164+
}
165+
166+
125167
private static class TestResource {
126168

127169
public void addItem(@RequestBody Item item) {
@@ -135,6 +177,10 @@ public void addItems(@RequestBody List<Item> items) {
135177
public void addItem2() {
136178
// NOOP
137179
}
180+
181+
public void addSubItem(@RequestBody ParentItem item) {
182+
// NOOP
183+
}
138184
}
139185

140186
private static class Item {
@@ -143,4 +189,24 @@ private static class Item {
143189
@Size(max = 10)
144190
private Integer field2;
145191
}
192+
193+
194+
@JsonTypeInfo(use = NAME, include = PROPERTY, property = "type", visible = true)
195+
@JsonSubTypes({
196+
@JsonSubTypes.Type(value = SubItem1.class, name = "1"),
197+
@JsonSubTypes.Type(value = SubItem2.class, name = "2")
198+
})
199+
private static abstract class ParentItem {
200+
private String type;
201+
private String commonField;
202+
203+
}
204+
205+
private static class SubItem1 extends ParentItem {
206+
private Boolean subItem1Field;
207+
}
208+
209+
private static class SubItem2 extends ParentItem {
210+
private Integer subItem2Field;
211+
}
146212
}

0 commit comments

Comments
 (0)