Skip to content

Support nested parameter objects #605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 25, 2020
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
@@ -1,36 +1,30 @@
package org.springdoc.core;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import io.swagger.v3.oas.annotations.Parameter;
import org.apache.commons.lang3.ArrayUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springdoc.core.converters.AdditionalModelsConverter;

import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

/**
* @author zarebski.m
*/
class DelegatingMethodParameter extends MethodParameter {

private MethodParameter delegate;

private Annotation[] additionalParameterAnnotations;
Expand All @@ -50,10 +44,7 @@ public static MethodParameter[] customize(String[] pNames, MethodParameter[] par
MethodParameter p = parameters[i];
if (p.hasParameterAnnotation(ParameterObject.class)) {
Class<?> paramClass = AdditionalModelsConverter.getReplacement(p.getParameterType());
allFieldsOf(paramClass).stream()
.map(f -> fromGetterOfField(paramClass, f))
.filter(Objects::nonNull)
.forEach(explodedParameters::add);
MethodParameterPojoExtractor.extractFrom(paramClass).forEach(explodedParameters::add);
}
else {
String name = pNames != null ? pNames[i] : p.getParameterName();
Expand Down Expand Up @@ -139,31 +130,6 @@ public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDisc
delegate.initParameterNameDiscovery(parameterNameDiscoverer);
}

@Nullable
static MethodParameter fromGetterOfField(Class<?> paramClass, Field field) {
try {
Annotation[] filedAnnotations = field.getDeclaredAnnotations();
Parameter parameter = field.getAnnotation(Parameter.class);
if (parameter != null && !parameter.required()) {
Field fieldNullable = NullableFieldClass.class.getDeclaredField("nullableField");
Annotation annotation = fieldNullable.getAnnotation(Nullable.class);
filedAnnotations = ArrayUtils.add(filedAnnotations, annotation);
}
Annotation[] filedAnnotationsNew = filedAnnotations;
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
.filter(d -> d.getName().equals(field.getName()))
.map(PropertyDescriptor::getReadMethod)
.filter(Objects::nonNull)
.findFirst()
.map(method -> new MethodParameter(method, -1))
.map(param -> new DelegatingMethodParameter(param, field.getName(), filedAnnotationsNew))
.orElse(null);
}
catch (IntrospectionException | NoSuchFieldException e) {
return null;
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -181,18 +147,4 @@ public int hashCode() {
result = 31 * result + Arrays.hashCode(additionalParameterAnnotations);
return result;
}

private class NullableFieldClass {
@Nullable
private String nullableField;
}

private static List<Field> allFieldsOf(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
do {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
} while (clazz != null);
return fields;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package org.springdoc.core;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;

import io.swagger.v3.oas.annotations.Parameter;
import org.apache.commons.lang3.ArrayUtils;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;

class MethodParameterPojoExtractor {
static Stream<MethodParameter> extractFrom(Class<?> clazz) {
return extractFrom(clazz, "");
}

private static Stream<MethodParameter> extractFrom(Class<?> clazz, String fieldNamePrefix) {
return allFieldsOf(clazz).stream()
.flatMap(f -> fromGetterOfField(clazz, f, fieldNamePrefix))
.filter(Objects::nonNull);
}

private static Stream<MethodParameter> fromGetterOfField(Class<?> paramClass, Field field, String fieldNamePrefix) {
if (isSimpleType(field.getType()))
return fromSimpleClass(paramClass, field, fieldNamePrefix);
else
return extractFrom(field.getType(), fieldNamePrefix + field.getName() + ".");
}

private static Stream<MethodParameter> fromSimpleClass(Class<?> paramClass, Field field, String fieldNamePrefix) {
Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
if (isOptional(field))
fieldAnnotations = ArrayUtils.add(fieldAnnotations, NULLABLE_ANNOTATION);
try {
Annotation[] finalFieldAnnotations = fieldAnnotations;
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
.filter(d -> d.getName().equals(field.getName()))
.map(PropertyDescriptor::getReadMethod)
.filter(Objects::nonNull)
.map(method -> new MethodParameter(method, -1))
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), finalFieldAnnotations));
}
catch (IntrospectionException e) {
return Stream.of();
}
}

private static boolean isOptional(Field field) {
Parameter parameter = field.getAnnotation(Parameter.class);
return parameter == null || !parameter.required();
}

private static List<Field> allFieldsOf(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
do {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
} while (clazz != null);
return fields;
}

private static boolean isSimpleType(Class<?> clazz) {
return SIMPLE_TYPE_PREDICATES.stream().anyMatch(p -> p.test(clazz)) ||
SIMPLE_TYPES.stream().anyMatch(c -> c.isAssignableFrom(clazz));
}

private static final Nullable NULLABLE_ANNOTATION = new Nullable() {
@Override
public Class<? extends Annotation> annotationType() {
return Nullable.class;
}
};

private static final List<Predicate<Class<?>>> SIMPLE_TYPE_PREDICATES = new ArrayList<>();

private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>();

static void addSimpleTypePredicate(Predicate<Class<?>> predicate) {
SIMPLE_TYPE_PREDICATES.add(predicate);
}

static void addSimpleTypes(Class<?>... classes) {
SIMPLE_TYPES.addAll(Arrays.asList(classes));
}

static void removeSimpleTypes(Class<?>... classes) {
SIMPLE_TYPES.removeAll(Arrays.asList(classes));
}

static {
SIMPLE_TYPES.add(Boolean.class);
SIMPLE_TYPES.add(Character.class);
SIMPLE_TYPES.add(Number.class);
SIMPLE_TYPES.add(CharSequence.class);
SIMPLE_TYPES.add(Optional.class);
SIMPLE_TYPES.add(OptionalInt.class);
SIMPLE_TYPES.add(OptionalLong.class);
SIMPLE_TYPES.add(OptionalDouble.class);

SIMPLE_TYPES.add(Map.class);
SIMPLE_TYPES.add(Iterable.class);

SIMPLE_TYPE_PREDICATES.add(Class::isPrimitive);
SIMPLE_TYPE_PREDICATES.add(Class::isEnum);
SIMPLE_TYPE_PREDICATES.add(Class::isArray);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

package org.springdoc.core;

import java.util.function.Predicate;

import io.swagger.v3.oas.models.media.Schema;
import org.springdoc.api.AbstractOpenApiResource;
import org.springdoc.core.converters.AdditionalModelsConverter;
Expand Down Expand Up @@ -113,5 +115,20 @@ public SpringDocUtils removeFluxWrapperToIgnore(Class<?> cls) {
ConverterUtils.removeFluxWrapperToIgnore(cls);
return this;
}

public SpringDocUtils addSimpleTypesForParameterObject(Class<?>... classes) {
MethodParameterPojoExtractor.addSimpleTypes(classes);
return this;
}

public SpringDocUtils removeSimpleTypesForParameterObject(Class<?>... classes) {
MethodParameterPojoExtractor.removeSimpleTypes(classes);
return this;
}

public SpringDocUtils addSimpleTypePredicateForParameterObject(Predicate<Class<?>> predicate) {
MethodParameterPojoExtractor.addSimpleTypePredicate(predicate);
return this;
}
}

Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package test.org.springdoc.api.app102;

import javax.validation.constraints.NotBlank;

import io.swagger.v3.oas.annotations.Parameter;

public class InheritedRequestParams extends RequestParams {
@Parameter(description = "parameter from child of RequestParams")
@NotBlank
private String childParam;

public String getChildParam() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package test.org.springdoc.api.app102;

import java.math.BigInteger;
import java.util.List;
import java.util.Optional;

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

public class RequestParams {

public static class Nested {
private String param1;
private BigInteger param2;

@Parameter(description = "nested string parameter")
public String getParam1() {
return param1;
}

public void setParam1(String param1) {
this.param1 = param1;
}

@Parameter(description = "nested BigInteger parameter")
public BigInteger getParam2() {
return param2;
}

public void setParam2(BigInteger param2) {
this.param2 = param2;
}
}

@Parameter(description = "string parameter")
private String stringParam;

Expand All @@ -24,6 +49,10 @@ public class RequestParams {
@Nullable
private String intParam3;

private Nested nested;

private List<Nested> nestedList;

public String getStringParam() {
return stringParam;
}
Expand Down Expand Up @@ -72,4 +101,20 @@ public String getStringParam2() {
public void setStringParam2(String stringParam2) {
this.stringParam2 = stringParam2;
}

public Nested getNested() {
return nested;
}

public void setNested(Nested nested) {
this.nested = nested;
}

public List<Nested> getNestedList() {
return nestedList;
}

public void setNestedList(List<Nested> nestedList) {
this.nestedList = nestedList;
}
}
Loading