Skip to content

Commit e6eb12a

Browse files
authored
Merge pull request #605 from zarebski-m/master
Support nested parameter objects
2 parents a6652ef + 7aaf552 commit e6eb12a

File tree

6 files changed

+234
-53
lines changed

6 files changed

+234
-53
lines changed

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

+2-50
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
11
package org.springdoc.core;
22

3-
import java.beans.IntrospectionException;
4-
import java.beans.Introspector;
5-
import java.beans.PropertyDescriptor;
63
import java.lang.annotation.Annotation;
74
import java.lang.reflect.AnnotatedElement;
85
import java.lang.reflect.Constructor;
96
import java.lang.reflect.Executable;
10-
import java.lang.reflect.Field;
117
import java.lang.reflect.Member;
128
import java.lang.reflect.Method;
139
import java.lang.reflect.Type;
1410
import java.util.ArrayList;
1511
import java.util.Arrays;
1612
import java.util.List;
1713
import java.util.Objects;
18-
import java.util.stream.Stream;
1914

20-
import io.swagger.v3.oas.annotations.Parameter;
2115
import org.apache.commons.lang3.ArrayUtils;
2216
import org.springdoc.api.annotations.ParameterObject;
2317
import org.springdoc.core.converters.AdditionalModelsConverter;
2418

2519
import org.springframework.core.MethodParameter;
2620
import org.springframework.core.ParameterNameDiscoverer;
2721
import org.springframework.lang.NonNull;
28-
import org.springframework.lang.Nullable;
2922

