Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,25 @@
import static capital.scalable.restdocs.OperationAttributeHelper.getConstraintReader;
import static capital.scalable.restdocs.OperationAttributeHelper.getJavadocReader;
import static capital.scalable.restdocs.OperationAttributeHelper.getObjectMapper;
import static java.util.Collections.singletonList;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import capital.scalable.restdocs.constraints.ConstraintReader;
import capital.scalable.restdocs.jackson.FieldDocumentationGenerator;
import capital.scalable.restdocs.javadoc.JavadocReader;
import capital.scalable.restdocs.snippet.StandardTableSnippet;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.core.MethodParameter;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.payload.FieldDescriptor;
Expand All @@ -47,35 +53,66 @@ protected AbstractJacksonFieldSnippet(String type, Map<String, Object> attribute
super(type + "-fields", attributes);
}

protected List<FieldDescriptor> createFieldDescriptors(Operation operation,
protected Collection<FieldDescriptor> createFieldDescriptors(Operation operation,
HandlerMethod handlerMethod) {
List<FieldDescriptor> fieldDescriptors = new ArrayList<>();
ObjectMapper objectMapper = getObjectMapper(operation);
ObjectWriter writer = objectMapper.writer();
TypeFactory typeFactory = objectMapper.getTypeFactory();

Type type = getType(handlerMethod);
if (type != null) {
ObjectMapper objectMapper = getObjectMapper(operation);
JavadocReader javadocReader = getJavadocReader(operation);
ConstraintReader constraintReader = getConstraintReader(operation);
JavadocReader javadocReader = getJavadocReader(operation);
ConstraintReader constraintReader = getConstraintReader(operation);

try {
FieldDocumentationGenerator generator = new FieldDocumentationGenerator(
objectMapper.writer(), javadocReader, constraintReader);

List<FieldDescriptor> descriptors = generator
.generateDocumentation(type, objectMapper.getTypeFactory());
Map<String, FieldDescriptor> fieldDescriptors = new LinkedHashMap<>();

fieldDescriptors.addAll(descriptors);
Type signatureType = getType(handlerMethod);
if (signatureType != null) {
try {
for (Type type : resolveActualTypes(signatureType)) {
resolveFieldDescriptors(fieldDescriptors, type, writer, typeFactory,
javadocReader, constraintReader);
}
} catch (JsonMappingException e) {
throw new JacksonFieldProcessingException("Error while parsing fields", e);
}
}

return fieldDescriptors;
return fieldDescriptors.values();
}

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

protected abstract Type getType(HandlerMethod method);

private Collection<Type> resolveActualTypes(Type type) {

if (type instanceof Class) {
JsonSubTypes jsonSubTypes = (JsonSubTypes) ((Class) type).getAnnotation(
JsonSubTypes.class);
if (jsonSubTypes != null) {
Collection<Type> types = new ArrayList<>();
for (JsonSubTypes.Type subType : jsonSubTypes.value()) {
types.add(subType.value());
}
return types;
}
}

return singletonList(type);
}

private void resolveFieldDescriptors(Map<String, FieldDescriptor> fieldDescriptors,
Type type, ObjectWriter writer, TypeFactory typeFactory, JavadocReader javadocReader,
ConstraintReader constraintReader)
throws JsonMappingException {
FieldDocumentationGenerator generator = new FieldDocumentationGenerator(writer,
javadocReader, constraintReader);
List<FieldDescriptor> descriptors = generator.generateDocumentation(type, typeFactory);
for (FieldDescriptor descriptor : descriptors) {
if (fieldDescriptors.get(descriptor.getPath()) == null) {
fieldDescriptors.put(descriptor.getPath(), descriptor);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.apache.commons.lang3.StringUtils.join;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -44,23 +45,23 @@ protected StandardTableSnippet(String snippetName, Map<String, Object> attribute
protected Map<String, Object> createModel(Operation operation) {
HandlerMethod handlerMethod = getHandlerMethod(operation);

List<FieldDescriptor> fieldDescriptors = emptyList();
Collection<FieldDescriptor> fieldDescriptors = emptyList();
if (handlerMethod != null) {
fieldDescriptors = createFieldDescriptors(operation, handlerMethod);
}

return createModel(handlerMethod, fieldDescriptors);
}

protected abstract List<FieldDescriptor> createFieldDescriptors(Operation operation,
protected abstract Collection<FieldDescriptor> createFieldDescriptors(Operation operation,
HandlerMethod handlerMethod);

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

private Map<String, Object> createModel(HandlerMethod handlerMethod,
List<FieldDescriptor> fieldDescriptors) {
Collection<FieldDescriptor> fieldDescriptors) {
Map<String, Object> model = new HashMap<>();
enrichModel(model, handlerMethod);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package capital.scalable.restdocs.payload;

import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME;
import static java.util.Collections.singletonList;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.mockito.Mockito.mock;
Expand All @@ -27,6 +29,8 @@
import capital.scalable.restdocs.constraints.ConstraintReader;
import capital.scalable.restdocs.javadoc.JavadocReader;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hibernate.validator.constraints.NotBlank;
import org.junit.Test;
Expand Down Expand Up @@ -122,6 +126,44 @@ public void listRequest() throws Exception {
.build());
}

@Test
public void jsonSubTypesRequest() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY));

HandlerMethod handlerMethod = new HandlerMethod(new TestResource(), "addSubItem",
ParentItem.class);
JavadocReader javadocReader = mock(JavadocReader.class);
when(javadocReader.resolveFieldComment(ParentItem.class, "type"))
.thenReturn("A type");
when(javadocReader.resolveFieldComment(ParentItem.class, "commonField"))
.thenReturn("A common field");
when(javadocReader.resolveFieldComment(SubItem1.class, "subItem1Field"))
.thenReturn("A sub item 1 field");
when(javadocReader.resolveFieldComment(SubItem2.class, "subItem2Field"))
.thenReturn("A sub item 2 field");

ConstraintReader constraintReader = mock(ConstraintReader.class);

this.snippet.expectRequestFields().withContents(
tableWithHeader("Path", "Type", "Optional", "Description")
.row("type", "String", "true", "A type")
.row("commonField", "String", "true", "A common field")
.row("subItem1Field", "Boolean", "true", "A sub item 1 field")
.row("subItem2Field", "Integer", "true", "A sub item 2 field"));

new JacksonRequestFieldSnippet().document(operationBuilder
.attribute(HandlerMethod.class.getName(), handlerMethod)
.attribute(ObjectMapper.class.getName(), mapper)
.attribute(JavadocReader.class.getName(), javadocReader)
.attribute(ConstraintReader.class.getName(), constraintReader)
.request("http://localhost")
.content("{\"type\":\"1\"}")
.build());
}


private static class TestResource {

public void addItem(@RequestBody Item item) {
Expand All @@ -135,6 +177,10 @@ public void addItems(@RequestBody List<Item> items) {
public void addItem2() {
// NOOP
}

public void addSubItem(@RequestBody ParentItem item) {
// NOOP
}
}

private static class Item {
Expand All @@ -143,4 +189,24 @@ private static class Item {
@Size(max = 10)
private Integer field2;
}


@JsonTypeInfo(use = NAME, include = PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = SubItem1.class, name = "1"),
@JsonSubTypes.Type(value = SubItem2.class, name = "2")
})
private static abstract class ParentItem {
private String type;
private String commonField;

}

private static class SubItem1 extends ParentItem {
private Boolean subItem1Field;
}

private static class SubItem2 extends ParentItem {
private Integer subItem2Field;
}
}