Skip to content
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

feat: default flat param object #1805

Merged
merged 1 commit into from Aug 20, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -185,6 +185,7 @@ protected AbstractRequestService(GenericParameterService parameterBuilder, Reque
parameterCustomizers.ifPresent(customizers -> customizers.removeIf(Objects::isNull));
this.parameterCustomizers = parameterCustomizers;
this.localSpringDocParameterNameDiscoverer = localSpringDocParameterNameDiscoverer;
parameterBuilder.addIgnoreType(PARAM_TYPES_TO_IGNORE);
}

/**
Expand Down Expand Up @@ -239,7 +240,7 @@ public Operation build(HandlerMethod handlerMethod, RequestMethod requestMethod,
String[] reflectionParametersNames = Arrays.stream(handlerMethod.getMethod().getParameters()).map(java.lang.reflect.Parameter::getName).toArray(String[]::new);
if (pNames == null || Arrays.stream(pNames).anyMatch(Objects::isNull))
pNames = reflectionParametersNames;
parameters = DelegatingMethodParameter.customize(pNames, parameters, parameterBuilder.getDelegatingMethodParameterCustomizer());
parameters = parameterBuilder.customize(pNames, parameters, parameterBuilder.getDelegatingMethodParameterCustomizer());
RequestBodyInfo requestBodyInfo = new RequestBodyInfo();
List<Parameter> operationParameters = (operation.getParameters() != null) ? operation.getParameters() : new ArrayList<>();
Map<String, io.swagger.v3.oas.annotations.Parameter> parametersDocMap = getApiParameters(handlerMethod.getMethod());
Expand Down Expand Up @@ -322,7 +323,7 @@ else if (!RequestMethod.GET.equals(requestMethod)) {
Entry<String, Parameter> entry = it.next();
Parameter parameter = entry.getValue();
if (!ParameterIn.PATH.toString().equals(parameter.getIn())) {
io.swagger.v3.oas.models.media.Schema<?> itemSchema = new io.swagger.v3.oas.models.media.Schema() ;
io.swagger.v3.oas.models.media.Schema<?> itemSchema = new io.swagger.v3.oas.models.media.Schema();
itemSchema.setName(entry.getKey());
itemSchema.setDescription(parameter.getDescription());
itemSchema.setDeprecated(parameter.getDeprecated());
Expand Down
Expand Up @@ -29,23 +29,16 @@
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.Optional;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.api.annotations.ParameterObject;
import org.springdoc.core.converters.AdditionalModelsConverter;
import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer;

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

Expand Down Expand Up @@ -103,33 +96,6 @@ public class DelegatingMethodParameter extends MethodParameter {
this.isNotRequired = isNotRequired;
}

/**
* Customize method parameter [ ].
*
* @param pNames the p names
* @param parameters the parameters
* @param optionalDelegatingMethodParameterCustomizer the optional delegating method parameter customizer
* @return the method parameter [ ]
*/
public static MethodParameter[] customize(String[] pNames, MethodParameter[] parameters, Optional<DelegatingMethodParameterCustomizer> optionalDelegatingMethodParameterCustomizer) {
List<MethodParameter> explodedParameters = new ArrayList<>();
for (int i = 0; i < parameters.length; ++i) {
MethodParameter p = parameters[i];
Class<?> paramClass = AdditionalModelsConverter.getParameterObjectReplacement(p.getParameterType());

if (!MethodParameterPojoExtractor.isSimpleType(paramClass) && (p.hasParameterAnnotation(ParameterObject.class) || AnnotatedElementUtils.isAnnotated(paramClass, ParameterObject.class))) {
MethodParameterPojoExtractor.extractFrom(paramClass).forEach(methodParameter -> {
optionalDelegatingMethodParameterCustomizer.ifPresent(customizer -> customizer.customize(p, methodParameter));
explodedParameters.add(methodParameter);
});
}
else {
String name = pNames != null ? pNames[i] : p.getParameterName();
explodedParameters.add(new DelegatingMethodParameter(p, name, null, false, false));
}
}
return explodedParameters.toArray(new MethodParameter[0]);
}