3023
/**
3124
* @author zarebski.m
3225
*/
3326
class DelegatingMethodParameter extends MethodParameter {
27+
3428
private MethodParameter delegate;
3529

3630
private Annotation[] additionalParameterAnnotations;
@@ -50,10 +44,7 @@ public static MethodParameter[] customize(String[] pNames, MethodParameter[] par
5044
MethodParameter p = parameters[i];
5145
if (p.hasParameterAnnotation(ParameterObject.class)) {
5246
Class<?> paramClass = AdditionalModelsConverter.getReplacement(p.getParameterType());
53-
allFieldsOf(paramClass).stream()
54-
.map(f -> fromGetterOfField(paramClass, f))
55-
.filter(Objects::nonNull)
56-
.forEach(explodedParameters::add);
47+
MethodParameterPojoExtractor.extractFrom(paramClass).forEach(explodedParameters::add);
5748
}
5849
else {
5950
String name = pNames != null ? pNames[i] : p.getParameterName();
@@ -139,31 +130,6 @@ public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDisc
139130
delegate.initParameterNameDiscovery(parameterNameDiscoverer);
140131
}
141132

142-
@Nullable
143-
static MethodParameter fromGetterOfField(Class<?> paramClass, Field field) {
144-
try {
145-
Annotation[] filedAnnotations = field.getDeclaredAnnotations();
146-
Parameter parameter = field.getAnnotation(Parameter.class);
147-
if (parameter != null && !parameter.required()) {
148-
Field fieldNullable = NullableFieldClass.class.getDeclaredField("nullableField");
149-
Annotation annotation = fieldNullable.getAnnotation(Nullable.class);
150-
filedAnnotations = ArrayUtils.add(filedAnnotations, annotation);
151-
}
152-
Annotation[] filedAnnotationsNew = filedAnnotations;
153-
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
154-
.filter(d -> d.getName().equals(field.getName()))
155-
.map(PropertyDescriptor::getReadMethod)
156-
.filter(Objects::nonNull)
157-
.findFirst()
158-
.map(method -> new MethodParameter(method, -1))
159-
.map(param -> new DelegatingMethodParameter(param, field.getName(), filedAnnotationsNew))
160-
.orElse(null);
161-
}
162-
catch (IntrospectionException | NoSuchFieldException e) {
163-
return null;
164-
}
165-
}
166-
167133
@Override
168134
public boolean equals(Object o) {
169135
if (this == o) return true;
@@ -181,18 +147,4 @@ public int hashCode() {
181147
result = 31 * result + Arrays.hashCode(additionalParameterAnnotations);
182148
return result;
183149
}
184-
185-
private class NullableFieldClass {
186-
@Nullable
187-
private String nullableField;
188-
}
189-
190-
private static List<Field> allFieldsOf(Class<?> clazz) {
191-
List<Field> fields = new ArrayList<>();
192-
do {
193-
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
194-
clazz = clazz.getSuperclass();
195-
} while (clazz != null);
196-
return fields;
197-
}
198150
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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.Field;
8+
import java.util.ArrayList;
9+
import java.util.Arrays;
10+
import java.util.HashSet;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.Objects;
14+
import java.util.Optional;
15+
import java.util.OptionalDouble;
16+
import java.util.OptionalInt;
17+
import java.util.OptionalLong;
18+
import java.util.Set;
19+
import java.util.function.Predicate;
20+
import java.util.stream.Stream;
21+
22+
import io.swagger.v3.oas.annotations.Parameter;
23+
import org.apache.commons.lang3.ArrayUtils;
24+
25+
import org.springframework.core.MethodParameter;
26+
import org.springframework.lang.Nullable;
27+
28+
class MethodParameterPojoExtractor {
29+
static Stream<MethodParameter> extractFrom(Class<?> clazz) {
30+
return extractFrom(clazz, "");
31+
}
32+
33+
private static Stream<MethodParameter> extractFrom(Class<?> clazz, String fieldNamePrefix) {
34+
return allFieldsOf(clazz).stream()
35+
.flatMap(f -> fromGetterOfField(clazz, f, fieldNamePrefix))
36+
.filter(Objects::nonNull);
37+
}
38+
39+
private static Stream<MethodParameter> fromGetterOfField(Class<?> paramClass, Field field, String fieldNamePrefix) {
40+
if (isSimpleType(field.getType()))
41+
return fromSimpleClass(paramClass, field, fieldNamePrefix);
42+
else
43+
return extractFrom(field.getType(), fieldNamePrefix + field.getName() + ".");
44+
}
45+
46+
private static Stream<MethodParameter> fromSimpleClass(Class<?> paramClass, Field field, String fieldNamePrefix) {
47+
Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
48+
if (isOptional(field))
49+
fieldAnnotations = ArrayUtils.add(fieldAnnotations, NULLABLE_ANNOTATION);
50+
try {
51+
Annotation[] finalFieldAnnotations = fieldAnnotations;
52+
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
53+
.filter(d -> d.getName().equals(field.getName()))
54+
.map(PropertyDescriptor::getReadMethod)
55+
.filter(Objects::nonNull)
56+
.map(method -> new MethodParameter(method, -1))
57+
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), finalFieldAnnotations));
58+
}
59+
catch (IntrospectionException e) {
60+
return Stream.of();
61+
}
62+
}
63+
64+
private static boolean isOptional(Field field) {
65+
Parameter parameter = field.getAnnotation(Parameter.class);
66+
return parameter == null || !parameter.required();
67+
}
68+
69+
private static List<Field> allFieldsOf(Class<?> clazz) {
70+
List<Field> fields = new ArrayList<>();
71+
do {
72+
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
73+
clazz = clazz.getSuperclass();
74+
} while (clazz != null);
75+
return fields;
76+
}
77+
78+
private static boolean isSimpleType(Class<?> clazz) {
79+
return SIMPLE_TYPE_PREDICATES.stream().anyMatch(p -> p.test(clazz)) ||
80+
SIMPLE_TYPES.stream().anyMatch(c -> c.isAssignableFrom(clazz));
81+
}
82+
83+
private static final Nullable NULLABLE_ANNOTATION = new Nullable() {
84+
@Override
85+
public Class<? extends Annotation> annotationType() {
86+
return Nullable.class;
87+
}
88+
};
89+
90+
private static final List<Predicate<Class<?>>> SIMPLE_TYPE_PREDICATES = new ArrayList<>();
91+
92+
private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>();
93+
94+
static void addSimpleTypePredicate(Predicate<Class<?>> predicate) {
95+
SIMPLE_TYPE_PREDICATES.add(predicate);
96+
}
97+
98+
static void addSimpleTypes(Class<?>... classes) {
99+
SIMPLE_TYPES.addAll(Arrays.asList(classes));
100+
}
101+
102+
static void removeSimpleTypes(Class<?>... classes) {
103+
SIMPLE_TYPES.removeAll(Arrays.asList(classes));
104+
}
105+
106+
static {
107+
SIMPLE_TYPES.add(Boolean.class);
108+
SIMPLE_TYPES.add(Character.class);
109+
SIMPLE_TYPES.add(Number.class);
110+
SIMPLE_TYPES.add(CharSequence.class);
111+
SIMPLE_TYPES.add(Optional.class);
112+
SIMPLE_TYPES.add(OptionalInt.class);
113+
SIMPLE_TYPES.add(OptionalLong.class);
114+
SIMPLE_TYPES.add(OptionalDouble.class);
115+
116+
SIMPLE_TYPES.add(Map.class);
117+
SIMPLE_TYPES.add(Iterable.class);
118+
119+
SIMPLE_TYPE_PREDICATES.add(Class::isPrimitive);
120+
SIMPLE_TYPE_PREDICATES.add(Class::isEnum);
121+
SIMPLE_TYPE_PREDICATES.add(Class::isArray);
122+
}
123+
}

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

