diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestService.java index cbf8d35fa..650b21c7e 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestService.java @@ -49,7 +49,12 @@ import com.fasterxml.jackson.annotation.JsonView; import io.swagger.v3.core.util.PrimitiveType; +import io.swagger.v3.oas.annotations.enums.Explode; import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.enums.ParameterStyle; +import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; @@ -164,6 +169,18 @@ public abstract class AbstractRequestService { */ private final Optional> parameterCustomizers; + private final DelegatingMethodParameterProvider delegatingMethodParameterProvider; + + /** + * + */ + private final boolean supportFormData; + + /** + * + */ + private final boolean schemaToParameter; + /** * Instantiates a new Abstract request builder. * @@ -175,7 +192,9 @@ public abstract class AbstractRequestService { */ protected AbstractRequestService(GenericParameterService parameterBuilder, RequestBodyService requestBodyService, OperationService operationService, Optional> parameterCustomizers, - LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer) { + LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer, + DelegatingMethodParameterProvider delegatingMethodParameterProvider, + SpringDocConfigProperties springDocConfigProperties) { super(); this.parameterBuilder = parameterBuilder; this.requestBodyService = requestBodyService; @@ -183,6 +202,9 @@ protected AbstractRequestService(GenericParameterService parameterBuilder, Reque parameterCustomizers.ifPresent(customizers -> customizers.removeIf(Objects::isNull)); this.parameterCustomizers = parameterCustomizers; this.localSpringDocParameterNameDiscoverer = localSpringDocParameterNameDiscoverer; + this.delegatingMethodParameterProvider = delegatingMethodParameterProvider; + this.supportFormData = springDocConfigProperties.isDefaultSupportFormData(); + this.schemaToParameter = springDocConfigProperties.isDefaultSupportFormData(); } /** @@ -237,7 +259,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 = delegatingMethodParameterProvider.customize(pNames, parameters, parameterBuilder.getDelegatingMethodParameterCustomizer()); RequestBodyInfo requestBodyInfo = new RequestBodyInfo(); List operationParameters = (operation.getParameters() != null) ? operation.getParameters() : new ArrayList<>(); Map parametersDocMap = getApiParameters(handlerMethod.getMethod()); @@ -257,6 +279,17 @@ public Operation build(HandlerMethod handlerMethod, RequestMethod requestMethod, if (parameterDoc == null) parameterDoc = parametersDocMap.get(parameterInfo.getpName()); + + if(this.schemaToParameter) { + if (parameterDoc == null) { + io.swagger.v3.oas.annotations.media.Schema schema = AnnotatedElementUtils.findMergedAnnotation( + AnnotatedElementUtils.forAnnotations(methodParameter.getParameterAnnotations()), io.swagger.v3.oas.annotations.media.Schema.class); + if (schema != null) { + parameterDoc = generateParameterBySchema(schema); + } + } + } + // use documentation as reference if (parameterDoc != null) { if (parameterDoc.hidden() || parameterDoc.schema().hidden()) @@ -300,6 +333,24 @@ else if (!RequestMethod.GET.equals(requestMethod)) { } LinkedHashMap map = getParameterLinkedHashMap(components, methodAttributes, operationParameters, parametersDocMap); + if(this.supportFormData) { + RequestBody body = requestBodyInfo.getRequestBody(); + if (body != null && body.getContent() != null && body.getContent().containsKey("multipart/form-data")) { + Set keys = map.keySet(); + io.swagger.v3.oas.models.media.Schema mergedSchema = requestBodyInfo.getMergedSchema(); + for (String key : keys) { + Parameter parameter = map.get(key); + io.swagger.v3.oas.models.media.Schema itemSchema = new io.swagger.v3.oas.models.media.Schema(); + itemSchema.setName(key); + itemSchema.setDescription(parameter.getDescription()); + itemSchema.setDeprecated(parameter.getDeprecated()); + itemSchema.setExample(parameter.getExample()); + itemSchema.setNullable(parameter.getAllowEmptyValue()); + mergedSchema.addProperty(key, itemSchema); + } + map.clear(); + } + } setParams(operation, new ArrayList<>(map.values()), requestBodyInfo); return operation; } @@ -313,7 +364,7 @@ else if (!RequestMethod.GET.equals(requestMethod)) { * @param parametersDocMap the parameters doc map * @return the parameter linked hash map */ - private LinkedHashMap getParameterLinkedHashMap(Components components, MethodAttributes methodAttributes, List operationParameters, Map parametersDocMap) { + protected LinkedHashMap getParameterLinkedHashMap(Components components, MethodAttributes methodAttributes, List operationParameters, Map parametersDocMap) { LinkedHashMap map = operationParameters.stream() .collect(Collectors.toMap( parameter -> parameter.getName() != null ? parameter.getName() : Integer.toString(parameter.hashCode()), @@ -400,7 +451,7 @@ public boolean isParamToIgnore(MethodParameter parameter) { * @param parameter the parameter * @return the boolean */ - private boolean isRequiredAnnotation(MethodParameter parameter) { + protected boolean isRequiredAnnotation(MethodParameter parameter) { RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class); PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class); org.springframework.web.bind.annotation.RequestBody requestBody = parameter.getParameterAnnotation(org.springframework.web.bind.annotation.RequestBody.class); @@ -416,7 +467,7 @@ private boolean isRequiredAnnotation(MethodParameter parameter) { * @param operationParameters the operation parameters * @param requestBodyInfo the request body info */ - private void setParams(Operation operation, List operationParameters, RequestBodyInfo requestBodyInfo) { + protected void setParams(Operation operation, List operationParameters, RequestBodyInfo requestBodyInfo) { if (!CollectionUtils.isEmpty(operationParameters)) operation.setParameters(operationParameters); if (requestBodyInfo.getRequestBody() != null) @@ -560,7 +611,7 @@ public void applyBeanValidatorAnnotations(final RequestBody requestBody, final L * @param annos the annos * @param schema the schema */ - private void calculateSize(Map annos, Schema schema) { + protected void calculateSize(Map annos, Schema schema) { if (annos.containsKey(Size.class.getSimpleName())) { Size size = (Size) annos.get(Size.class.getSimpleName()); if (OPENAPI_ARRAY_TYPE.equals(schema.getType())) { @@ -589,7 +640,7 @@ public RequestBodyService getRequestBodyBuilder() { * @param method the method * @return the api parameters */ - private Map getApiParameters(Method method) { + protected Map getApiParameters(Method method) { Class declaringClass = method.getDeclaringClass(); Set apiParametersDoc = AnnotatedElementUtils @@ -628,7 +679,7 @@ private Map getApiParameters(Me * @param annos the annos * @param schema the schema */ - private void applyValidationsToSchema(Map annos, Schema schema) { + protected void applyValidationsToSchema(Map annos, Schema schema) { if (annos.containsKey(Min.class.getSimpleName())) { Min min = (Min) annos.get(Min.class.getSimpleName()); schema.setMinimum(BigDecimal.valueOf(min.value())); @@ -669,7 +720,7 @@ private void applyValidationsToSchema(Map annos, Schema s * @param parameterInfo the parameter info * @return the boolean */ - private boolean isRequestBodyParam(RequestMethod requestMethod, ParameterInfo parameterInfo) { + protected boolean isRequestBodyParam(RequestMethod requestMethod, ParameterInfo parameterInfo) { MethodParameter methodParameter = parameterInfo.getMethodParameter(); DelegatingMethodParameter delegatingMethodParameter = (DelegatingMethodParameter) methodParameter; @@ -690,7 +741,7 @@ private boolean isRequestBodyParam(RequestMethod requestMethod, ParameterInfo pa * @param pName the p name * @return the param javadoc */ - private String getParamJavadoc(JavadocProvider javadocProvider, MethodParameter methodParameter, String pName) { + protected String getParamJavadoc(JavadocProvider javadocProvider, MethodParameter methodParameter, String pName) { DelegatingMethodParameter delegatingMethodParameter = (DelegatingMethodParameter) methodParameter; final String paramJavadocDescription; if (delegatingMethodParameter.isParameterObject()) { @@ -707,4 +758,98 @@ private String getParamJavadoc(JavadocProvider javadocProvider, MethodParameter return paramJavadocDescription; } + protected io.swagger.v3.oas.annotations.Parameter generateParameterBySchema(io.swagger.v3.oas.annotations.media.Schema schema) { + return new io.swagger.v3.oas.annotations.Parameter() { + + @Override + public Class annotationType() { + return io.swagger.v3.oas.annotations.Parameter.class; + } + + @Override + public String name() { + return schema.name(); + } + + @Override + public ParameterIn in() { + return ParameterIn.DEFAULT; + } + + @Override + public String description() { + return schema.description(); + } + + @Override + public boolean required() { + return schema.required(); + } + + @Override + public boolean deprecated() { + return schema.deprecated(); + } + + @Override + public boolean allowEmptyValue() { + return schema.nullable(); + } + + @Override + public ParameterStyle style() { + return ParameterStyle.DEFAULT; + } + + @Override + public Explode explode() { + return Explode.DEFAULT; + } + + @Override + public boolean allowReserved() { + return false; + } + + @Override + public io.swagger.v3.oas.annotations.media.Schema schema() { + return schema; + } + + @Override + public ArraySchema array() { + return null; + } + + @Override + public io.swagger.v3.oas.annotations.media.Content[] content() { + return new io.swagger.v3.oas.annotations.media.Content[0]; + } + + @Override + public boolean hidden() { + return schema.hidden(); + } + + @Override + public ExampleObject[] examples() { + return new ExampleObject[0]; + } + + @Override + public String example() { + return schema.example(); + } + + @Override + public Extension[] extensions() { + return schema.extensions(); + } + + @Override + public String ref() { + return schema.ref(); + } + }; + } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameter.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameter.java index 92c76bdd3..f74f6d5a5 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameter.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameter.java @@ -94,7 +94,7 @@ public class DelegatingMethodParameter extends MethodParameter { * @param isParameterObject the is parameter object * @param isNotRequired the is required */ - DelegatingMethodParameter(MethodParameter delegate, String parameterName, Annotation[] additionalParameterAnnotations, boolean isParameterObject, boolean isNotRequired) { + public DelegatingMethodParameter(MethodParameter delegate, String parameterName, Annotation[] additionalParameterAnnotations, boolean isParameterObject, boolean isNotRequired) { super(delegate); this.delegate = delegate; this.additionalParameterAnnotations = additionalParameterAnnotations; @@ -103,34 +103,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 optionalDelegatingMethodParameterCustomizer) { - List 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 public Annotation[] getParameterAnnotations() { @@ -251,27 +223,4 @@ public int hashCode() { public boolean isParameterObject() { return isParameterObject; } - - /** - * Return a variant of this {@code MethodParameter} which refers to the - * given containing class. - * @param methodParameter the method parameter - * @param containingClass a specific containing class (potentially a subclass of the declaring class, e.g. substituting a type variable) A copy of spring withContainingClass, to keep compatibility with older spring versions - * @return the method parameter - * @see #getParameterType() #getParameterType() - */ - public static MethodParameter changeContainingClass(MethodParameter methodParameter, @Nullable Class containingClass) { - MethodParameter result = methodParameter.clone(); - try { - Field containingClassField = FieldUtils.getDeclaredField(result.getClass(), "containingClass", true); - containingClassField.set(result, containingClass); - Field parameterTypeField = FieldUtils.getDeclaredField(result.getClass(), "parameterType", true); - parameterTypeField.set(result, null); - } - catch (IllegalAccessException e) { - LOGGER.warn(e.getMessage()); - } - return result; - } - -} \ No newline at end of file +} diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameterProvider.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameterProvider.java new file mode 100644 index 000000000..7dd019081 --- /dev/null +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameterProvider.java @@ -0,0 +1,116 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2022 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + */ +package org.springdoc.core; + +import io.swagger.v3.oas.annotations.parameters.RequestBody; +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.annotation.AnnotatedElementUtils; +import org.springframework.lang.Nullable; +import org.springframework.web.bind.annotation.RequestPart; + +import java.lang.reflect.*; +import java.util.*; + +/** + * The type Delegating method parameter utils. + * @author NaccOll + */ +public class DelegatingMethodParameterProvider { + /** + * The constant LOGGER. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(DelegatingMethodParameterProvider.class); + + private final boolean defaultParameterObject; + + public DelegatingMethodParameterProvider(boolean defaultParameterObject){ + this.defaultParameterObject = defaultParameterObject; + } + /** + * 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 optionalDelegatingMethodParameterCustomizer) { + List 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 (defaultParameterObject) { + boolean isSimpleType = MethodParameterPojoExtractor.isSimpleType(paramClass); + boolean isRequestBody = p.hasParameterAnnotation(RequestBody.class) || p.hasParameterAnnotation(org.springframework.web.bind.annotation.RequestBody.class); + boolean isRequestPart = p.hasParameterAnnotation(RequestPart.class); + if(!isSimpleType && !(isRequestBody || isRequestPart)) { + 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]); + } + + /** + * Return a variant of this {@code MethodParameter} which refers to the + * given containing class. + * @param methodParameter the method parameter + * @param containingClass a specific containing class (potentially a subclass of the declaring class, e.g. substituting a type variable) A copy of spring withContainingClass, to keep compatibility with older spring versions + * @return the method parameter + * @see DelegatingMethodParameter#getParameterType() + */ + public static MethodParameter changeContainingClass(MethodParameter methodParameter, @Nullable Class containingClass) { + MethodParameter result = methodParameter.clone(); + try { + Field containingClassField = FieldUtils.getDeclaredField(result.getClass(), "containingClass", true); + containingClassField.set(result, containingClass); + Field parameterTypeField = FieldUtils.getDeclaredField(result.getClass(), "parameterType", true); + parameterTypeField.set(result, null); + } + catch (IllegalAccessException e) { + LOGGER.warn(e.getMessage()); + } + return result; + } + +} diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java index 0c5137ef7..591e4338e 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java @@ -182,7 +182,7 @@ public static Parameter mergeParameter(List existingParamDoc, Paramet * @param paramCalcul the param calcul * @param paramDoc the param doc */ - private static void mergeParameter(Parameter paramCalcul, Parameter paramDoc) { + protected static void mergeParameter(Parameter paramCalcul, Parameter paramDoc) { if (StringUtils.isBlank(paramDoc.getDescription())) paramDoc.setDescription(paramCalcul.getDescription()); @@ -281,7 +281,7 @@ public Parameter buildParameterFromDoc(io.swagger.v3.oas.annotations.Parameter p * @param jsonView the json view * @param parameter the parameter */ - private void setSchema(io.swagger.v3.oas.annotations.Parameter parameterDoc, Components components, JsonView jsonView, Parameter parameter) { + protected void setSchema(io.swagger.v3.oas.annotations.Parameter parameterDoc, Components components, JsonView jsonView, Parameter parameter) { if (StringUtils.isNotBlank(parameterDoc.ref())) parameter.$ref(parameterDoc.ref()); else { @@ -322,7 +322,7 @@ private void setSchema(io.swagger.v3.oas.annotations.Parameter parameterDoc, Com * @param jsonView the json view * @return the schema */ - Schema calculateSchema(Components components, ParameterInfo parameterInfo, RequestBodyInfo requestBodyInfo, JsonView jsonView) { + public Schema calculateSchema(Components components, ParameterInfo parameterInfo, RequestBodyInfo requestBodyInfo, JsonView jsonView) { Schema schemaN; String paramName = parameterInfo.getpName(); MethodParameter methodParameter = parameterInfo.getMethodParameter(); @@ -356,7 +356,7 @@ Schema calculateSchema(Components components, ParameterInfo parameterInfo, Reque * @param paramName the param name * @return the schema */ - private Schema calculateRequestBodySchema(Components components, ParameterInfo parameterInfo, RequestBodyInfo requestBodyInfo, Schema schemaN, String paramName) { + protected Schema calculateRequestBodySchema(Components components, ParameterInfo parameterInfo, RequestBodyInfo requestBodyInfo, Schema schemaN, String paramName) { if (schemaN != null && StringUtils.isEmpty(schemaN.getDescription()) && parameterInfo.getParameterModel() != null) { String description = parameterInfo.getParameterModel().getDescription(); if (schemaN.get$ref() != null && schemaN.get$ref().contains(AnnotationsUtils.COMPONENTS_REF)) { @@ -392,7 +392,7 @@ else if (parameterInfo.isRequestPart() || schemaN instanceof FileSchema || schem * @param parameterDoc the parameter doc * @param parameter the parameter */ - private void setExamples(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter) { + protected void setExamples(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter) { Map exampleMap = new HashMap<>(); if (parameterDoc.examples().length == 1 && StringUtils.isBlank(parameterDoc.examples()[0].name())) { Optional exampleOptional = AnnotationsUtils.getExample(parameterDoc.examples()[0]); @@ -415,7 +415,7 @@ private void setExamples(io.swagger.v3.oas.annotations.Parameter parameterDoc, P * @param parameterDoc the parameter doc * @param parameter the parameter */ - private void setExtensions(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter) { + protected void setExtensions(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter) { if (parameterDoc.extensions().length > 0) { Map extensionMap = AnnotationsUtils.getExtensions(parameterDoc.extensions()); extensionMap.forEach(parameter::addExtension); @@ -428,7 +428,7 @@ private void setExtensions(io.swagger.v3.oas.annotations.Parameter parameterDoc, * @param parameter the parameter * @param p the p */ - private void setParameterExplode(Parameter parameter, io.swagger.v3.oas.annotations.Parameter p) { + protected void setParameterExplode(Parameter parameter, io.swagger.v3.oas.annotations.Parameter p) { if (isExplodable(p)) { if (Explode.TRUE.equals(p.explode())) { parameter.setExplode(Boolean.TRUE); @@ -445,7 +445,7 @@ else if (Explode.FALSE.equals(p.explode())) { * @param parameter the parameter * @param p the p */ - private void setParameterStyle(Parameter parameter, io.swagger.v3.oas.annotations.Parameter p) { + protected void setParameterStyle(Parameter parameter, io.swagger.v3.oas.annotations.Parameter p) { if (StringUtils.isNotBlank(p.style().toString())) { parameter.setStyle(Parameter.StyleEnum.valueOf(p.style().toString().toUpperCase())); } @@ -457,7 +457,7 @@ private void setParameterStyle(Parameter parameter, io.swagger.v3.oas.annotation * @param p the p * @return the boolean */ - private boolean isExplodable(io.swagger.v3.oas.annotations.Parameter p) { + protected boolean isExplodable(io.swagger.v3.oas.annotations.Parameter p) { io.swagger.v3.oas.annotations.media.Schema schema = p.schema(); io.swagger.v3.oas.annotations.media.ArraySchema arraySchema = p.array(); @@ -501,7 +501,7 @@ public Optional getDelegatingMethodParamete * @param parameterizedType the parameterized type * @return the boolean */ - private boolean isFile(ParameterizedType parameterizedType) { + protected boolean isFile(ParameterizedType parameterizedType) { Type type = parameterizedType.getActualTypeArguments()[0]; Class fileClass = ResolvableType.forType(type).getRawClass(); if (fileClass != null && isFile(fileClass)) diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java index 7e82058e6..76844ab44 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java @@ -57,7 +57,7 @@ * The type Method parameter pojo extractor. * @author bnasslahsen */ -class MethodParameterPojoExtractor { +public class MethodParameterPojoExtractor { /** * Instantiates a new Method parameter pojo extractor. @@ -177,7 +177,7 @@ private static Stream fromSimpleClass(Class paramClass, Fiel .map(PropertyDescriptor::getReadMethod) .filter(Objects::nonNull) .map(method -> new MethodParameter(method, -1)) - .map(methodParameter -> DelegatingMethodParameter.changeContainingClass(methodParameter, paramClass)) + .map(methodParameter -> DelegatingMethodParameterProvider.changeContainingClass(methodParameter, paramClass)) .map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), finalFieldAnnotations, true, isNotRequired)); } catch (IntrospectionException e) { @@ -206,7 +206,7 @@ private static List allFieldsOf(Class clazz) { * @param clazz the clazz * @return the boolean */ - static boolean isSimpleType(Class clazz) { + public static boolean isSimpleType(Class clazz) { return SIMPLE_TYPE_PREDICATES.stream().anyMatch(p -> p.test(clazz)) || SIMPLE_TYPES.stream().anyMatch(c -> c.isAssignableFrom(clazz)); } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/RequestBodyService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/RequestBodyService.java index 25836fd58..920b570a1 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/RequestBodyService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/RequestBodyService.java @@ -126,7 +126,7 @@ public Optional buildRequestBodyFromDoc( * @param methodConsumes the method consumes * @param requestBodyObject the request body object */ - private void buildResquestBodyContent(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes, Components components, JsonView jsonViewAnnotation, String[] classConsumes, String[] methodConsumes, RequestBody requestBodyObject) { + protected void buildResquestBodyContent(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes, Components components, JsonView jsonViewAnnotation, String[] classConsumes, String[] methodConsumes, RequestBody requestBodyObject) { Optional optionalContent = SpringDocAnnotationsUtils .getContent(requestBody.content(), getConsumes(classConsumes), getConsumes(methodConsumes), null, components, jsonViewAnnotation); @@ -161,7 +161,7 @@ private void buildResquestBodyContent(io.swagger.v3.oas.annotations.parameters.R * @param content the content * @return the boolean */ - private boolean containsResponseBodySchema(Content content) { + protected boolean containsResponseBodySchema(Content content) { return content.entrySet().stream().anyMatch(stringMediaTypeEntry -> stringMediaTypeEntry.getValue().getSchema() != null); } @@ -171,7 +171,7 @@ private boolean containsResponseBodySchema(Content content) { * @param classConsumes the class consumes * @return the string [ ] */ - private String[] getConsumes(String[] classConsumes) { + protected String[] getConsumes(String[] classConsumes) { return classConsumes == null ? new String[0] : classConsumes; } @@ -263,7 +263,7 @@ public void calculateRequestBodyInfo(Components components, MethodAttributes met * @param requestBodyInfo the request body info * @return the request body */ - private RequestBody buildRequestBody(RequestBody requestBody, Components components, + protected RequestBody buildRequestBody(RequestBody requestBody, Components components, MethodAttributes methodAttributes, ParameterInfo parameterInfo, RequestBodyInfo requestBodyInfo) { if (requestBody == null) { @@ -291,7 +291,7 @@ else if (!methodAttributes.isWithResponseBodySchemaDoc()) { * @param methodAttributes the method attributes * @param schema the schema */ - private void mergeContent(RequestBody requestBody, MethodAttributes methodAttributes, Schema schema) { + protected void mergeContent(RequestBody requestBody, MethodAttributes methodAttributes, Schema schema) { Content content = requestBody.getContent(); buildContent(requestBody, methodAttributes, schema, content); } @@ -303,7 +303,7 @@ private void mergeContent(RequestBody requestBody, MethodAttributes methodAttrib * @param methodAttributes the method attributes * @param schema the schema */ - private void buildContent(RequestBody requestBody, MethodAttributes methodAttributes, Schema schema) { + protected void buildContent(RequestBody requestBody, MethodAttributes methodAttributes, Schema schema) { Content content = new Content(); buildContent(requestBody, methodAttributes, schema, content); } @@ -316,7 +316,7 @@ private void buildContent(RequestBody requestBody, MethodAttributes methodAttrib * @param schema the schema * @param content the content */ - private void buildContent(RequestBody requestBody, MethodAttributes methodAttributes, Schema schema, Content content) { + protected void buildContent(RequestBody requestBody, MethodAttributes methodAttributes, Schema schema, Content content) { for (String value : methodAttributes.getMethodConsumes()) { io.swagger.v3.oas.models.media.MediaType mediaTypeObject = new io.swagger.v3.oas.models.media.MediaType(); mediaTypeObject.setSchema(schema); diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java index 1787f7bf4..eef32980b 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java @@ -179,6 +179,75 @@ public class SpringDocConfigProperties { */ private ModelConverters modelConverters = new ModelConverters(); + /** + * flat query object without ParameterObject + */ + private boolean defaultFlatQueryObject; + + /** + * convert query param to form data when consumes is multipart/form-data + */ + private boolean defaultSupportFormData; + + /** + * schema as parameter in a query object + */ + private boolean defaultSchemaToParameter; + + /** + * Is defaultFlatQueryObject boolean. + * + * @return the boolean + */ + public boolean isDefaultFlatQueryObject() { + return defaultFlatQueryObject; + } + + /** + * Sets defaultFlatQueryObject. + * + * @param defaultFlatQueryObject the enabled + */ + public void setDefaultFlatQueryObject(boolean defaultFlatQueryObject) { + this.defaultFlatQueryObject = defaultFlatQueryObject; + } + + /** + * Is defaultSupportFormData boolean. + * + * @return the boolean + */ + public boolean isDefaultSupportFormData() { + return defaultSupportFormData; + } + + /** + * Sets defaultSupportFormData. + * + * @param defaultSupportFormData the enabled + */ + public void setDefaultSupportFormData(boolean defaultSupportFormData) { + this.defaultSupportFormData = defaultSupportFormData; + } + + /** + * Is defaultSchemaToParameter boolean. + * + * @return the boolean + */ + public boolean isDefaultSchemaToParameter() { + return defaultSchemaToParameter; + } + + /** + * Sets defaultSchemaToParameter. + * + * @param defaultSchemaToParameter the enabled + */ + public void setDefaultSchemaToParameter(boolean defaultSchemaToParameter) { + this.defaultSchemaToParameter = defaultSchemaToParameter; + } + /** * Is show spring cloud functions boolean. * diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java index 9b780be72..4c5a13cad 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java @@ -411,6 +411,19 @@ SpringDocProviders springDocProviders(Optional actuatorProvide return new SpringDocProviders(actuatorProvider, springCloudFunctionProvider, springSecurityOAuth2Provider, repositoryRestResourceProvider, routerFunctionProvider, springWebProvider, webConversionServiceProvider, objectMapperProvider); } + /** + * Spring doc delegating method parameter provider + * + * @param springDocConfigProperties the spring doc config properties + * @return + */ + @Bean + @ConditionalOnMissingBean + @Lazy(false) + DelegatingMethodParameterProvider delegatingMethodParameterProvider(SpringDocConfigProperties springDocConfigProperties) { + return new DelegatingMethodParameterProvider(springDocConfigProperties.isDefaultFlatQueryObject()); + } + /** * The type Open api resource advice. * @author bnasslashen diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringDocDataRestConfiguration.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringDocDataRestConfiguration.java index 703121874..0a04ff37b 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringDocDataRestConfiguration.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringDocDataRestConfiguration.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.querydsl.core.types.Predicate; import org.springdoc.core.AbstractRequestService; +import org.springdoc.core.DelegatingMethodParameterProvider; import org.springdoc.core.GenericParameterService; import org.springdoc.core.GenericResponseService; import org.springdoc.core.OpenAPIService; @@ -208,9 +209,9 @@ DataRestOperationService dataRestOperationBuilder(DataRestRequestService dataRes @ConditionalOnMissingBean @Lazy(false) DataRestRequestService dataRestRequestBuilder(LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer, GenericParameterService parameterBuilder, - RequestBodyService requestBodyService, AbstractRequestService requestBuilder, SpringDocDataRestUtils springDocDataRestUtils) { + RequestBodyService requestBodyService, AbstractRequestService requestBuilder, SpringDocDataRestUtils springDocDataRestUtils, DelegatingMethodParameterProvider delegatingMethodParameterProvider) { return new DataRestRequestService(localSpringDocParameterNameDiscoverer, parameterBuilder, - requestBodyService, requestBuilder, springDocDataRestUtils); + requestBodyService, requestBuilder, springDocDataRestUtils, delegatingMethodParameterProvider); } /** diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestService.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestService.java index f07292575..6232b1c8e 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestService.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestService.java @@ -40,7 +40,7 @@ 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.DelegatingMethodParameterProvider; import org.springdoc.core.GenericParameterService; import org.springdoc.core.MethodAttributes; import org.springdoc.core.ParameterInfo; @@ -92,6 +92,11 @@ public class DataRestRequestService { */ private final SpringDocDataRestUtils springDocDataRestUtils; + /** + * The Spring doc delegating method parameter provider + */ + private final DelegatingMethodParameterProvider delegatingMethodParameterProvider; + /** * Instantiates a new Data rest request builder. * @@ -102,12 +107,14 @@ public class DataRestRequestService { * @param springDocDataRestUtils the spring doc data rest utils */ public DataRestRequestService(LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer, GenericParameterService parameterBuilder, - RequestBodyService requestBodyService, AbstractRequestService requestBuilder, SpringDocDataRestUtils springDocDataRestUtils) { + RequestBodyService requestBodyService, AbstractRequestService requestBuilder, SpringDocDataRestUtils springDocDataRestUtils, + DelegatingMethodParameterProvider delegatingMethodParameterProvider) { this.localSpringDocParameterNameDiscoverer = localSpringDocParameterNameDiscoverer; this.parameterBuilder = parameterBuilder; this.requestBodyService = requestBodyService; this.requestBuilder = requestBuilder; this.springDocDataRestUtils = springDocDataRestUtils; + this.delegatingMethodParameterProvider = delegatingMethodParameterProvider; } /** @@ -149,7 +156,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 = delegatingMethodParameterProvider.customize(pNames, parameters, parameterBuilder.getDelegatingMethodParameterCustomizer()); Class domainType = dataRestRepository.getDomainType(); for (MethodParameter methodParameter : parameters) { final String pName = methodParameter.getParameterName(); diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/RequestService.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/RequestService.java index 68f6dccb2..fb8d56868 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/RequestService.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/RequestService.java @@ -26,9 +26,11 @@ import java.util.Optional; import org.springdoc.core.AbstractRequestService; +import org.springdoc.core.DelegatingMethodParameterProvider; import org.springdoc.core.GenericParameterService; import org.springdoc.core.OperationService; import org.springdoc.core.RequestBodyService; +import org.springdoc.core.SpringDocConfigProperties; import org.springdoc.core.customizers.ParameterCustomizer; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; @@ -64,7 +66,9 @@ public class RequestService extends AbstractRequestService { */ public RequestService(GenericParameterService parameterBuilder, RequestBodyService requestBodyService, OperationService operationService, Optional> parameterCustomizers, - LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer) { - super(parameterBuilder, requestBodyService, operationService, parameterCustomizers, localSpringDocParameterNameDiscoverer); + LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer, + DelegatingMethodParameterProvider delegatingMethodParameterProvider, SpringDocConfigProperties springDocConfigProperties) { + super(parameterBuilder, requestBodyService, operationService, parameterCustomizers, localSpringDocParameterNameDiscoverer, + delegatingMethodParameterProvider, springDocConfigProperties); } } diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/SpringDocWebFluxConfiguration.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/SpringDocWebFluxConfiguration.java index b046d6158..131dc8360 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/SpringDocWebFluxConfiguration.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/SpringDocWebFluxConfiguration.java @@ -26,6 +26,7 @@ import java.util.Optional; import org.springdoc.core.AbstractRequestService; +import org.springdoc.core.DelegatingMethodParameterProvider; import org.springdoc.core.GenericParameterService; import org.springdoc.core.GenericResponseService; import org.springdoc.core.OpenAPIService; @@ -123,9 +124,11 @@ OpenApiWebfluxResource openApiResource(ObjectFactory openAPIBuil RequestService requestBuilder(GenericParameterService parameterBuilder, RequestBodyService requestBodyService, OperationService operationService, Optional> parameterCustomizers, - LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer) { + LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer, + DelegatingMethodParameterProvider delegatingMethodParameterProvider, SpringDocConfigProperties springDocConfigProperties) { return new RequestService(parameterBuilder, requestBodyService, - operationService, parameterCustomizers, localSpringDocParameterNameDiscoverer); + operationService, parameterCustomizers, localSpringDocParameterNameDiscoverer, + delegatingMethodParameterProvider, springDocConfigProperties); } /** diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/RequestService.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/RequestService.java index 4801a4c9d..9886e7559 100644 --- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/RequestService.java +++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/RequestService.java @@ -26,9 +26,11 @@ import java.util.Optional; import org.springdoc.core.AbstractRequestService; +import org.springdoc.core.DelegatingMethodParameterProvider; import org.springdoc.core.GenericParameterService; import org.springdoc.core.OperationService; import org.springdoc.core.RequestBodyService; +import org.springdoc.core.SpringDocConfigProperties; import org.springdoc.core.customizers.ParameterCustomizer; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; @@ -60,7 +62,9 @@ public class RequestService extends AbstractRequestService { */ public RequestService(GenericParameterService parameterBuilder, RequestBodyService requestBodyService, OperationService operationService, Optional> parameterCustomizers, - LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer) { - super(parameterBuilder, requestBodyService, operationService, parameterCustomizers, localSpringDocParameterNameDiscoverer); + LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer, + DelegatingMethodParameterProvider delegatingMethodParameterProvider, SpringDocConfigProperties springDocConfigProperties) { + super(parameterBuilder, requestBodyService, operationService, parameterCustomizers, localSpringDocParameterNameDiscoverer, + delegatingMethodParameterProvider, springDocConfigProperties); } } diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/SpringDocWebMvcConfiguration.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/SpringDocWebMvcConfiguration.java index 4121bc358..a347b21a2 100644 --- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/SpringDocWebMvcConfiguration.java +++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/SpringDocWebMvcConfiguration.java @@ -26,6 +26,7 @@ import java.util.Optional; import org.springdoc.core.AbstractRequestService; +import org.springdoc.core.DelegatingMethodParameterProvider; import org.springdoc.core.GenericParameterService; import org.springdoc.core.GenericResponseService; import org.springdoc.core.OpenAPIService; @@ -129,9 +130,11 @@ OpenApiWebMvcResource openApiResource(ObjectFactory openAPIBuild @Lazy(false) RequestService requestBuilder(GenericParameterService parameterBuilder, RequestBodyService requestBodyService, OperationService operationService, Optional> parameterCustomizers, - LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer) { + LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer, DelegatingMethodParameterProvider delegatingMethodParameterProvider, + SpringDocConfigProperties springDocConfigProperties) { return new RequestService(parameterBuilder, requestBodyService, - operationService, parameterCustomizers, localSpringDocParameterNameDiscoverer); + operationService, parameterCustomizers, localSpringDocParameterNameDiscoverer, + delegatingMethodParameterProvider, springDocConfigProperties); } /**