diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/DelegatingMethodParameter.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/DelegatingMethodParameter.java index f9450f512..6db652d55 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/DelegatingMethodParameter.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/DelegatingMethodParameter.java @@ -196,11 +196,13 @@ public String getParameterName() { } @Override + @Nullable public Method getMethod() { return delegate.getMethod(); } @Override + @Nullable public Constructor getConstructor() { return delegate.getConstructor(); } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java index c35614bcf..d9bc96ce8 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java @@ -390,7 +390,7 @@ Schema calculateSchema(Components components, ParameterInfo parameterInfo, Reque Annotation[] paramAnnotations = getParameterAnnotations(methodParameter); TypeAndTypeAnnotations resolved = resolveTypeAndTypeAnnotationsForParameter(methodParameter); Type type = resolved.type(); - Annotation[] typeAnnotations = resolved.typeAnnotations(); + Annotation[] typeAnnotations = resolved.typeAnnotations().toArray(Annotation[]::new); Annotation[] mergedAnnotations = Stream.concat( Arrays.stream(paramAnnotations), @@ -439,7 +439,7 @@ private TypeAndTypeAnnotations resolveTypeAndTypeAnnotationsForParameter(MethodP && delegatingMethodParameter.getField() != null) { AnnotatedType annotated = delegatingMethodParameter.getField().getAnnotatedType(); Type type = GenericTypeResolver.resolveType(annotated.getType(), methodParameter.getContainingClass()); - return new TypeAndTypeAnnotations(type, annotationsFromAnnotatedTypeArguments(annotated)); + return new TypeAndTypeAnnotations(type, Arrays.asList(annotationsFromAnnotatedTypeArguments(annotated))); } Type type = GenericTypeResolver.resolveType(methodParameter.getGenericParameterType(), methodParameter.getContainingClass()); @@ -449,17 +449,17 @@ private TypeAndTypeAnnotations resolveTypeAndTypeAnnotationsForParameter(MethodP && type == String.class) { Class restored = KotlinInlineParameterResolver.resolveInlineType(methodParameter, type); return restored != null - ? new TypeAndTypeAnnotations(restored, restored.getAnnotations()) - : new TypeAndTypeAnnotations(type, new Annotation[0]); + ? new TypeAndTypeAnnotations(restored, Arrays.asList(restored.getAnnotations())) + : new TypeAndTypeAnnotations(type, new ArrayList<>()); } - return new TypeAndTypeAnnotations(type, methodParameter.getParameterType().getAnnotations()); + return new TypeAndTypeAnnotations(type, Arrays.asList(methodParameter.getParameterType().getAnnotations())); } /** * Pair of resolved Java type and type annotations merged with parameter annotations for {@code extractSchema}. */ - private record TypeAndTypeAnnotations(Type type, Annotation[] typeAnnotations) { + private record TypeAndTypeAnnotations(Type type, List typeAnnotations) { } /** diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OperationService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OperationService.java index 709392cff..ae5d5670b 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OperationService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OperationService.java @@ -248,7 +248,9 @@ private void setPathItemOperation(PathItem pathItemObject, String method, Operat case TRACE_METHOD -> pathItemObject.trace(operation); case HEAD_METHOD -> pathItemObject.head(operation); case OPTIONS_METHOD -> pathItemObject.options(operation); - default -> { } + default -> { + // best effort with regard to supported operations + } } } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SchemaUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SchemaUtils.java index c157f8869..3a5708764 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SchemaUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SchemaUtils.java @@ -163,13 +163,17 @@ public boolean fieldNullable(Field field) { } // @Nullable/@NotNull - Boolean ann = nullableFromAnnotations(field); - if (ann != null) return ann; + Optional ann = nullableFromAnnotations(field); + if (ann.isPresent()) { + return ann.get(); + } // Kotlin nullability if (kotlinUtilsOptional.isPresent() && isKotlinDeclaringClass(field)) { - Boolean kotlin = kotlinNullability(field); - if (kotlin != null) return kotlin; + Optional kotlin = kotlinNullability(field); + if (kotlin.isPresent()) { + return kotlin.get(); + } } return JAVA_FIELD_NULLABLE_DEFAULT; @@ -204,17 +208,15 @@ public boolean fieldRequired(Field field, io.swagger.v3.oas.annotations.media.Sc // Kotlin logic if (kotlinUtilsOptional.isPresent() && isKotlinDeclaringClass(field)) { if (fieldNullable(field)) return false; - Boolean hasDefault = kotlinConstructorParamIsOptional(field); - if (Boolean.TRUE.equals(hasDefault)) return false; - return true; - } + return kotlinConstructorParamIsOptional(field) + .map(isOptional -> !isOptional) + .orElse(true); + } // Jackson @JsonProperty(required = true) JsonProperty jp = getJsonProperty(field); - if (jp != null && jp.required()) return true; - - return false; - } + return jp != null && jp.required(); + } /** * Apply validations to schema. the annotation order effects the result of the @@ -376,15 +378,15 @@ private static List> findOverrides(Annotation annota * @param field the field * @return the boolean */ - private static Boolean nullableFromAnnotations(Field field) { - if (hasNullableAnnotation(field)) return true; - if (hasNotNullAnnotation(field)) return false; + private static Optional nullableFromAnnotations(Field field) { + if (hasNullableAnnotation(field)) return Optional.of(true); + if (hasNotNullAnnotation(field)) return Optional.of(false); Method getter = findGetter(field); if (getter != null) { - if (hasNullableAnnotation(getter)) return true; - if (hasNotNullAnnotation(getter)) return false; + if (hasNullableAnnotation(getter)) return Optional.of(true); + if (hasNotNullAnnotation(getter)) return Optional.of(false); } - return null; + return Optional.empty(); } /** @@ -579,15 +581,13 @@ public static void fixOAS31ExclusiveConstraints(Schema schema) { return; } if (schema.getSpecVersion().equals(SpecVersion.V31)) { - if (schema.getExclusiveMaximumValue() != null && schema.getMaximum() != null) { - if (schema.getMaximum().compareTo(schema.getExclusiveMaximumValue()) == 0) { - schema.setMaximum(null); - } + if (schema.getExclusiveMaximumValue() != null && schema.getMaximum() != null + && schema.getMaximum().compareTo(schema.getExclusiveMaximumValue()) == 0) { + schema.setMaximum(null); } - if (schema.getExclusiveMinimumValue() != null && schema.getMinimum() != null) { - if (schema.getMinimum().compareTo(schema.getExclusiveMinimumValue()) == 0) { - schema.setMinimum(null); - } + if (schema.getExclusiveMinimumValue() != null && schema.getMinimum() != null && + schema.getMinimum().compareTo(schema.getExclusiveMinimumValue()) == 0) { + schema.setMinimum(null); } } } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocKotlinUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocKotlinUtils.java index a40cc5152..278f5c3b9 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocKotlinUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocKotlinUtils.java @@ -27,6 +27,7 @@ package org.springdoc.core.utils; import java.lang.reflect.Field; +import java.util.Optional; import kotlin.jvm.JvmClassMappingKt; import kotlin.reflect.KClass; @@ -46,6 +47,9 @@ */ public class SpringDocKotlinUtils { + public SpringDocKotlinUtils() { + } + /** * Is kotlin declaring class boolean. * @@ -64,19 +68,18 @@ static boolean isKotlinDeclaringClass(Field f) { * @param f the f * @return the boolean */ - private static Boolean kotlinMarkedNullableFallback(Field f) { + private static Optional kotlinMarkedNullableFallback(Field f) { try { KClass kClass = JvmClassMappingKt.getKotlinClass(f.getDeclaringClass()); - for (Object pObj : KClasses.getMemberProperties((KClass) kClass)) { - KProperty1 p = (KProperty1) pObj; - if (p.getName().equals(f.getName())) { - return p.getReturnType().isMarkedNullable(); + for (KProperty1 pObj : KClasses.getMemberProperties(kClass)) { + if (pObj.getName().equals(f.getName())) { + return Optional.of(pObj.getReturnType().isMarkedNullable()); } } } catch (Exception ignored) { // best-effort only } - return null; + return Optional.empty(); } /** @@ -85,21 +88,21 @@ private static Boolean kotlinMarkedNullableFallback(Field f) { * @param f the f * @return the boolean */ - static Boolean kotlinConstructorParamIsOptional(Field f) { + static Optional kotlinConstructorParamIsOptional(Field f) { try { KClass kClass = JvmClassMappingKt.getKotlinClass(f.getDeclaringClass()); KFunction primary = KClasses.getPrimaryConstructor(kClass); if (primary != null) { for (KParameter p : primary.getParameters()) { if (f.getName().equals(p.getName())) { - return p.isOptional(); + return Optional.of(p.isOptional()); } } } } catch (Exception ignored) { // best-effort only } - return null; + return Optional.empty(); } /** @@ -108,12 +111,12 @@ static Boolean kotlinConstructorParamIsOptional(Field f) { * @param field the field * @return the boolean */ - static Boolean kotlinNullability(Field field) { - if (!isKotlinDeclaringClass(field)) return null; + static Optional kotlinNullability(Field field) { + if (!isKotlinDeclaringClass(field)) return Optional.empty(); KProperty prop = ReflectJvmMapping.getKotlinProperty(field); if (prop != null) { - return prop.getReturnType().isMarkedNullable(); + return Optional.of(prop.getReturnType().isMarkedNullable()); } return kotlinMarkedNullableFallback(field); diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java index 849aa3255..d1007e7e4 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java @@ -201,10 +201,9 @@ public static void fixNullOnlyAdditionalProperties(Schema schema) { if (additionalProperties instanceof Schema addPropSchema) { boolean isNullOnlyType = false; Set types = addPropSchema.getTypes(); - if (types != null && types.size() == 1 && types.contains("null")) { - isNullOnlyType = true; - } - else if (types == null && "null".equals(addPropSchema.getType())) { + boolean onlyNullTypeOAS31 = types != null && types.size() == 1 && types.contains("null"); + boolean onlyNullTypeOAS30 = types == null && "null".equals(addPropSchema.getType()); + if (onlyNullTypeOAS31 || onlyNullTypeOAS30) { isNullOnlyType = true; } if (isNullOnlyType && addPropSchema.get$ref() == null @@ -214,7 +213,7 @@ else if (types == null && "null".equals(addPropSchema.getType())) { } } if (schema.getProperties() != null) { - schema.getProperties().values().forEach(prop -> fixNullOnlyAdditionalProperties((Schema) prop)); + schema.getProperties().values().forEach(SpringDocUtils::fixNullOnlyAdditionalProperties); } } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/scalar/ScalarConstants.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/scalar/ScalarConstants.java index 3424529a7..625f9eb70 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/scalar/ScalarConstants.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/scalar/ScalarConstants.java @@ -33,6 +33,9 @@ */ public class ScalarConstants { + private ScalarConstants() { + } + /** * The constant SCALAR_DEFAULT_PATH. */ diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java index b0e47f2cc..2e2d84e92 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java @@ -27,7 +27,9 @@ package org.springdoc.ui; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.springdoc.core.properties.SwaggerUiConfigProperties; @@ -185,10 +187,10 @@ private PathPattern parsePattern(String pattern) { * @param patterns the patterns to match. * @param locations the locations to use. */ - protected record SwaggerResourceHandlerConfig(boolean cacheResources, String[] patterns, String[] locations) { + protected record SwaggerResourceHandlerConfig(boolean cacheResources, List patterns, List locations) { private SwaggerResourceHandlerConfig(boolean cacheResources) { - this(cacheResources, new String[]{}, new String[]{}); + this(cacheResources, new ArrayList<>(), new ArrayList<>()); } /** @@ -199,7 +201,15 @@ private SwaggerResourceHandlerConfig(boolean cacheResources) { * @return the updated config. */ public SwaggerResourceHandlerConfig setPatterns(String... patterns) { - return new SwaggerResourceHandlerConfig(cacheResources, patterns, locations); + return new SwaggerResourceHandlerConfig(cacheResources, Arrays.asList(patterns), locations); + } + + /** + * + * @return String array of patterns + */ + public String[] patternsArray() { + return patterns.toArray(new String[0]); } /** @@ -210,7 +220,15 @@ public SwaggerResourceHandlerConfig setPatterns(String... patterns) { * @return the updated config. */ public SwaggerResourceHandlerConfig setLocations(String... locations) { - return new SwaggerResourceHandlerConfig(cacheResources, patterns, locations); + return new SwaggerResourceHandlerConfig(cacheResources, patterns, Arrays.asList(locations)); + } + + /** + * + * @return String array of locations + */ + public String[] locationsArray() { + return locations.toArray(new String[0]); } /** diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java index 6e88fa647..60917decd 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java @@ -127,8 +127,8 @@ protected void addSwaggerResourceHandlers(ResourceHandlerRegistry registry, Swag * @param handlerConfig the swagger handler config. */ protected void addSwaggerResourceHandler(ResourceHandlerRegistry registry, SwaggerResourceHandlerConfig handlerConfig) { - ResourceHandlerRegistration handlerRegistration = registry.addResourceHandler(handlerConfig.patterns()); - handlerRegistration.addResourceLocations(handlerConfig.locations()); + ResourceHandlerRegistration handlerRegistration = registry.addResourceHandler(handlerConfig.patternsArray()); + handlerRegistration.addResourceLocations(handlerConfig.locationsArray()); ResourceChainRegistration chainRegistration; if (handlerConfig.cacheResources()) { diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java index 87bcc2156..3d0bf0d74 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java @@ -145,8 +145,8 @@ protected void addSwaggerResourceHandlers(ResourceHandlerRegistry registry, Swag * @param handlerConfig the swagger handler config. */ protected void addSwaggerResourceHandler(ResourceHandlerRegistry registry, SwaggerResourceHandlerConfig handlerConfig) { - ResourceHandlerRegistration handlerRegistration = registry.addResourceHandler(handlerConfig.patterns()); - handlerRegistration.addResourceLocations(handlerConfig.locations()); + ResourceHandlerRegistration handlerRegistration = registry.addResourceHandler(handlerConfig.patternsArray()); + handlerRegistration.addResourceLocations(handlerConfig.locationsArray()); ResourceChainRegistration chainRegistration; if (handlerConfig.cacheResources()) {