+17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
package org.springdoc.core;
2020

21+
import java.util.function.Predicate;
22+
2123
import io.swagger.v3.oas.models.media.Schema;
2224
import org.springdoc.api.AbstractOpenApiResource;
2325
import org.springdoc.core.converters.AdditionalModelsConverter;
@@ -113,5 +115,20 @@ public SpringDocUtils removeFluxWrapperToIgnore(Class<?> cls) {
113115
ConverterUtils.removeFluxWrapperToIgnore(cls);
114116
return this;
115117
}
118+
119+
public SpringDocUtils addSimpleTypesForParameterObject(Class<?>... classes) {
120+
MethodParameterPojoExtractor.addSimpleTypes(classes);
121+
return this;
122+
}
123+
124+
public SpringDocUtils removeSimpleTypesForParameterObject(Class<?>... classes) {
125+
MethodParameterPojoExtractor.removeSimpleTypes(classes);
126+
return this;
127+
}
128+
129+
public SpringDocUtils addSimpleTypePredicateForParameterObject(Predicate<Class<?>> predicate) {
130+
MethodParameterPojoExtractor.addSimpleTypePredicate(predicate);
131+
return this;
132+
}
116133
}
117134

springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app102/InheritedRequestParams.java

+3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package test.org.springdoc.api.app102;
22

3+
import javax.validation.constraints.NotBlank;
4+
35
import io.swagger.v3.oas.annotations.Parameter;
46

57
public class InheritedRequestParams extends RequestParams {
68
@Parameter(description = "parameter from child of RequestParams")
9+
@NotBlank
710
private String childParam;
811

912
public String getChildParam() {

springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app102/RequestParams.java

+45
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package test.org.springdoc.api.app102;
22

3+
import java.math.BigInteger;
4+
import java.util.List;
35
import java.util.Optional;
46

57
import io.swagger.v3.oas.annotations.Parameter;
@@ -8,6 +10,29 @@
810

911
public class RequestParams {
1012

13+
public static class Nested {
14+
private String param1;
15+
private BigInteger param2;
16+
17+
@Parameter(description = "nested string parameter")
18+
public String getParam1() {
19+
return param1;
20+
}
21+
22+
public void setParam1(String param1) {
23+
this.param1 = param1;
24+
}
25+
26+
@Parameter(description = "nested BigInteger parameter")
27+
public BigInteger getParam2() {
28+
return param2;
29+
}
30+
31+
public void setParam2(BigInteger param2) {
32+
this.param2 = param2;
33+
}
34+
}
35+
1136
@Parameter(description = "string parameter")
1237
private String stringParam;
1338

@@ -24,6 +49,10 @@ public class RequestParams {
2449
@Nullable
2550
private String intParam3;
2651

52+
private Nested nested;
53+
54+
private List<Nested> nestedList;
55+
2756
public String getStringParam() {
2857
return stringParam;
2958
}
@@ -72,4 +101,20 @@ public String getStringParam2() {
72101
public void setStringParam2(String stringParam2) {
73102
this.stringParam2 = stringParam2;
74103
}
104+
105+
public Nested getNested() {
106+
return nested;
107+
}
108+
109+
public void setNested(Nested nested) {
110+
this.nested = nested;
111+
}
112+
113+
public List<Nested> getNestedList() {
114+
return nestedList;
115+
}
116+
117+
public void setNestedList(List<Nested> nestedList) {
118+
this.nestedList = nestedList;
119+
}
75120
}

0 commit comments

Comments
 (0)