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

Commit f77936e

Browse files
authored
Resources as response (#276)
If a response class is a `org.springframework.hateoas.Resources` it means it will be serialised as ``` "_embedded": { "someCollection": [{ ... }] } ``` 1) In this case we want embedded snippet to take over and response fields snippet to be empty. 2) Saying "no response body" in response fields snippet is misleading, therefore we say ~ response contains embedded content.
1 parent fdc302f commit f77936e

File tree

13 files changed

+225
-73
lines changed

13 files changed

+225
-73
lines changed

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2398,7 +2398,7 @@ <h4 id="contributing-building-build"><a class="link" href="#contributing-buildin
23982398
<div id="footer">
23992399
<div id="footer-text">
24002400
Version 2.0.3-SNAPSHOT<br>
2401-
Last updated 2018-11-22 10:12:58 CET
2401+
Last updated 2018-11-22 13:12:12 CET
24022402
</div>
24032403
</div>
24042404
<link rel="stylesheet" href="highlight/styles/github.min.css">

spring-auto-restdocs-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@
5757
<artifactId>spring-data-commons</artifactId>
5858
<scope>test</scope>
5959
</dependency>
60+
<dependency>
61+
<groupId>org.springframework.boot</groupId>
62+
<artifactId>spring-boot-starter-hateoas</artifactId>
63+
<version>2.0.5.RELEASE</version>
64+
<scope>test</scope>
65+
</dependency>
6066
<dependency>
6167
<groupId>org.apache.commons</groupId>
6268
<artifactId>commons-lang3</artifactId>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*-
2+
* #%L
3+
* Spring Auto REST Docs Core
4+
* %%
5+
* Copyright (C) 2015 - 2018 Scalable Capital GmbH
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package capital.scalable.restdocs.jackson;
21+
22+
import java.util.Collection;
23+
import java.util.LinkedHashMap;
24+
import java.util.Map;
25+
26+
import org.springframework.restdocs.payload.FieldDescriptor;
27+
28+
public class FieldDescriptors {
29+
private Map<String, FieldDescriptor> map = new LinkedHashMap<>();
30+
private String noContentMessageKey;
31+
32+
public void putIfAbsent(String path, FieldDescriptor descriptor) {
33+
map.putIfAbsent(path, descriptor);
34+
}
35+
36+
public Collection<FieldDescriptor> values() {
37+
return map.values();
38+
}
39+
40+
public String getNoContentMessageKey() {
41+
return noContentMessageKey;
42+
}
43+
44+
void setNoContentMessageKey(String noContentMessageKey) {
45+
this.noContentMessageKey = noContentMessageKey;
46+
}
47+
}

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationGenerator.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
public class FieldDocumentationGenerator {
3939
private static final Logger log = getLogger(FieldDocumentationGenerator.class);
40+
private static final String RESOURCES_TYPE = "org.springframework.hateoas.Resources";
4041

4142
private final ObjectWriter writer;
4243
private final DeserializationConfig deserializationConfig;
@@ -53,20 +54,28 @@ public FieldDocumentationGenerator(ObjectWriter writer,
5354
this.constraintReader = constraintReader;
5455
}
5556

56-
public List<FieldDescriptor> generateDocumentation(Type baseType, TypeFactory typeFactory)
57+
public FieldDescriptors generateDocumentation(Type baseType, TypeFactory typeFactory)
5758
throws JsonMappingException {
5859
JavaType javaBaseType = typeFactory.constructType(baseType);
5960
List<JavaType> types = resolveAllTypes(javaBaseType, typeFactory);
61+
FieldDescriptors result = new FieldDescriptors();
6062

6163
FieldDocumentationVisitorWrapper visitorWrapper = FieldDocumentationVisitorWrapper.create(
6264
javadocReader, constraintReader, deserializationConfig,
6365
new TypeRegistry(types), typeFactory);
6466

6567
for (JavaType type : types) {
6668
log.debug("(TOP) {}", type.getRawClass().getSimpleName());
69+
if (RESOURCES_TYPE.equals(type.getRawClass().getCanonicalName())) {
70+
result.setNoContentMessageKey("body-as-embedded-resources");
71+
continue;
72+
}
6773
writer.acceptJsonFormatVisitor(type, visitorWrapper);
6874
}
6975

70-
return visitorWrapper.getFields();
76+
for (FieldDescriptor descriptor : visitorWrapper.getFields()) {
77+
result.putIfAbsent(descriptor.getPath(), descriptor);
78+
}
79+
return result;
7180
}
7281
}

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationObjectVisitor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,17 @@ public void property(BeanProperty prop, boolean required) throws JsonMappingExce
9494
if (ser == null) {
9595
return;
9696
}
97+
if (shouldSkip(prop)) {
98+
return;
99+
}
100+
97101