@Override
@NonNull
Expand Down Expand Up @@ -274,4 +240,4 @@ public static MethodParameter changeContainingClass(MethodParameter methodParame
return result;
}

}
}
Expand Up @@ -55,6 +55,8 @@
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.api.annotations.ParameterObject;
import org.springdoc.core.converters.AdditionalModelsConverter;
import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springdoc.core.providers.WebConversionServiceProvider;
Expand All @@ -64,6 +66,7 @@
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.io.Resource;
import org.springframework.web.context.request.RequestScope;
import org.springframework.web.multipart.MultipartFile;
Expand Down Expand Up @@ -122,6 +125,13 @@ public class GenericParameterService {
*/
private final ObjectMapperProvider objectMapperProvider;

/**
* The constant PARAM_TYPES_TO_IGNORE.
*/
private final List<Class<?>> PARAM_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());

private final boolean defaultFlatParamObject;

/**
* Instantiates a new Generic parameter builder.
* @param propertyResolverUtils the property resolver utils
Expand All @@ -130,13 +140,18 @@ public class GenericParameterService {
* @param objectMapperProvider the object mapper provider
*/
public GenericParameterService(PropertyResolverUtils propertyResolverUtils, Optional<DelegatingMethodParameterCustomizer> optionalDelegatingMethodParameterCustomizer,
Optional<WebConversionServiceProvider> optionalWebConversionServiceProvider, ObjectMapperProvider objectMapperProvider) {
Optional<WebConversionServiceProvider> optionalWebConversionServiceProvider, ObjectMapperProvider objectMapperProvider, boolean defaultFlatParamObject) {
this.propertyResolverUtils = propertyResolverUtils;
this.optionalDelegatingMethodParameterCustomizer = optionalDelegatingMethodParameterCustomizer;
this.optionalWebConversionServiceProvider = optionalWebConversionServiceProvider;
this.configurableBeanFactory = propertyResolverUtils.getFactory();
this.expressionContext = (configurableBeanFactory != null ? new BeanExpressionContext(configurableBeanFactory, new RequestScope()) : null);
this.objectMapperProvider = objectMapperProvider;
this.defaultFlatParamObject = defaultFlatParamObject;
}

protected void addIgnoreType(List<Class<?>> classes) {
PARAM_TYPES_TO_IGNORE.addAll(classes);
}

