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

Commit b9f133c

Browse files
AnWeberfbenz
authored andcommitted
Better support for ModelAttribute (#368)
* feat: better support for ModelAttribute Annotation * fix: only not simple classes are automatcally modelattribute * fix: removed unused imports * fix: changed negated allMatch to noneMatch * fix: moved getTranslationKeys to ModelAttributeSnippet
1 parent dad0418 commit b9f133c

21 files changed

+596
-75
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,18 @@
2424
import capital.scalable.restdocs.misc.AuthorizationSnippet;
2525
import capital.scalable.restdocs.misc.DescriptionSnippet;
2626
import capital.scalable.restdocs.misc.MethodAndPathSnippet;
27+
import capital.scalable.restdocs.payload.JacksonModelAttributeSnippet;
2728
import capital.scalable.restdocs.payload.JacksonRequestFieldSnippet;
2829
import capital.scalable.restdocs.payload.JacksonResponseFieldSnippet;
2930
import capital.scalable.restdocs.request.PathParametersSnippet;
3031
import capital.scalable.restdocs.request.RequestHeaderSnippet;
3132
import capital.scalable.restdocs.request.RequestParametersSnippet;
3233
import capital.scalable.restdocs.section.SectionBuilder;
34+
35+
import java.util.Collection;
36+
3337
import org.springframework.restdocs.snippet.Snippet;
38+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
3439

3540
public abstract class AutoDocumentation {
3641

@@ -41,6 +46,10 @@ public static JacksonRequestFieldSnippet requestFields() {
4146
return new JacksonRequestFieldSnippet();
4247
}
4348

49+
public static JacksonModelAttributeSnippet modelAttribute(Collection<HandlerMethodArgumentResolver> handlerMethodArgumentResolvers) {
50+
return new JacksonModelAttributeSnippet(handlerMethodArgumentResolvers, false);
51+
}
52+
4453
public static JacksonResponseFieldSnippet responseFields() {
4554
return new JacksonResponseFieldSnippet();
4655
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class SnippetRegistry {
3333
public static final String AUTO_PATH_PARAMETERS = "auto-path-parameters";
3434
public static final String AUTO_REQUEST_HEADERS = "auto-request-headers";
3535
public static final String AUTO_REQUEST_PARAMETERS = "auto-request-parameters";
36+
public static final String AUTO_MODELATTRIBUTE = "auto-modelattribute";
3637
public static final String AUTO_REQUEST_FIELDS = "auto-request-fields";
3738
public static final String AUTO_RESPONSE_FIELDS = "auto-response-fields";
3839
public static final String AUTO_LINKS = "auto-links";
@@ -101,7 +102,7 @@ public String getFileName() {
101102
}
102103

103104
@Override
104-
public String getHeaderKey() {
105+
public String getHeaderKey(Operation operation) {
105106
return headerKey;
106107
}
107108

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/hypermedia/EmbeddedSnippet.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.lang.reflect.Type;
2525

2626
import capital.scalable.restdocs.payload.AbstractJacksonFieldSnippet;
27+
28+
import org.springframework.restdocs.operation.Operation;
2729
import org.springframework.web.method.HandlerMethod;
2830

2931
public class EmbeddedSnippet extends AbstractJacksonFieldSnippet {
@@ -55,7 +57,7 @@ protected Type getType(HandlerMethod method) {
5557
}
5658

5759
@Override
58-
public String getHeaderKey() {
60+
public String getHeaderKey(Operation operation) {
5961
return "embedded";
6062
}
6163

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/hypermedia/LinksSnippet.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.lang.reflect.Type;
2525

2626
import capital.scalable.restdocs.payload.AbstractJacksonFieldSnippet;
27+
import org.springframework.restdocs.operation.Operation;
2728
import org.springframework.web.method.HandlerMethod;
2829

2930
public class LinksSnippet extends AbstractJacksonFieldSnippet {
@@ -54,7 +55,7 @@ protected Type getType(HandlerMethod method) {
5455
}
5556

5657
@Override
57-
public String getHeaderKey() {
58+
public String getHeaderKey(Operation operation) {
5859
return "hypermedia-links";
5960
}
6061

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/misc/AuthorizationSnippet.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public String getFileName() {
6868
}
6969

7070
@Override
71-
public String getHeaderKey() {
71+
public String getHeaderKey(Operation operation) {
7272
return "authorization";
7373
}
7474

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
99
* You may obtain a copy of the License at
10-
*
10+
*
1111
* http://www.apache.org/licenses/LICENSE-2.0
12-
*
12+
*
1313
* Unless required by applicable law or agreed to in writing, software
1414
* distributed under the License is distributed on an "AS IS" BASIS,
1515
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -80,7 +80,7 @@ protected FieldDescriptors createFieldDescriptors(Operation operation,
8080
javadocReader, constraintReader, typeMapping, translationResolver);
8181

8282
if (shouldFailOnUndocumentedFields()) {
83-
assertAllDocumented(fieldDescriptors.values(), translationResolver.translate(getHeaderKey()).toLowerCase());
83+
assertAllDocumented(fieldDescriptors.values(), translationResolver.translate(getHeaderKey(operation)).toLowerCase());
8484
}
8585
return fieldDescriptors;
8686
} catch (JsonMappingException e) {
@@ -101,7 +101,7 @@ protected Type firstGenericType(MethodParameter param) {
101101
} else if (actualArgument instanceof TypeVariable) {
102102
TypeVariable typeVariable = (TypeVariable)actualArgument;
103103
return findTypeFromTypeVariable(typeVariable, param.getContainingClass());
104-
}
104+
}
105105
return ((ParameterizedType) type).getActualTypeArguments()[0];
106106
} else {
107107
return Object.class;
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*-
2+
* #%L
3+
* Spring Auto REST Docs Core
4+
* %%
5+
* Copyright (C) 2015 - 2020 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+
21+
package capital.scalable.restdocs.payload;
22+
23+
import java.lang.reflect.GenericArrayType;
24+
import java.lang.reflect.Type;
25+
import java.util.Arrays;
26+
import java.util.Collection;
27+
import java.util.Map;
28+
29+
import org.springframework.beans.BeanUtils;
30+
import org.springframework.core.MethodParameter;
31+
import org.springframework.restdocs.operation.Operation;
32+
import org.springframework.web.bind.annotation.ModelAttribute;
33+
import org.springframework.web.bind.annotation.RequestMapping;
34+
import org.springframework.web.bind.annotation.RequestMethod;
35+
import org.springframework.web.method.HandlerMethod;
36+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
37+
38+
import capital.scalable.restdocs.OperationAttributeHelper;
39+
import capital.scalable.restdocs.i18n.SnippetTranslationResolver;
40+
import capital.scalable.restdocs.jackson.FieldDescriptors;
41+
import capital.scalable.restdocs.payload.AbstractJacksonFieldSnippet;
42+
import capital.scalable.restdocs.request.RequestParametersSnippet;
43+
import capital.scalable.restdocs.util.HandlerMethodUtil;
44+
45+
import static capital.scalable.restdocs.SnippetRegistry.AUTO_MODELATTRIBUTE;
46+
47+
public class JacksonModelAttributeSnippet extends AbstractJacksonFieldSnippet {
48+
private final boolean failOnUndocumentedFields;
49+
private final Collection<HandlerMethodArgumentResolver> handlerMethodArgumentResolvers;
50+
51+
public JacksonModelAttributeSnippet() {
52+
this(null, false);
53+
}
54+
55+
public JacksonModelAttributeSnippet(Collection<HandlerMethodArgumentResolver> handlerMethodArgumentResolvers, boolean failOnUndocumentedFields) {
56+
super(AUTO_MODELATTRIBUTE, null);
57+
this.failOnUndocumentedFields = failOnUndocumentedFields;
58+
this.handlerMethodArgumentResolvers = handlerMethodArgumentResolvers;
59+
}
60+
61+
@Override
62+
protected Type getType(HandlerMethod method) {
63+
for (MethodParameter param : method.getMethodParameters()) {
64+
if (isModelAttribute(param) || hasNoHandlerMethodArgumentResolver(param)) {
65+
return getType(param);
66+
}
67+
}
68+
return null;
69+
}
70+
71+
72+
private boolean isModelAttribute(MethodParameter param) {
73+
return param.getParameterAnnotation(ModelAttribute.class) != null;
74+
}
75+
76+
private boolean hasNoHandlerMethodArgumentResolver(MethodParameter param) {
77+
return !BeanUtils.isSimpleProperty(param.getParameterType())
78+
&& this.handlerMethodArgumentResolvers != null
79+
&& this.handlerMethodArgumentResolvers
80+
.stream()
81+
.noneMatch(obj -> obj.supportsParameter(param));
82+
}
83+
84+
85+
private Type getType(final MethodParameter param) {
86+
if (isCollection(param.getParameterType())) {
87+
return (GenericArrayType) () -> firstGenericType(param);
88+
} else {
89+
return param.getParameterType();
90+
}
91+
}
92+
93+
/**
94+
* is request method get supported
95+
* @param method
96+
* @return
97+
*/
98+
protected boolean isRequestMethodGet(HandlerMethod method) {
99+
RequestMapping requestMapping = method.getMethodAnnotation(RequestMapping.class);
100+
return requestMapping == null
101+
|| requestMapping.method() == null
102+
|| Arrays.stream(requestMapping.method()).anyMatch(requestMethod -> {
103+
return requestMethod == RequestMethod.GET;
104+
});
105+
}
106+
107+
@Override
108+
protected void enrichModel(Map<String, Object> model, HandlerMethod handlerMethod,
109+
FieldDescriptors fieldDescriptors, SnippetTranslationResolver translationResolver) {
110+
boolean isPageRequest = HandlerMethodUtil.isPageRequest(handlerMethod);
111+
model.put("isPageRequest", isPageRequest);
112+
if (isPageRequest) {
113+
model.put("noContent", false);
114+
}
115+
}
116+
117+
@Override
118+
protected boolean shouldFailOnUndocumentedFields() {
119+
return failOnUndocumentedFields;
120+
}
121+
122+
@Override
123+
public String getHeaderKey(Operation operation) {
124+
HandlerMethod method = OperationAttributeHelper.getHandlerMethod(operation);
125+
if (method != null && this.isRequestMethodGet(method)) {
126+
return "request-parameters";
127+
}
128+
return "request-fields";
129+
}
130+
131+
@Override
132+
protected String[] getTranslationKeys() {
133+
return new String[]{
134+
"th-parameter",
135+
"th-type",
136+
"th-optional",
137+
"th-description",
138+
"pagination-request-adoc",
139+
"pagination-request-md",
140+
"no-params"
141+
};
142+
}
143+
}

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

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
99
* You may obtain a copy of the License at
10-
*
10+
*
1111
* http://www.apache.org/licenses/LICENSE-2.0
12-
*
12+
*
1313
* Unless required by applicable law or agreed to in writing, software
1414
* distributed under the License is distributed on an "AS IS" BASIS,
1515
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -25,7 +25,7 @@
2525
import java.lang.reflect.Type;
2626

2727
import org.springframework.core.MethodParameter;
28-
import org.springframework.web.bind.annotation.ModelAttribute;
28+
import org.springframework.restdocs.operation.Operation;
2929
import org.springframework.web.bind.annotation.RequestBody;
3030
import org.springframework.web.method.HandlerMethod;
3131

@@ -59,7 +59,7 @@ protected Type getType(HandlerMethod method) {
5959
}
6060

6161
for (MethodParameter param : method.getMethodParameters()) {
62-
if (isRequestBody(param) || isModelAttribute(param)) {
62+
if (isRequestBody(param)) {
6363
return getType(param);
6464
}
6565
}
@@ -70,10 +70,6 @@ private boolean isRequestBody(MethodParameter param) {
7070
return param.getParameterAnnotation(RequestBody.class) != null;
7171
}
7272

73-
private boolean isModelAttribute(MethodParameter param) {
74-
return param.getParameterAnnotation(ModelAttribute.class) != null;
75-
}
76-
7773
private Type getType(final MethodParameter param) {
7874
if (isCollection(param.getParameterType())) {
7975
return (GenericArrayType) () -> firstGenericType(param);
@@ -83,7 +79,7 @@ private Type getType(final MethodParameter param) {
8379
}
8480

8581
@Override
86-
public String getHeaderKey() {
82+
public String getHeaderKey(Operation operation) {
8783
return "request-fields";
8884
}
8985

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import capital.scalable.restdocs.i18n.SnippetTranslationResolver;
3434
import capital.scalable.restdocs.jackson.FieldDescriptors;
3535
import org.springframework.http.HttpEntity;
36+
import org.springframework.restdocs.operation.Operation;
3637
import org.springframework.web.method.HandlerMethod;
3738

3839
public class JacksonResponseFieldSnippet extends AbstractJacksonFieldSnippet {
@@ -118,7 +119,7 @@ private boolean isPageResponse(HandlerMethod handlerMethod) {
118119
}
119120

120121
@Override
121-
public String getHeaderKey() {
122+
public String getHeaderKey(Operation operation) {
122123
return "response-fields";
123124
}
124125

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ protected FieldDescriptors createFieldDescriptors(Operation operation,
7474
}
7575

7676
if (shouldFailOnUndocumentedParams()) {
77-
assertAllDocumented(fieldDescriptors.values(), translationResolver.translate(getHeaderKey()).toLowerCase());
77+
assertAllDocumented(fieldDescriptors.values(), translationResolver.translate(getHeaderKey(operation)).toLowerCase());
7878
}
7979

8080
return fieldDescriptors;

0 commit comments

Comments
 (0)