98102
visitType(prop, jsonName, fieldName, javaType, ser, required);
99103
}
100104
}
101105

102106
private void visitType(BeanProperty prop, String jsonName, String fieldName, JavaType fieldType,
103107
JsonSerializer<?> ser, boolean required) throws JsonMappingException {
104-
if (shouldSkip(prop)) {
105-
return;
106-
}
107-
108108
String fieldPath = path + (path.isEmpty() ? "" : ".") + jsonName;
109109
log.debug("({}) {}", fieldPath, fieldType.getRawClass().getSimpleName());
110110
Class<?> javaBaseClass = prop.getMember().getDeclaringClass();

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

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@
2929
import java.lang.reflect.ParameterizedType;
3030
import java.lang.reflect.Type;
3131
import java.util.Collection;
32-
import java.util.LinkedHashMap;
33-
import java.util.List;
3432
import java.util.Map;
3533
import java.util.stream.Stream;
3634

3735
import capital.scalable.restdocs.constraints.ConstraintReader;
36+
import capital.scalable.restdocs.jackson.FieldDescriptors;
3837
import capital.scalable.restdocs.jackson.FieldDocumentationGenerator;
3938
import capital.scalable.restdocs.javadoc.JavadocReader;
4039
import capital.scalable.restdocs.section.SectionSupport;
@@ -43,7 +42,6 @@
4342
import com.fasterxml.jackson.databind.ObjectMapper;
4443
import org.springframework.core.MethodParameter;
4544
import org.springframework.restdocs.operation.Operation;
46-
import org.springframework.restdocs.payload.FieldDescriptor;
4745
import org.springframework.web.method.HandlerMethod;
4846

4947
public abstract class AbstractJacksonFieldSnippet extends StandardTableSnippet implements SectionSupport {
@@ -62,34 +60,34 @@ protected AbstractJacksonFieldSnippet(String snippetName, Map<String, Object> at
6260
super(snippetName, attributes);
6361
}
6462

65-
protected Collection<FieldDescriptor> createFieldDescriptors(Operation operation,
63+
protected FieldDescriptors createFieldDescriptors(Operation operation,
6664
HandlerMethod handlerMethod) {
6765
ObjectMapper objectMapper = getObjectMapper(operation);
6866

6967
JavadocReader javadocReader = getJavadocReader(operation);
7068
ConstraintReader constraintReader = getConstraintReader(operation);
7169

72-
Map<String, FieldDescriptor> fieldDescriptors = new LinkedHashMap<>();
73-
7470
Type type = getType(handlerMethod);
75-
if (type != null) {
76-
try {
77-
resolveFieldDescriptors(fieldDescriptors, type, objectMapper,
78-
javadocReader, constraintReader);
79-
} catch (JsonMappingException e) {
80-
throw new JacksonFieldProcessingException("Error while parsing fields", e);
81-
}
71+
if (type == null) {
72+
return new FieldDescriptors();
8273
}
8374

84-
if (shouldFailOnUndocumentedFields()) {
85-
assertAllDocumented(fieldDescriptors.values(), translate(getHeaderKey()).toLowerCase());
75+
try {
76+
FieldDescriptors fieldDescriptors = resolveFieldDescriptors(type, objectMapper,
77+
javadocReader, constraintReader);
78+
79+
if (shouldFailOnUndocumentedFields()) {
80+
assertAllDocumented(fieldDescriptors.values(), translate(getHeaderKey()).toLowerCase());
81+
}
82+
return fieldDescriptors;
83+
} catch (JsonMappingException e) {
84+
throw new JacksonFieldProcessingException("Error while parsing fields", e);
8685
}
87-
return fieldDescriptors.values();
8886
}
8987

9088
protected Type firstGenericType(MethodParameter param) {
9189
Type type = param.getGenericParameterType();
92-
if (type != null && type instanceof ParameterizedType) {
90+
if (type instanceof ParameterizedType) {
9391
return ((ParameterizedType) type).getActualTypeArguments()[0];
9492
} else {
9593
return Object.class;
@@ -105,17 +103,12 @@ protected boolean isCollection(Class<?> type) {
105103
(SCALA_TRAVERSABLE != null && SCALA_TRAVERSABLE.isAssignableFrom(type));
106104
}
107105

108-
private void resolveFieldDescriptors(Map<String, FieldDescriptor> fieldDescriptors,
109-
Type type, ObjectMapper objectMapper, JavadocReader javadocReader,
110-
ConstraintReader constraintReader) throws JsonMappingException {
106+
private FieldDescriptors resolveFieldDescriptors(Type type, ObjectMapper objectMapper,
107+
JavadocReader javadocReader, ConstraintReader constraintReader) throws JsonMappingException {
111108
FieldDocumentationGenerator generator = new FieldDocumentationGenerator(
112109
objectMapper.writer(), objectMapper.getDeserializationConfig(), javadocReader,
113110
constraintReader);
114-
List<FieldDescriptor> descriptors = generator.generateDocumentation(type,
115-
objectMapper.getTypeFactory());
116-
for (FieldDescriptor descriptor : descriptors) {
117-
fieldDescriptors.putIfAbsent(descriptor.getPath(), descriptor);
118-
}
111+
return generator.generateDocumentation(type, objectMapper.getTypeFactory());
119112
}
120113

121114
@Override

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
package capital.scalable.restdocs.payload;
2121

2222
import static capital.scalable.restdocs.SnippetRegistry.AUTO_RESPONSE_FIELDS;
23+
import static capital.scalable.restdocs.i18n.SnippetTranslationResolver.translate;
2324

2425
import java.lang.reflect.GenericArrayType;
2526
import java.lang.reflect.Type;
2627
import java.util.Map;
2728

29+
import capital.scalable.restdocs.jackson.FieldDescriptors;
2830
import org.springframework.http.HttpEntity;
2931
import org.springframework.http.ResponseEntity;
3032
import org.springframework.web.method.HandlerMethod;
@@ -83,8 +85,12 @@ protected Type getType(final HandlerMethod method) {
8385
}
8486

8587
@Override
86-
protected void enrichModel(Map<String, Object> model, HandlerMethod handlerMethod) {
88+
protected void enrichModel(Map<String, Object> model, HandlerMethod handlerMethod,
89+
FieldDescriptors fieldDescriptors) {
8790
model.put("isPageResponse", isPageResponse(handlerMethod));
91+
if (fieldDescriptors.getNoContentMessageKey() != null) {
92+
model.put("no-response-body", translate(fieldDescriptors.getNoContentMessageKey()));
93+
}
8894
}
8995

9096
private boolean isPageResponse(HandlerMethod handlerMethod) {
@@ -104,7 +110,7 @@ protected boolean shouldFailOnUndocumentedFields() {
104110

105111
@Override
106112
protected String[] getTranslationKeys() {
107-
return new String[]{
113+
return new String[] {
108114
"th-path",
109115
"th-type",
110116
"th-optional",

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/request/AbstractParameterSnippet.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,10 @@
3434
import static org.springframework.util.StringUtils.hasLength;
3535

3636
import java.lang.annotation.Annotation;
37-
import java.util.ArrayList;
38-
import java.util.List;
3937
import java.util.Map;
4038

4139
import capital.scalable.restdocs.constraints.ConstraintReader;
40+
import capital.scalable.restdocs.jackson.FieldDescriptors;
4241
import capital.scalable.restdocs.javadoc.JavadocReader;
4342
import capital.scalable.restdocs.section.SectionSupport;
4443
import capital.scalable.restdocs.snippet.StandardTableSnippet;
@@ -56,12 +55,12 @@ protected AbstractParameterSnippet(String snippetName, Map<String, Object> attri
5655
}
5756

5857
@Override
59-
protected List<FieldDescriptor> createFieldDescriptors(Operation operation,
58+
protected FieldDescriptors createFieldDescriptors(Operation operation,
6059
HandlerMethod handlerMethod) {
6160
JavadocReader javadocReader = getJavadocReader(operation);
6261
ConstraintReader constraintReader = getConstraintReader(operation);
6362

64-
List<FieldDescriptor> fieldDescriptors = new ArrayList<>();
63+
FieldDescriptors fieldDescriptors = new FieldDescriptors();
6564
for (MethodParameter param : handlerMethod.getMethodParameters()) {
6665
A annot = getAnnotation(param);
6766
if (annot != null) {
@@ -71,15 +70,15 @@ protected List<FieldDescriptor> createFieldDescriptors(Operation operation,
7170
}
7271

7372
if (shouldFailOnUndocumentedParams()) {
74-
assertAllDocumented(fieldDescriptors, translate(getHeaderKey()).toLowerCase());
73+
assertAllDocumented(fieldDescriptors.values(), translate(getHeaderKey()).toLowerCase());
7574
}
7675

7776
return fieldDescriptors;
7877
}
7978

8079
private void addFieldDescriptor(HandlerMethod handlerMethod,
8180
JavadocReader javadocReader, ConstraintReader constraintReader,
82-
List<FieldDescriptor> fieldDescriptors, MethodParameter param, A annot) {
81+
FieldDescriptors fieldDescriptors, MethodParameter param, A annot) {
8382
String javaParameterName = param.getParameterName();
8483
String pathName = getPath(annot);
8584

@@ -103,7 +102,7 @@ private void addFieldDescriptor(HandlerMethod handlerMethod,
103102
descriptor.attributes(constraints, optionals, deprecated, defaultValue);
104103
}
105104

106-
fieldDescriptors.add(descriptor);
105+
fieldDescriptors.putIfAbsent(parameterName, descriptor);
107106
}
108107

109108
abstract protected String getDefaultValue(A annotation);

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/request/RequestParametersSnippet.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import java.util.Map;
2525

26+
import capital.scalable.restdocs.jackson.FieldDescriptors;
2627
import org.springframework.core.MethodParameter;
2728
import org.springframework.web.bind.annotation.RequestParam;
2829
import org.springframework.web.bind.annotation.ValueConstants;
@@ -78,7 +79,8 @@ protected RequestParam getAnnotation(MethodParameter param) {
7879
}
7980

8081
@Override
81-
protected void enrichModel(Map<String, Object> model, HandlerMethod handlerMethod) {
82+
protected void enrichModel(Map<String, Object> model, HandlerMethod handlerMethod,
83+
FieldDescriptors fieldDescriptors) {
8284
boolean isPageRequest = isPageRequest(handlerMethod);
8385
model.put("isPageRequest", isPageRequest);
8486
if (isPageRequest) {

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

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.List;
3939
import java.util.Map;
4040

41+
import capital.scalable.restdocs.jackson.FieldDescriptors;
4142
import capital.scalable.restdocs.util.TemplateFormatting;
4243
import org.apache.commons.lang3.StringUtils;
4344
import org.springframework.restdocs.operation.Operation;
@@ -59,30 +60,34 @@ protected Map<String, Object> createModel(Operation operation) {
5960
return model;
6061
}
6162

62-
Collection<FieldDescriptor> fieldDescriptors =
63+
FieldDescriptors fieldDescriptors =
6364
createFieldDescriptors(operation, handlerMethod);
6465
TemplateFormatting templateFormatting = determineTemplateFormatting(operation);
6566
return createModel(handlerMethod, model, fieldDescriptors, templateFormatting);
6667
}
6768

68-
protected abstract Collection<FieldDescriptor> createFieldDescriptors(Operation operation,
69+
protected abstract FieldDescriptors createFieldDescriptors(Operation operation,
6970
HandlerMethod handlerMethod);
7071

7172
protected abstract String[] getTranslationKeys();
7273

73-
protected void enrichModel(Map<String, Object> model, HandlerMethod handlerMethod) {
74+
protected void enrichModel(Map<String, Object> model, HandlerMethod handlerMethod,
75+
FieldDescriptors fieldDescriptors) {
7476
// can be used to add additional fields
7577
}
7678

7779
private Map<String, Object> createModel(HandlerMethod handlerMethod, Map<String, Object> model,
78-
Collection<FieldDescriptor> fieldDescriptors, TemplateFormatting templateFormatting) {
79-
model.put("content", fieldDescriptors.stream()
80+
FieldDescriptors fieldDescriptors, TemplateFormatting templateFormatting) {
81+
Collection<FieldDescriptor> fields = fieldDescriptors.values();
82+
List<Map<String, Object>> content = fields.stream()
8083
.map(descriptor -> createModelForDescriptor(descriptor, templateFormatting))
81-
.collect(toList()));
82-
model.put("hasContent", !fieldDescriptors.isEmpty());
83-
model.put("noContent", fieldDescriptors.isEmpty());
84+
.collect(toList());
8485

85-
enrichModel(model, handlerMethod);
86+
model.put("content", content);
87+
model.put("hasContent", !fields.isEmpty());
88+
model.put("noContent", fields.isEmpty());
89+
90+
enrichModel(model, handlerMethod, fieldDescriptors);
8691

8792
return model;
8893
}
@@ -95,6 +100,7 @@ private Map<String, Object> defaultModel() {
95100
model.put("content", "");
96101
model.put("hasContent", false);
97102
model.put("noContent", true);
103+
98104
return model;
99105
}
100106

0 commit comments

Comments
 (0)