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

⚡ support(kotlin&customize&validation&generic) SchemaUtils #2950

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
Expand Up @@ -31,6 +31,7 @@ import org.springdoc.core.customizers.KotlinDeprecatedPropertyCustomizer
import org.springdoc.core.customizers.ParameterCustomizer
import org.springdoc.core.providers.ObjectMapperProvider
import org.springdoc.core.utils.Constants
import org.springdoc.core.utils.SchemaUtils
import org.springdoc.core.utils.SpringDocUtils
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
Expand Down Expand Up @@ -75,17 +76,13 @@ class SpringDocKotlinConfiguration() {
}

/**
* Kotlin springdoc-openapi ParameterCustomizer
* Kotlin springdoc-openapi ParameterCustomizer.
* deprecated as not anymore required, use [SchemaUtils.fieldNullable]
*
* @return the nullable Kotlin Request Parameter Customizer
* @see SchemaUtils.fieldNullable()
*/
@Bean
@Lazy(false)
@ConditionalOnProperty(
name = [Constants.SPRINGDOC_NULLABLE_REQUEST_PARAMETER_ENABLED],
matchIfMissing = true
)
@ConditionalOnMissingBean
@Deprecated("Deprecated since 2.8.7", level = DeprecationLevel.ERROR)
fun nullableKotlinRequestParameterCustomizer(): ParameterCustomizer {
return ParameterCustomizer { parameterModel, methodParameter ->
if (parameterModel == null) return@ParameterCustomizer null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,27 @@
package org.springdoc.core.customizers;

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

import java.util.List;

/**
* The interface Delegating method parameter customizer.
* @author dyun
*/
@FunctionalInterface
public interface DelegatingMethodParameterCustomizer {
/**
* Customize.
* tip: parameters include the parent fields, you can choose how to deal with the methodParameters
*
* @param originalParameter the original parameter
* @param methodParameters the exploded parameters
*/
@Nullable
default void customizeList(MethodParameter originalParameter, List<MethodParameter> methodParameters) {
methodParameters.forEach(parameter -> customize(originalParameter, parameter));
}

/**
* Customize.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ private void addParameters(OpenAPI openAPI, RequestMethod requestMethod, MethodA
MethodParameter methodParameter, ParameterInfo parameterInfo, Parameter parameter) {
List<Annotation> parameterAnnotations = Arrays.asList(getParameterAnnotations(methodParameter));
if (requestBuilder.isValidParameter(parameter,methodAttributes)) {
requestBuilder.applyBeanValidatorAnnotations(parameter, parameterAnnotations, parameterInfo.isParameterObject());
requestBuilder.applyBeanValidatorAnnotations(methodParameter, parameter, parameterAnnotations, parameterInfo.isParameterObject());
operation.addParametersItem(parameter);
}
else if (!RequestMethod.GET.equals(requestMethod)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
Expand Down Expand Up @@ -88,6 +89,11 @@ public class DelegatingMethodParameter extends MethodParameter {
*/
private final boolean isParameterObject;

/**
* If Is parameter object. then The Field should be not null
*/
private final Field field;

/**
* The Method annotations.
*/
Expand All @@ -108,9 +114,10 @@ public class DelegatingMethodParameter extends MethodParameter {
* @param isParameterObject the is parameter object
* @param isNotRequired the is required
*/
DelegatingMethodParameter(MethodParameter delegate, String parameterName, Annotation[] additionalParameterAnnotations, Annotation[] methodAnnotations, boolean isParameterObject, boolean isNotRequired) {
DelegatingMethodParameter(MethodParameter delegate, String parameterName, Annotation[] additionalParameterAnnotations, Annotation[] methodAnnotations, boolean isParameterObject, Field field, boolean isNotRequired) {
super(delegate);
this.delegate = delegate;
this.field = field;
this.additionalParameterAnnotations = additionalParameterAnnotations;
this.parameterName = parameterName;
this.isParameterObject = isParameterObject;
Expand Down Expand Up @@ -139,14 +146,14 @@ public static MethodParameter[] customize(String[] pNames, MethodParameter[] par
.anyMatch(annotation -> Arrays.asList(RequestBody.class, RequestPart.class).contains(annotation.annotationType()));
if (!MethodParameterPojoExtractor.isSimpleType(paramClass)
&& (hasFlatAnnotation || (defaultFlatParamObject && !hasNotFlatAnnotation && !AbstractRequestService.isRequestTypeToIgnore(paramClass)))) {
MethodParameterPojoExtractor.extractFrom(paramClass).forEach(methodParameter -> {
optionalDelegatingMethodParameterCustomizers.ifPresent(delegatingMethodParameterCustomizers -> delegatingMethodParameterCustomizers.forEach(customizer -> customizer.customize(p, methodParameter)));
explodedParameters.add(methodParameter);
});
List<MethodParameter> flatParams = new CopyOnWriteArrayList<>();
MethodParameterPojoExtractor.extractFrom(paramClass).forEach(flatParams::add);
optionalDelegatingMethodParameterCustomizers.orElseGet(ArrayList::new).forEach(cz -> cz.customizeList(p, flatParams));
explodedParameters.addAll(flatParams);
}
else {
String name = pNames != null ? pNames[i] : p.getParameterName();
explodedParameters.add(new DelegatingMethodParameter(p, name, null, null, false, false));
explodedParameters.add(new DelegatingMethodParameter(p, name, null, null, false, null, false));
}
}
return explodedParameters.toArray(new MethodParameter[0]);
Expand Down Expand Up @@ -297,4 +304,13 @@ public boolean isParameterObject() {
return isParameterObject;
}

}
/**
* Gets field. If Is parameter object. then The Field should be not null
* @return the field
* @see #isParameterObject
*/
@Nullable
public Field getField() {
return field;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -61,17 +59,16 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.swagger.v3.core.util.PrimitiveType;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;

import org.springdoc.core.utils.SchemaUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;

import static org.springdoc.core.service.AbstractRequestService.hasNotNullAnnotation;
import static org.springdoc.core.utils.Constants.DOT;

/**
Expand Down Expand Up @@ -174,13 +171,13 @@ private static Stream<MethodParameter> fromGetterOfField(Class<?> paramClass, Fi
else {
Parameter parameter = field.getAnnotation(Parameter.class);
Schema schema = field.getAnnotation(Schema.class);
boolean visible = resolveVisible(parameter, schema);
boolean visible = SchemaUtils.swaggerVisible(schema, parameter);
if (!visible) {
return Stream.empty();
}
String prefix = fieldNamePrefix + resolveName(parameter, schema).orElse(field.getName()) + DOT;
boolean isNullable = isNullable(field.getDeclaredAnnotations());
return extractFrom(type, prefix, parentRequired && resolveRequired(schema, parameter, isNullable));
boolean fieldRequired = SchemaUtils.fieldRequired(field, schema, parameter);
return extractFrom(type, prefix, parentRequired && fieldRequired);
}
}

Expand Down Expand Up @@ -208,46 +205,6 @@ private static Optional<String> resolveNameFromSchema(Schema schema) {
return Optional.of(schema.name());
}

private static boolean resolveVisible(Parameter parameter, Schema schema) {
if (parameter != null) {
return !parameter.hidden();
}
if (schema != null) {
return !schema.hidden();
}
return true;
}

private static boolean resolveRequired(Schema schema, Parameter parameter, boolean nullable) {
if (parameter != null) {
return resolveRequiredFromParameter(parameter, nullable);
}
if (schema != null) {
return resolveRequiredFromSchema(schema, nullable);
}
return !nullable;
}

private static boolean resolveRequiredFromParameter(Parameter parameter, boolean nullable) {
if (parameter.required()) {
return true;
}
return !nullable;
}

private static boolean resolveRequiredFromSchema(Schema schema, boolean nullable) {
if (schema.required()) {
return true;
}
else if (schema.requiredMode() == Schema.RequiredMode.REQUIRED) {
return true;
}
else if (schema.requiredMode() == Schema.RequiredMode.NOT_REQUIRED) {
return false;
}
return !nullable;
}

/**
* Extract the type
*
Expand Down Expand Up @@ -277,20 +234,21 @@ private static Class<?> extractType(Class<?> paramClass, Field field) {
* @param fieldNamePrefix the field name prefix
* @return the stream
*/
private static Stream<MethodParameter> fromSimpleClass(Class<?> paramClass, Field field, String fieldNamePrefix, boolean isParentRequired) {
private static Stream<MethodParameter> fromSimpleClass(Class<?> paramClass, Field field, String fieldNamePrefix, boolean parentRequired) {
Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
try {
Parameter parameter = field.getAnnotation(Parameter.class);
Schema schema = field.getAnnotation(Schema.class);
boolean isNullable = isNullable(fieldAnnotations);
boolean isNotRequired = !(isParentRequired && resolveRequired(schema, parameter, isNullable));
boolean fieldRequired = SchemaUtils.fieldRequired(field, schema, parameter);

boolean paramRequired = parentRequired && fieldRequired;
if (paramClass.getSuperclass() != null && paramClass.isRecord()) {
return Stream.of(paramClass.getRecordComponents())
.filter(d -> d.getName().equals(field.getName()))
.map(RecordComponent::getAccessor)
.map(method -> new MethodParameter(method, -1))
.map(methodParameter -> DelegatingMethodParameter.changeContainingClass(methodParameter, paramClass))
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), fieldAnnotations, param.getMethodAnnotations(), true, isNotRequired));
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), fieldAnnotations, param.getMethodAnnotations(), true, field, !paramRequired));
}
else
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
Expand All @@ -299,15 +257,15 @@ private static Stream<MethodParameter> fromSimpleClass(Class<?> paramClass, Fiel
.filter(Objects::nonNull)
.map(method -> new MethodParameter(method, -1))
.map(methodParameter -> DelegatingMethodParameter.changeContainingClass(methodParameter, paramClass))
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), fieldAnnotations, param.getMethodAnnotations(), true, isNotRequired));
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), fieldAnnotations, param.getMethodAnnotations(), true, field, !paramRequired));
}
catch (IntrospectionException e) {
return Stream.of();
}
}

/**
* All fields of list.
* All fields of list. include parent fields
*
* @param clazz the clazz
* @return the list
Expand Down Expand Up @@ -370,17 +328,5 @@ public static void removeSimpleTypes(Class<?>... classes) {
SIMPLE_TYPES.removeAll(Arrays.asList(classes));
}

/**
* Is nullable boolean.
*
* @param fieldAnnotations the field annotations
* @return the boolean
*/
private static boolean isNullable(Annotation[] fieldAnnotations) {
Collection<String> annotationSimpleNames = Arrays.stream(fieldAnnotations)
.map(Annotation::annotationType)
.map(Class::getSimpleName)
.collect(Collectors.toCollection(LinkedHashSet::new));
return !hasNotNullAnnotation(annotationSimpleNames);
}

}
Loading