Skip to content

Commit c924084

Browse files
authored
Merge pull request #5 from springdoc/master
Merge
2 parents 9b767ae + 55b4d7f commit c924084

File tree

19 files changed

+715
-42
lines changed

19 files changed

+715
-42
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ To generate documentation automatically, make sure all the methods declare the H
9696

9797
## spring-webflux support with Annotated Controllers
9898
* Documentation can be available in yaml format as well, on the following path : /v3/api-docs.yaml
99-
* Add the library to the list of your project dependencies (No additional configuration is needed)
99+
* Add the library to the list of your project dependencies ( No additional configuration is needed)
100100

101101
```xml
102102
<dependency>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.springdoc.api.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.PARAMETER)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface ParameterObject {}

springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,16 @@ public Operation build(HandlerMethod handlerMethod, RequestMethod requestMethod,
163163
String[] reflectionParametersNames = Arrays.stream(parameters).map(MethodParameter::getParameterName).toArray(String[]::new);
164164
if (pNames == null)
165165
pNames = reflectionParametersNames;
166+
parameters = DelegatingMethodParameter.customize(pNames, parameters);
166167
RequestBodyInfo requestBodyInfo = new RequestBodyInfo();
167168
List<Parameter> operationParameters = (operation.getParameters() != null) ? operation.getParameters() : new ArrayList<>();
168169
Map<String, io.swagger.v3.oas.annotations.Parameter> parametersDocMap = getApiParameters(handlerMethod.getMethod());
169170
Components components = openAPI.getComponents();
170171

171-
for (int i = 0; i < pNames.length; i++) {
172+
for (MethodParameter methodParameter : parameters) {
172173
// check if query param
173174
Parameter parameter = null;
174-
final String pName = pNames[i] == null ? reflectionParametersNames[i] : pNames[i];
175-
MethodParameter methodParameter = parameters[i];
175+
final String pName = methodParameter.getParameterName();
176176
io.swagger.v3.oas.annotations.Parameter parameterDoc = methodParameter.getParameterAnnotation(io.swagger.v3.oas.annotations.Parameter.class);
177177
if (parameterDoc == null)
178178
parameterDoc = parametersDocMap.get(pName);
@@ -205,7 +205,7 @@ else if (!RequestMethod.GET.equals(requestMethod)) {
205205
}
206206

207207
LinkedHashMap<String, Parameter> map = getParameterLinkedHashMap(components, methodAttributes, operationParameters, parametersDocMap);
208-
setParams(operation, new ArrayList<Parameter>(map.values()), requestBodyInfo);
208+
setParams(operation, new ArrayList<>(map.values()), requestBodyInfo);
209209
// allow for customisation
210210
return customiseOperation(operation, handlerMethod);
211211
}
@@ -257,7 +257,7 @@ protected boolean isParamToIgnore(MethodParameter parameter) {
257257
return true;
258258
if (parameter.getParameterAnnotation(PathVariable.class) != null || parameter.getParameterAnnotation(RequestParam.class) != null)
259259
return false;
260-
return PARAM_TYPES_TO_IGNORE.contains(parameter.getParameterType());
260+
return PARAM_TYPES_TO_IGNORE.stream().anyMatch(paramToIgnore -> paramToIgnore.isAssignableFrom(parameter.getParameterType()));
261261
}
262262

263263
private void setParams(Operation operation, List<Parameter> operationParameters, RequestBodyInfo requestBodyInfo) {
@@ -297,7 +297,7 @@ else if (pathVar != null) {
297297
String name = StringUtils.isBlank(pathVar.value()) ? pName : pathVar.value();
298298
parameterInfo.setpName(name);
299299
// check if PATH PARAM
300-
requestInfo = new RequestInfo(ParameterIn.PATH.toString(), pathVar.value(), Boolean.TRUE, null);
300+
requestInfo = new RequestInfo(ParameterIn.PATH.toString(), pathVar.value(), !methodParameter.isOptional(), null);
301301
parameter = buildParam(parameterInfo, components, requestInfo, jsonView);
302302
}
303303
else if (cookieValue != null) {
@@ -307,7 +307,7 @@ else if (cookieValue != null) {
307307
}
308308
// By default
309309
if (RequestMethod.GET.equals(requestMethod) || (parameterInfo.getParameterModel() != null && ParameterIn.PATH.toString().equals(parameterInfo.getParameterModel().getIn())))
310-
parameter = this.buildParam(QUERY_PARAM, components, parameterInfo, Boolean.TRUE, null, jsonView);
310+
parameter = this.buildParam(QUERY_PARAM, components, parameterInfo, !methodParameter.isOptional(), null, jsonView);
311311

312312
return parameter;
313313
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package org.springdoc.core;
2+
3+
import java.beans.IntrospectionException;
4+
import java.beans.Introspector;
5+
import java.beans.PropertyDescriptor;
6+
import java.lang.annotation.Annotation;
7+
import java.lang.reflect.AnnotatedElement;
8+
import java.lang.reflect.Constructor;
9+
import java.lang.reflect.Executable;
10+
import java.lang.reflect.Field;
11+
import java.lang.reflect.Member;
12+
import java.lang.reflect.Method;
13+
import java.lang.reflect.Type;
14+
import java.util.ArrayList;
15+
import java.util.Arrays;
16+
import java.util.List;
17+
import java.util.Objects;
18+
import java.util.stream.Stream;
19+
20+
import org.apache.commons.lang3.ArrayUtils;
21+
import org.springdoc.api.annotations.ParameterObject;
22+
import org.springdoc.core.converters.AdditionalModelsConverter;
23+
24+
import org.springframework.core.MethodParameter;
25+
import org.springframework.core.ParameterNameDiscoverer;
26+
import org.springframework.lang.NonNull;
27+
import org.springframework.lang.Nullable;
28+
29+
/**
30+
* @author zarebski.m
31+
*/
32+
class DelegatingMethodParameter extends MethodParameter {
33+
private MethodParameter delegate;
34+
35+
private Annotation[] additionalParameterAnnotations;
36+
37+
private String parameterName;
38+
39+
DelegatingMethodParameter(MethodParameter delegate, String parameterName, Annotation[] additionalParameterAnnotations) {
40+
super(delegate);
41+
this.delegate = delegate;
42+
this.additionalParameterAnnotations = additionalParameterAnnotations;
43+
this.parameterName = parameterName;
44+
}
45+
46+
public static MethodParameter[] customize(String[] pNames, MethodParameter[] parameters) {
47+
List<MethodParameter> explodedParameters = new ArrayList<>();
48+
for (int i = 0; i < parameters.length; ++i) {
49+
MethodParameter p = parameters[i];
50+
if (p.hasParameterAnnotation(ParameterObject.class)) {
51+
Class<?> paramClass = AdditionalModelsConverter.getReplacement(p.getParameterType());
52+
Stream.of(paramClass.getDeclaredFields())
53+
.map(f -> fromGetterOfField(paramClass, f))
54+
.filter(Objects::nonNull)
55+
.forEach(explodedParameters::add);
56+
}
57+
else {
58+
String name = pNames != null ? pNames[i] : p.getParameterName();
59+
explodedParameters.add(new DelegatingMethodParameter(p, name, null));
60+
}
61+
}
62+
return explodedParameters.toArray(new MethodParameter[0]);
63+
}
64+
65+
@Override
66+
@NonNull
67+
public Annotation[] getParameterAnnotations() {
68+
return ArrayUtils.addAll(delegate.getParameterAnnotations(), additionalParameterAnnotations);
69+
}
70+
71+
@Override
72+
public String getParameterName() {
73+
return parameterName;
74+
}
75+
76+
@Override
77+
public Method getMethod() {
78+
return delegate.getMethod();
79+
}
80+
81+
@Override
82+
public Constructor<?> getConstructor() {
83+
return delegate.getConstructor();
84+
}
85+
86+
@Override
87+
public Class<?> getDeclaringClass() {
88+
return delegate.getDeclaringClass();
89+
}
90+
91+
@Override
92+
public Member getMember() {
93+
return delegate.getMember();
94+
}
95+
96+
@Override
97+
public AnnotatedElement getAnnotatedElement() {
98+
return delegate.getAnnotatedElement();
99+
}
100+
101+
@Override
102+
public Executable getExecutable() {
103+
return delegate.getExecutable();
104+
}
105+
106+
@Override
107+
public MethodParameter withContainingClass(Class<?> containingClass) {
108+
return delegate.withContainingClass(containingClass);
109+
}
110+
111+
@Override
112+
public Class<?> getContainingClass() {
113+
return delegate.getContainingClass();
114+
}
115+
116+
@Override
117+
public Class<?> getParameterType() {
118+
return delegate.getParameterType();
119+
}
120+
121+
@Override
122+
public Type getGenericParameterType() {
123+
return delegate.getGenericParameterType();
124+
}
125+
126+
@Override
127+
public Class<?> getNestedParameterType() {
128+
return delegate.getNestedParameterType();
129+
}
130+
131+
@Override
132+
public Type getNestedGenericParameterType() {
133+
return delegate.getNestedGenericParameterType();
134+
}
135+
136+
@Override
137+
public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) {
138+
delegate.initParameterNameDiscovery(parameterNameDiscoverer);
139+
}
140+
141+
@Nullable
142+
static MethodParameter fromGetterOfField(Class<?> paramClass, Field field) {
143+
try {
144+
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
145+
.filter(d -> d.getName().equals(field.getName()))
146+
.map(PropertyDescriptor::getReadMethod)
147+
.filter(Objects::nonNull)
148+
.findFirst()
149+
.map(method -> new MethodParameter(method, -1))
150+
.map(param -> new DelegatingMethodParameter(param, field.getName(), field.getDeclaredAnnotations()))
151+
.orElse(null);
152+
}
153+
catch (IntrospectionException e) {
154+
return null;
155+
}
156+
}
157+
158+
@Override
159+
public boolean equals(Object o) {
160+
if (this == o) return true;
161+
if (o == null || getClass() != o.getClass()) return false;
162+
if (!super.equals(o)) return false;
163+
DelegatingMethodParameter that = (DelegatingMethodParameter) o;
164+
return Objects.equals(delegate, that.delegate) &&
165+
Arrays.equals(additionalParameterAnnotations, that.additionalParameterAnnotations) &&
166+
Objects.equals(parameterName, that.parameterName);
167+
}
168+
169+
@Override
170+
public int hashCode() {
171+
int result = Objects.hash(super.hashCode(), delegate, parameterName);
172+
result = 31 * result + Arrays.hashCode(additionalParameterAnnotations);
173+
return result;
174+
}
175+
}

springdoc-openapi-common/src/main/java/org/springdoc/core/converters/AdditionalModelsConverter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
package org.springdoc.core.converters;
2020

21-
2221
import java.util.HashMap;
2322
import java.util.Iterator;
2423
import java.util.Map;
@@ -44,6 +43,10 @@ public static void replaceWithSchema(Class source, Schema target) {
4443
modelToSchemaMap.put(source, target);
4544
}
4645

46+
public static Class getReplacement(Class clazz) {
47+
return modelToClassMap.getOrDefault(clazz, clazz);
48+
}
49+
4750
@Override
4851
public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
4952
JavaType javaType = Json.mapper().constructType(type.getType());

springdoc-openapi-data-rest/src/main/java/org/springdoc/core/converters/Pageable.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,47 +18,54 @@
1818

1919
package org.springdoc.core.converters;
2020

21-
import java.util.List;
22-
import java.util.Objects;
21+
import io.swagger.v3.oas.annotations.media.ArraySchema;
22+
import io.swagger.v3.oas.annotations.media.Schema;
23+
import org.springframework.lang.Nullable;
2324

2425
import javax.validation.constraints.Max;
2526
import javax.validation.constraints.Min;
2627
import javax.validation.constraints.NotNull;
28+
import java.util.List;
29+
import java.util.Objects;
2730

2831
@NotNull
2932
public class Pageable {
3033

31-
@NotNull
34+
@Nullable
3235
@Min(0)
33-
private int page;
36+
@Schema(description = "Zero-based page index (0..N)", defaultValue = "0")
37+
private Integer page;
3438

35-
@NotNull
39+
@Nullable
3640
@Min(1)
3741
@Max(2000)
38-
private int size;
42+
@Schema(description = "The size of the page to be returned", defaultValue = "20")
43+
private Integer size;
3944

40-
@NotNull
45+
@Nullable
46+
@ArraySchema(arraySchema = @Schema(description = "Sorting criteria in the format: property(,asc|desc). "
47+
+ "Default sort order is ascending. " + "Multiple sort criteria are supported."))
4148
private List<String> sort;
4249

43-
public Pageable(@NotNull @Min(0) int page, @NotNull @Min(1) @Max(2000) int size, List<String> sort) {
50+
public Pageable(int page, int size, List<String> sort) {
4451
this.page = page;
4552
this.size = size;
4653
this.sort = sort;
4754
}
4855

49-
public int getPage() {
56+
public Integer getPage() {
5057
return page;
5158
}
5259

53-
public void setPage(int page) {
60+
public void setPage(Integer page) {
5461
this.page = page;
5562
}
5663

57-
public int getSize() {
64+
public Integer getSize() {
5865
return size;
5966
}
6067

61-
public void setSize(int size) {
68+
public void setSize(Integer size) {
6269
this.size = size;
6370
}
6471

@@ -67,10 +74,11 @@ public List<String> getSort() {
6774
}
6875

6976
public void setSort(List<String> sort) {
70-
if (sort == null)
77+
if (sort == null) {
7178
this.sort.clear();
72-
else
79+
} else {
7380
this.sort = sort;
81+
}
7482
}
7583

7684
public void addSort(String sort) {
@@ -82,8 +90,8 @@ public boolean equals(Object o) {
8290
if (this == o) return true;
8391
if (o == null || getClass() != o.getClass()) return false;
8492
Pageable pageable = (Pageable) o;
85-
return page == pageable.page &&
86-
size == pageable.size &&
93+
return Objects.equals(page, pageable.page) &&
94+
Objects.equals(size, pageable.size) &&
8795
Objects.equals(sort, pageable.sort);
8896
}
8997

0 commit comments

Comments
 (0)