/**
Expand Down Expand Up @@ -333,9 +348,9 @@ Schema calculateSchema(Components components, ParameterInfo parameterInfo, Reque

if (parameterInfo.getParameterModel() == null || parameterInfo.getParameterModel().getSchema() == null) {
Type type = ReturnTypeParser.getType(methodParameter);
if(type instanceof Class && optionalWebConversionServiceProvider.isPresent()){
if (type instanceof Class && optionalWebConversionServiceProvider.isPresent()) {
WebConversionServiceProvider webConversionServiceProvider = optionalWebConversionServiceProvider.get();
if (!MethodParameterPojoExtractor.isSwaggerPrimitiveType((Class) type) && methodParameter.getParameterType().getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class)==null)
if (!MethodParameterPojoExtractor.isSwaggerPrimitiveType((Class) type) && methodParameter.getParameterType().getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class) == null)
type = webConversionServiceProvider.getSpringConvertedType(methodParameter.getParameterType());
}
schemaN = SpringDocAnnotationsUtils.extractSchema(components, type, jsonView, methodParameter.getParameterAnnotations());
Expand Down Expand Up @@ -566,6 +581,7 @@ public io.swagger.v3.oas.annotations.Parameter generateParameterBySchema(io.swag
public Class<? extends Annotation> annotationType() {
return io.swagger.v3.oas.annotations.Parameter.class;
}

@Override
public String name() {
return schema.name();
Expand Down Expand Up @@ -652,4 +668,47 @@ public String ref() {
}
};
}

/**
* Customize method parameter [ ].
*
* @param pNames the p names
* @param parameters the parameters
* @param optionalDelegatingMethodParameterCustomizer the optional delegating method parameter customizer
* @return the method parameter [ ]
*/
public MethodParameter[] customize(String[] pNames, MethodParameter[] parameters, Optional<DelegatingMethodParameterCustomizer> optionalDelegatingMethodParameterCustomizer) {
List<MethodParameter> explodedParameters = new ArrayList<>();
for (int i = 0; i < parameters.length; ++i) {
MethodParameter p = parameters[i];
Class<?> paramClass = AdditionalModelsConverter.getParameterObjectReplacement(p.getParameterType());
if (!MethodParameterPojoExtractor.isSimpleType(paramClass) && (p.hasParameterAnnotation(ParameterObject.class) || AnnotatedElementUtils.isAnnotated(paramClass, ParameterObject.class))) {
MethodParameterPojoExtractor.extractFrom(paramClass).forEach(methodParameter -> {
optionalDelegatingMethodParameterCustomizer.ifPresent(customizer -> customizer.customize(p, methodParameter));
explodedParameters.add(methodParameter);
});
}
else if (defaultFlatParamObject) {
boolean isSimpleType = MethodParameterPojoExtractor.isSimpleType(paramClass);
boolean hasAnnotation = p.hasParameterAnnotations();
boolean shouldFlat = !isSimpleType && !hasAnnotation;
if (shouldFlat && PARAM_TYPES_TO_IGNORE.stream().noneMatch(ignore -> ignore.isAssignableFrom(paramClass))) {
MethodParameterPojoExtractor.extractFrom(paramClass).forEach(methodParameter -> {
optionalDelegatingMethodParameterCustomizer
.ifPresent(customizer -> customizer.customize(p, methodParameter));
explodedParameters.add(methodParameter);
});
}
else {
String name = pNames != null ? pNames[i] : p.getParameterName();
explodedParameters.add(new DelegatingMethodParameter(p, name, null, false, false));
}
}
else {
String name = pNames != null ? pNames[i] : p.getParameterName();
explodedParameters.add(new DelegatingMethodParameter(p, name, null, false, false));
}
}
return explodedParameters.toArray(new MethodParameter[0]);
}
}
Expand Up @@ -28,6 +28,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.LocalTime;
import java.util.ArrayList;
Expand Down Expand Up @@ -83,6 +84,7 @@ private MethodParameterPojoExtractor() {
SIMPLE_TYPES.add(OptionalDouble.class);
SIMPLE_TYPES.add(AtomicLong.class);
SIMPLE_TYPES.add(AtomicInteger.class);
SIMPLE_TYPES.add(Charset.class);

SIMPLE_TYPES.add(Map.class);
SIMPLE_TYPES.add(Iterable.class);
Expand Down Expand Up @@ -154,7 +156,7 @@ private static Class<?> extractType(Class<?> paramClass, Field field) {

if (fieldType instanceof Class<?>)
type = (Class<?>) fieldType;
else // This is the case for not reifiable types
else // This is the case for not reifiable types
type = null;
}

Expand All @@ -173,7 +175,7 @@ private static Stream<MethodParameter> fromSimpleClass(Class<?> paramClass, Fiel
Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
try {
Parameter parameter = field.getAnnotation(Parameter.class);
boolean isNotRequired = parameter == null || !parameter.required();
boolean isNotRequired = parameter == null || !parameter.required();
Annotation[] finalFieldAnnotations = fieldAnnotations;
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
.filter(d -> d.getName().equals(field.getName()))
Expand Down
Expand Up @@ -176,6 +176,11 @@ public class SpringDocConfigProperties {
*/
private boolean showSpringCloudFunctions;

/**
* The param default flatten
*/
private boolean defaultFlatParamObject;

/**
* The model Converters
*/
Expand Down Expand Up @@ -222,6 +227,18 @@ public void setShowSpringCloudFunctions(boolean showSpringCloudFunctions) {
this.showSpringCloudFunctions = showSpringCloudFunctions;
}

/**
* Is default flat param object
* @return the boolean
*/
public boolean isDefaultFlatParamObject() {
return defaultFlatParamObject;
}

public void setDefaultFlatParamObject(boolean defaultFlatParamObject) {
this.defaultFlatParamObject = defaultFlatParamObject;
}

/**
* Gets model converters.
*
Expand Down
Expand Up @@ -238,7 +238,7 @@ PolymorphicModelConverter polymorphicModelConverter(ObjectMapperProvider objectM
@Lazy(false)
OpenAPIService openAPIBuilder(Optional<OpenAPI> openAPI,
SecurityService securityParser,
SpringDocConfigProperties springDocConfigProperties,PropertyResolverUtils propertyResolverUtils,
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
return new OpenAPIService(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
Expand Down Expand Up @@ -333,16 +333,18 @@ ReturnTypeParser genericReturnTypeParser() {
* @param optionalDelegatingMethodParameterCustomizer the optional delegating method parameter customizer
* @param optionalWebConversionServiceProvider the optional web conversion service provider
* @param objectMapperProvider the object mapper provider
* @param springDocConfigProperties the springdoc config properties
* @return the generic parameter builder
*/
@Bean
@ConditionalOnMissingBean
@Lazy(false)
GenericParameterService parameterBuilder(PropertyResolverUtils propertyResolverUtils,
Optional<DelegatingMethodParameterCustomizer> optionalDelegatingMethodParameterCustomizer,
Optional<WebConversionServiceProvider> optionalWebConversionServiceProvider, ObjectMapperProvider objectMapperProvider) {
Optional<WebConversionServiceProvider> optionalWebConversionServiceProvider, ObjectMapperProvider objectMapperProvider,
SpringDocConfigProperties springDocConfigProperties) {
return new GenericParameterService(propertyResolverUtils, optionalDelegatingMethodParameterCustomizer,
optionalWebConversionServiceProvider, objectMapperProvider);
optionalWebConversionServiceProvider, objectMapperProvider, springDocConfigProperties.isDefaultFlatParamObject());
}

/**
Expand Down Expand Up @@ -409,7 +411,7 @@ static BeanFactoryPostProcessor springdocBeanFactoryPostProcessor2() {
@Lazy(false)
SpringDocProviders springDocProviders(Optional<ActuatorProvider> actuatorProvider, Optional<CloudFunctionProvider> springCloudFunctionProvider, Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider,
Optional<RepositoryRestResourceProvider> repositoryRestResourceProvider, Optional<RouterFunctionProvider> routerFunctionProvider,
Optional<SpringWebProvider> springWebProvider, Optional<WebConversionServiceProvider> webConversionServiceProvider,
Optional<SpringWebProvider> springWebProvider, Optional<WebConversionServiceProvider> webConversionServiceProvider,
ObjectMapperProvider objectMapperProvider) {
return new SpringDocProviders(actuatorProvider, springCloudFunctionProvider, springSecurityOAuth2Provider, repositoryRestResourceProvider, routerFunctionProvider, springWebProvider, webConversionServiceProvider, objectMapperProvider);
}
Expand Down Expand Up @@ -609,7 +611,7 @@ CloudFunctionProvider springCloudFunctionProvider(Optional<FunctionCatalog> func
@Bean
@ConditionalOnMissingBean
@Lazy(false)
ObjectMapperProvider springDocObjectMapperProvider(SpringDocConfigProperties springDocConfigProperties){
ObjectMapperProvider springDocObjectMapperProvider(SpringDocConfigProperties springDocConfigProperties) {
return new ObjectMapperProvider(springDocConfigProperties);
}

Expand Down
Expand Up @@ -40,7 +40,6 @@
import io.swagger.v3.oas.models.parameters.Parameter;
import org.apache.commons.lang3.ArrayUtils;
import org.springdoc.core.AbstractRequestService;
import org.springdoc.core.DelegatingMethodParameter;
import org.springdoc.core.GenericParameterService;
import org.springdoc.core.MethodAttributes;
import org.springdoc.core.ParameterInfo;
Expand Down Expand Up @@ -149,7 +148,7 @@ public void buildParameters(OpenAPI openAPI, HandlerMethod handlerMethod, Reques
*/
public void buildCommonParameters(OpenAPI openAPI, RequestMethod requestMethod, MethodAttributes methodAttributes, Operation operation, String[] pNames, MethodParameter[] parameters,
DataRestRepository dataRestRepository) {
parameters = DelegatingMethodParameter.customize(pNames, parameters, parameterBuilder.getDelegatingMethodParameterCustomizer());
parameters = parameterBuilder.customize(pNames, parameters, parameterBuilder.getDelegatingMethodParameterCustomizer());
Class<?> domainType = dataRestRepository.getDomainType();
for (MethodParameter methodParameter : parameters) {
final String pName = methodParameter.getParameterName();
Expand Down
Expand Up @@ -37,4 +37,11 @@ public class Dog {
)
String displayName;

}
public String getDisplayName() {
return displayName;
}

public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
Expand Up @@ -27,4 +27,12 @@
public abstract class BaseClientModel {
@JsonProperty("id")
int id;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}
}
Expand Up @@ -27,4 +27,12 @@
public class SpecificClientModel extends BaseClientModel {
@JsonProperty("name")
String name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}