From 2b2ea255773b11d53d33ffd1d48c30aa149293fe Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Wed, 6 Aug 2025 14:55:04 +0200 Subject: [PATCH 1/6] update swagger tooling for code generator --- .../RegisterCustomTypesWithSwagger.java | 19 -- .../toolbox/swagger/SwaggerMeta.java | 2 + .../toolbox/swagger/SwaggerMetaUtil.java | 8 + .../annotations/SwaggerScopedAuth.java | 12 - .../AuthorizationDescriptor.java | 50 ----- .../CustomTypeModelConverter.java | 2 +- ...TypeOpenApiCustomizerForCodeGenerator.java | 53 +++++ .../CustomTypeParameterCustomizer.java | 210 ++++++++++++++++++ ...peParameterCustomizerForCodeGenerator.java | 70 ++++++ .../CustomTypePropertyCustomizer.java | 2 +- ...ypePropertyCustomizerForCodeGenerator.java | 29 +++ .../default_not_null/NullableCustomizer.java | 34 ++- .../NullablePropertyCustomizer.java | 36 ++- .../error_response/ErrorCustomizer.java | 66 ------ .../NestedStructuresCustomizer.java | 40 ++++ 15 files changed, 474 insertions(+), 159 deletions(-) delete mode 100644 src/main/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/RegisterCustomTypesWithSwagger.java delete mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/annotations/SwaggerScopedAuth.java delete mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/authorization_docs/AuthorizationDescriptor.java rename src/main/java/it/aboutbits/springboot/toolbox/swagger/{type => customization/custom_type}/CustomTypeModelConverter.java (98%) create mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeOpenApiCustomizerForCodeGenerator.java create mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java create mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizerForCodeGenerator.java rename src/main/java/it/aboutbits/springboot/toolbox/swagger/{type => customization/custom_type}/CustomTypePropertyCustomizer.java (98%) create mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypePropertyCustomizerForCodeGenerator.java delete mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/error_response/ErrorCustomizer.java create mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/nested_structure/NestedStructuresCustomizer.java diff --git a/src/main/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/RegisterCustomTypesWithSwagger.java b/src/main/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/RegisterCustomTypesWithSwagger.java deleted file mode 100644 index cb2cc8e..0000000 --- a/src/main/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/RegisterCustomTypesWithSwagger.java +++ /dev/null @@ -1,19 +0,0 @@ -package it.aboutbits.springboot.toolbox.autoconfiguration.swagger; - -import it.aboutbits.springboot.toolbox.swagger.type.CustomTypeModelConverter; -import it.aboutbits.springboot.toolbox.swagger.type.CustomTypePropertyCustomizer; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Import({ - CustomTypeModelConverter.class, - CustomTypePropertyCustomizer.class -}) -public @interface RegisterCustomTypesWithSwagger { -} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMeta.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMeta.java index 20308aa..0198863 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMeta.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMeta.java @@ -12,6 +12,8 @@ public class SwaggerMeta { private String originalTypeFqn = null; @JsonProperty("isIdentity") private Boolean isIdentity = null; + @JsonProperty("isCustomType") + private Boolean isCustomType = null; @JsonProperty("isNestedStructure") private Boolean isNestedStructure = null; @JsonProperty("isMap") diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMetaUtil.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMetaUtil.java index 5332d0b..f1d46ed 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMetaUtil.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMetaUtil.java @@ -46,6 +46,14 @@ public static String setIsIdentity(@Nullable String currentMeta, boolean value) return OBJECT_MAPPER.writeValueAsString(meta); } + @SneakyThrows(JsonProcessingException.class) + public static String setIsCustomType(@Nullable String currentMeta, boolean value) { + var meta = getSwaggerMeta(currentMeta); + meta.setIsCustomType(!value ? null : true); + + return OBJECT_MAPPER.writeValueAsString(meta); + } + @SneakyThrows(JsonProcessingException.class) public static String setIsNestedStructure(@Nullable String currentMeta, boolean value) { var meta = getSwaggerMeta(currentMeta); diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/annotations/SwaggerScopedAuth.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/annotations/SwaggerScopedAuth.java deleted file mode 100644 index cdf039c..0000000 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/annotations/SwaggerScopedAuth.java +++ /dev/null @@ -1,12 +0,0 @@ -package it.aboutbits.springboot.toolbox.swagger.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface SwaggerScopedAuth { - String value(); -} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/authorization_docs/AuthorizationDescriptor.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/authorization_docs/AuthorizationDescriptor.java deleted file mode 100644 index 48c548e..0000000 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/authorization_docs/AuthorizationDescriptor.java +++ /dev/null @@ -1,50 +0,0 @@ -package it.aboutbits.springboot.toolbox.swagger.customization.authorization_docs; - -import io.swagger.v3.oas.models.Operation; -import it.aboutbits.springboot.toolbox.swagger.annotations.SwaggerScopedAuth; -import lombok.extern.slf4j.Slf4j; -import org.springdoc.core.customizers.OperationCustomizer; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.method.HandlerMethod; - -import java.util.ArrayList; -import java.util.Optional; - -@Slf4j -public class AuthorizationDescriptor implements OperationCustomizer { - @Override - public Operation customize(Operation operation, HandlerMethod handlerMethod) { - try { - var additionalDescription = new ArrayList(); - - var maybeAnnotation = Optional.ofNullable(handlerMethod.getMethodAnnotation(PreAuthorize.class)); - if (maybeAnnotation.isPresent()) { - var annotation = maybeAnnotation.get(); - additionalDescription.add("Authorization: " + annotation.value()); - } - - var maybeAnnotation2 = Optional.ofNullable(handlerMethod.getMethodAnnotation(SwaggerScopedAuth.class)); - if (maybeAnnotation2.isPresent()) { - var annotation = maybeAnnotation2.get(); - additionalDescription.add("Scoped Authorization: " + annotation.value()); - } - - if (!additionalDescription.isEmpty()) { - - var currentDescription = Optional.ofNullable(operation.getDescription()); - - var description = String.join("
", additionalDescription); - if (currentDescription.isPresent()) { - description = "

" + description + "

" + currentDescription.get(); - } - - operation.description( - description - ); - } - } catch (Exception e) { - log.error("Error when creating swagger documentation for authorities.", e); - } - return operation; - } -} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypeModelConverter.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeModelConverter.java similarity index 98% rename from src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypeModelConverter.java rename to src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeModelConverter.java index c8b8539..4181a6c 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypeModelConverter.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeModelConverter.java @@ -1,4 +1,4 @@ -package it.aboutbits.springboot.toolbox.swagger.type; +package it.aboutbits.springboot.toolbox.swagger.customization.custom_type; import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.ModelConverter; diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeOpenApiCustomizerForCodeGenerator.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeOpenApiCustomizerForCodeGenerator.java new file mode 100644 index 0000000..d2eca79 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeOpenApiCustomizerForCodeGenerator.java @@ -0,0 +1,53 @@ +package it.aboutbits.springboot.toolbox.swagger.customization.custom_type; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import it.aboutbits.springboot.toolbox.swagger.SwaggerMetaUtil; +import it.aboutbits.springboot.toolbox.type.CustomType; +import it.aboutbits.springboot.toolbox.type.identity.EntityId; +import lombok.extern.slf4j.Slf4j; +import org.springdoc.core.customizers.OpenApiCustomizer; + +import java.util.Optional; + +@Slf4j +public class CustomTypeOpenApiCustomizerForCodeGenerator implements OpenApiCustomizer { + @Override + public void customise(OpenAPI openApi) { + openApi.getComponents().getSchemas().forEach(this::updateSchema); + } + + public Optional> getClassFromSchemaReference(String schemaRef) { + try { + var className = schemaRef.replace("#/components/schemas/", ""); + return Optional.of(Class.forName(className)); + } catch (ClassNotFoundException e) { + return Optional.empty(); + } + } + + private void updateSchema(String fqn, Schema schema) { + var type = getClassFromSchemaReference(fqn); + + if (type.isEmpty()) { + log.debug("Can not resolve type for schema reference: {}", fqn); + return; + } + + var clazz = type.get(); + + if (CustomType.class.isAssignableFrom(clazz)) { + + var isIdentity = EntityId.class.isAssignableFrom(clazz); + + var description = SwaggerMetaUtil.setOriginalTypeFqn( + schema.getDescription(), + fqn + ); + description = SwaggerMetaUtil.setIsIdentity(description, isIdentity); + description = SwaggerMetaUtil.setIsCustomType(description, true); + schema.setDescription(description); + } + + } +} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java new file mode 100644 index 0000000..bb126e8 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java @@ -0,0 +1,210 @@ +package it.aboutbits.springboot.toolbox.swagger.customization.custom_type; + +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.parameters.Parameter; +import it.aboutbits.springboot.toolbox.reflection.util.CustomTypeReflectionUtil; +import it.aboutbits.springboot.toolbox.swagger.SwaggerMetaUtil; +import it.aboutbits.springboot.toolbox.type.CustomType; +import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; +import it.aboutbits.springboot.toolbox.type.identity.EntityId; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springdoc.core.customizers.ParameterCustomizer; +import org.springframework.core.MethodParameter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Optional; +import java.util.UUID; + +@Slf4j +public class CustomTypeParameterCustomizer implements ParameterCustomizer { + + public Optional> getClassFromSchemaReference(String schemaRef) { + try { + var className = schemaRef.replace("#/components/schemas/", ""); + return Optional.of(Class.forName(className)); + } catch (ClassNotFoundException e) { + // Log the error if needed + return Optional.empty(); + } + } + + + @SuppressWarnings("checkstyle:MethodLength") + @Override + @SneakyThrows(NoSuchMethodException.class) + public Parameter customize(Parameter parameter, MethodParameter methodParameter) { + if (parameter == null) { + return null; + } + + String schemaRef = null; + var property = parameter.getSchema(); + + if (parameter.getSchema() instanceof ArraySchema + && parameter.getSchema().getItems() != null + && parameter.getSchema().getItems().get$ref() != null + ) { + schemaRef = parameter.getSchema().getItems().get$ref(); + property = parameter.getSchema().getItems(); + } + if (parameter.getSchema() instanceof ObjectSchema + && parameter.getSchema().get$ref() != null + ) { + schemaRef = parameter.getSchema().getItems().get$ref(); + } + + if (schemaRef == null) { + return parameter; + } + + var optionalClass = getClassFromSchemaReference(schemaRef); + + if (optionalClass.isEmpty()) { + log.error("Can not resolve type for schema reference: {}", schemaRef); + return parameter; // throw new RuntimeException(); + } + + + var rawClass = optionalClass.get(); + + if (rawClass.getName().contains(".") && !rawClass.getName().startsWith("java.")) { + property.setDescription(SwaggerMetaUtil.setOriginalTypeFqn( + property.getDescription(), + rawClass.getName() + )); + } + + if (CustomType.class.isAssignableFrom(rawClass)) { + Class wrappedType = null; + if (!rawClass.equals(EntityId.class)) { + wrappedType = CustomTypeReflectionUtil.getWrappedType( + (Class>) rawClass + ); + } + + var isIdentity = EntityId.class.isAssignableFrom(rawClass); + + var description = SwaggerMetaUtil.setIsIdentity(property.getDescription(), isIdentity); + + if (wrappedType == null) { + return parameter; + } + + if (Boolean.class.isAssignableFrom(wrappedType)) { + property.type("boolean"); + property.format(null); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (Byte.class.isAssignableFrom(wrappedType)) { + property.type("integer"); + property.format(""); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (Short.class.isAssignableFrom(wrappedType)) { + property.type("integer"); + property.format(""); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (Integer.class.isAssignableFrom(wrappedType)) { + property.type("integer"); + property.format("int32"); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (Long.class.isAssignableFrom(wrappedType)) { + property.type("integer"); + property.format("int64"); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (BigInteger.class.isAssignableFrom(wrappedType)) { + property.type("integer"); + property.format(""); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (Float.class.isAssignableFrom(wrappedType)) { + property.type("number"); + property.format("float"); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (Double.class.isAssignableFrom(wrappedType)) { + property.type("number"); + property.format("double"); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (BigDecimal.class.isAssignableFrom(wrappedType)) { + property.type("number"); + property.format(""); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (ScaledBigDecimal.class.isAssignableFrom(wrappedType)) { + property.type("number"); + property.format(""); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (String.class.isAssignableFrom(wrappedType)) { + property.type("string"); + property.format(null); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (Character.class.isAssignableFrom(wrappedType)) { + property.type("string"); + property.format(null); + property.minLength(1); + property.maxLength(1); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + if (UUID.class.isAssignableFrom(wrappedType)) { + property.type("string"); + property.format("uuid"); + property.minLength(36); + property.maxLength(36); + property.setDescription(description); + property.setProperties(null); + property.set$ref(null); + return parameter; + } + + log.warn("Property {} of type WrappedValue: Can not resolve parameter type!", property.getName()); + } + + return parameter; + } +} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizerForCodeGenerator.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizerForCodeGenerator.java new file mode 100644 index 0000000..79629c2 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizerForCodeGenerator.java @@ -0,0 +1,70 @@ +package it.aboutbits.springboot.toolbox.swagger.customization.custom_type; + +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.parameters.Parameter; +import it.aboutbits.springboot.toolbox.swagger.SwaggerMetaUtil; +import lombok.extern.slf4j.Slf4j; +import org.springdoc.core.customizers.ParameterCustomizer; +import org.springframework.core.MethodParameter; + +import java.util.Optional; + +@Slf4j +public class CustomTypeParameterCustomizerForCodeGenerator implements ParameterCustomizer { + @SuppressWarnings("checkstyle:MethodLength") + @Override + public Parameter customize(Parameter parameter, MethodParameter methodParameter) { + if (parameter == null) { + return null; + } + + String schemaRef = null; + var property = parameter.getSchema(); + + if (parameter.getSchema() instanceof ArraySchema + && parameter.getSchema().getItems() != null + && parameter.getSchema().getItems().get$ref() != null + ) { + schemaRef = parameter.getSchema().getItems().get$ref(); + property = parameter.getSchema().getItems(); + } + if (parameter.getSchema() instanceof ObjectSchema + && parameter.getSchema().get$ref() != null + ) { + schemaRef = parameter.getSchema().getItems().get$ref(); + } + + if (schemaRef == null) { + return parameter; + } + + var optionalClass = getClassFromSchemaReference(schemaRef); + + if (optionalClass.isEmpty()) { + log.error("Can not resolve type for schema reference: {}", schemaRef); + return parameter; + } + + + var rawClass = optionalClass.get(); + + if (rawClass.getName().contains(".") && !rawClass.getName().startsWith("java.")) { + property.setDescription(SwaggerMetaUtil.setOriginalTypeFqn( + property.getDescription(), + rawClass.getName() + )); + } + + return parameter; + } + + public Optional> getClassFromSchemaReference(String schemaRef) { + try { + var className = schemaRef.replace("#/components/schemas/", ""); + return Optional.of(Class.forName(className)); + } catch (ClassNotFoundException e) { + return Optional.empty(); + } + } +} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypePropertyCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypePropertyCustomizer.java similarity index 98% rename from src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypePropertyCustomizer.java rename to src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypePropertyCustomizer.java index 85bcadc..093a926 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypePropertyCustomizer.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypePropertyCustomizer.java @@ -1,4 +1,4 @@ -package it.aboutbits.springboot.toolbox.swagger.type; +package it.aboutbits.springboot.toolbox.swagger.customization.custom_type; import com.fasterxml.jackson.databind.type.SimpleType; import io.swagger.v3.core.converter.AnnotatedType; diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypePropertyCustomizerForCodeGenerator.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypePropertyCustomizerForCodeGenerator.java new file mode 100644 index 0000000..180f850 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypePropertyCustomizerForCodeGenerator.java @@ -0,0 +1,29 @@ +package it.aboutbits.springboot.toolbox.swagger.customization.custom_type; + +import com.fasterxml.jackson.databind.type.SimpleType; +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.oas.models.media.Schema; +import it.aboutbits.springboot.toolbox.swagger.SwaggerMetaUtil; +import lombok.extern.slf4j.Slf4j; +import org.springdoc.core.customizers.PropertyCustomizer; + +@Slf4j +public class CustomTypePropertyCustomizerForCodeGenerator implements PropertyCustomizer { + @Override + public Schema customize(Schema property, AnnotatedType annotatedType) { + var type = annotatedType.getType(); + + if (type instanceof SimpleType simpleType) { + var rawClass = simpleType.getRawClass(); + + if (rawClass.getName().contains(".") && !rawClass.getName().startsWith("java.")) { + property.setDescription(SwaggerMetaUtil.setOriginalTypeFqn( + property.getDescription(), + rawClass.getName() + )); + } + } + + return property; + } +} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullableCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullableCustomizer.java index c57af64..a51b946 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullableCustomizer.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullableCustomizer.java @@ -8,6 +8,8 @@ import java.util.Map; public class NullableCustomizer implements OpenApiCustomizer { + public static final String NULLABLE_MARKER = "NULLABLE"; + @Override @SuppressWarnings("unchecked") public void customise(OpenAPI openApi) { @@ -36,12 +38,40 @@ public void customise(OpenAPI openApi) { private static void processProperties(Map properties, ArrayList requiredProperties) { properties.forEach((propertyName, property) -> { - if (property.getTitle() == null || !property.getTitle().equals("NULLABLE")) { + var isNullable = isNullable(property); + + if (!isNullable) { requiredProperties.add(propertyName); + } else { + requiredProperties.remove(propertyName); } - if (property.getTitle() != null && property.getTitle().equals("NULLABLE")) { + if (property.getTitle() != null && property.getTitle().equals(NULLABLE_MARKER)) { property.setTitle(null); } + if (property.get$ref() != null) { + property.set$ref(property.get$ref().replace(NULLABLE_MARKER, "")); + } + if (property.getItems() != null && property.getItems().get$ref() != null) { + property.getItems().set$ref(property.getItems().get$ref().replace(NULLABLE_MARKER, "")); + } }); } + + private static boolean isNullable(Schema property) { + if (property.getTitle() != null && property.getTitle().equals(NULLABLE_MARKER)) { + return true; + } + + if (property.get$ref() != null && property.get$ref().endsWith(NULLABLE_MARKER)) { + return true; + } + + if (property.getItems() != null && property.getItems().get$ref() != null && property.getItems() + .get$ref() + .endsWith("?nullable=true")) { + return true; + } + + return false; + } } diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullablePropertyCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullablePropertyCustomizer.java index 47b0cbf..bb5ea21 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullablePropertyCustomizer.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullablePropertyCustomizer.java @@ -9,12 +9,20 @@ import java.util.ArrayList; import java.util.Arrays; +import static it.aboutbits.springboot.toolbox.swagger.customization.default_not_null.NullableCustomizer.NULLABLE_MARKER; + +/** + * We treat all properties as not-nullable by default unless we specify @Nullable + * This is the opposite of what swagger does by default. + *

+ * This customizer adds a nullability maker. Later we process this to manipulate the required fields of a schema. + */ @Component public class NullablePropertyCustomizer implements PropertyCustomizer { static { /* - We need this because the ModelResolver will only process these whitelisted annotations. - We will then be able to manipulate each property based on the set annotations. + We need this because the ModelResolver will only retain annotations with a matching name + that is contained in the list. */ var list = new ArrayList<>(ModelResolver.NOT_NULL_ANNOTATIONS); @@ -25,15 +33,27 @@ public class NullablePropertyCustomizer implements PropertyCustomizer { @Override public Schema customize(Schema property, AnnotatedType annotatedType) { - /* - Mark the nullable ones as nullable. - */ + if (isAnnotatedAsNullable(annotatedType)) { + property.setTitle(NULLABLE_MARKER); - if (annotatedType.getCtxAnnotations() != null && Arrays.stream(annotatedType.getCtxAnnotations()) - .anyMatch(a -> "Nullable".equals(a.annotationType().getSimpleName()))) { - property.setTitle("NULLABLE"); + // refs do not retain other properties + if (property.get$ref() != null) { + property.set$ref(property.get$ref() + NULLABLE_MARKER); + } } return property; } + + @SuppressWarnings("java:S1872") // Disabled: "Use an 'instanceof' comparison instead." We MUST match by name. + private static boolean isAnnotatedAsNullable(AnnotatedType annotatedType) { + if (annotatedType.getCtxAnnotations() == null) { + return false; + } + + return Arrays.stream(annotatedType.getCtxAnnotations()) + .anyMatch( + a -> "Nullable".equals(a.annotationType().getSimpleName()) + ); + } } diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/error_response/ErrorCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/error_response/ErrorCustomizer.java deleted file mode 100644 index d7c246a..0000000 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/error_response/ErrorCustomizer.java +++ /dev/null @@ -1,66 +0,0 @@ -package it.aboutbits.springboot.toolbox.swagger.customization.error_response; - -import io.swagger.v3.core.converter.ModelConverters; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.media.Content; -import io.swagger.v3.oas.models.media.MediaType; -import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.responses.ApiResponse; -import io.swagger.v3.oas.models.responses.ApiResponses; -import it.aboutbits.springboot.toolbox.web.response.ErrorResponse; -import org.springdoc.core.customizers.OpenApiCustomizer; - -import java.util.Map; - -public class ErrorCustomizer implements OpenApiCustomizer { - @Override - public void customise(OpenAPI openApi) { - openApi.getComponents() - .getSchemas() - .putAll( - ModelConverters.getInstance().read(ErrorResponse.class) - ); - - var errorResponseSchema = openApi.getComponents().getSchemas().get("ErrorResponse"); - @SuppressWarnings("unchecked") - Map> props = errorResponseSchema.getProperties(); - for (var prop : props.values()) { - prop.nullable(true); - } - - openApi.getPaths() - .values() - .forEach( - pathItem -> pathItem.readOperations() - .forEach( - operation -> { - ApiResponses apiResponses = operation.getResponses(); - apiResponses.addApiResponse( - "400", - createApiResponse( - "Bad Request", - errorResponseSchema - ) - ); - apiResponses.addApiResponse( - "404", - createApiResponse( - "Not Found", - errorResponseSchema - ) - ); - } - ) - ); - } - - private ApiResponse createApiResponse(String message, Schema schema) { - var mediaType = new MediaType(); - mediaType.schema(schema); - return new ApiResponse().description(message) - .content(new Content().addMediaType( - org.springframework.http.MediaType.APPLICATION_JSON_VALUE, - mediaType - )); - } -} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/nested_structure/NestedStructuresCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/nested_structure/NestedStructuresCustomizer.java new file mode 100644 index 0000000..cf449b3 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/nested_structure/NestedStructuresCustomizer.java @@ -0,0 +1,40 @@ +package it.aboutbits.springboot.toolbox.swagger.customization.nested_structure; + +import io.swagger.v3.oas.models.OpenAPI; +import it.aboutbits.springboot.toolbox.swagger.SwaggerMetaUtil; +import org.springdoc.core.customizers.OpenApiCustomizer; + +/** + * We assume that records that are inner classes of other records should be treated as substructure. + * As such, we set the corresponding metadata for the FE code-generator. + */ +public class NestedStructuresCustomizer implements OpenApiCustomizer { + @Override + public void customise(OpenAPI openApi) { + var components = openApi.getComponents(); + if (components != null && components.getSchemas() != null) { + for (var schemaName : components.getSchemas().keySet()) { + var schema = components.getSchemas().get(schemaName); + if (schema != null) { + try { + // Assume that schema names correspond to fully qualified class names + var clazz = Class.forName(schemaName); + if (isChildRecordOfOtherRecord(clazz)) { + schema.setDescription(SwaggerMetaUtil.setIsNestedStructure( + schema.getDescription(), + true + )); + } + + } catch (ClassNotFoundException ignored) { + // do nothing + } + } + } + } + } + + private static boolean isChildRecordOfOtherRecord(Class clazz) { + return clazz.getEnclosingClass() != null && clazz.isRecord() && clazz.getEnclosingClass().isRecord(); + } +} From 6e9447a99fa17988519371ac1d285e219469232a Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Wed, 6 Aug 2025 14:58:15 +0200 Subject: [PATCH 2/6] remove old annotation --- src/test/java/it/aboutbits/springboot/toolbox/TestApp.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/it/aboutbits/springboot/toolbox/TestApp.java b/src/test/java/it/aboutbits/springboot/toolbox/TestApp.java index 51be45a..0ebacc0 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/TestApp.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/TestApp.java @@ -1,6 +1,5 @@ package it.aboutbits.springboot.toolbox; -import it.aboutbits.springboot.toolbox.autoconfiguration.swagger.RegisterCustomTypesWithSwagger; import it.aboutbits.springboot.toolbox.autoconfiguration.web.RegisterCustomTypesWithJacksonAndMvc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -8,7 +7,6 @@ @SuppressWarnings("checkstyle:HideUtilityClassConstructor") @SpringBootApplication @RegisterCustomTypesWithJacksonAndMvc -@RegisterCustomTypesWithSwagger public class TestApp { public static void main(String[] args) { From c8672bd12f04d48027bdc543a5853237bd339f46 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Wed, 6 Aug 2025 15:00:13 +0200 Subject: [PATCH 3/6] rename test support package --- pom.xml | 2 +- .../{support => _support}/ApplicationTest.java | 4 ++-- .../toolbox/{support => _support}/HttpTest.java | 2 +- .../{support => _support}/WithPersistence.java | 4 ++-- .../persistence/PostgresTestcontainer.java | 2 +- .../persistence/WithPostgres.java | 2 +- .../mvc/CustomTypeBindingsForControllerTest.java | 2 +- .../mvc/EntityIdBindingsForControllerTest.java | 2 +- .../persistence/CustomTypeJpaTest.java | 2 +- .../persistence/EntityIdJpaTest.java | 4 ++-- .../autoconfiguration/swagger/SwaggerTest.java | 2 +- .../persistence/javatype/WrapperTypesJpaTest.java | 2 +- .../transformer/QueryTransformerTest.java | 12 +++++++----- 13 files changed, 22 insertions(+), 20 deletions(-) rename src/test/java/it/aboutbits/springboot/toolbox/{support => _support}/ApplicationTest.java (74%) rename src/test/java/it/aboutbits/springboot/toolbox/{support => _support}/HttpTest.java (84%) rename src/test/java/it/aboutbits/springboot/toolbox/{support => _support}/WithPersistence.java (58%) rename src/test/java/it/aboutbits/springboot/toolbox/{support => _support}/persistence/PostgresTestcontainer.java (98%) rename src/test/java/it/aboutbits/springboot/toolbox/{support => _support}/persistence/WithPostgres.java (87%) diff --git a/pom.xml b/pom.xml index 5bc3090..5925184 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.8.5 + 2.8.9 diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/ApplicationTest.java b/src/test/java/it/aboutbits/springboot/toolbox/_support/ApplicationTest.java similarity index 74% rename from src/test/java/it/aboutbits/springboot/toolbox/support/ApplicationTest.java rename to src/test/java/it/aboutbits/springboot/toolbox/_support/ApplicationTest.java index 6763f38..3c1a7f8 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/support/ApplicationTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/_support/ApplicationTest.java @@ -1,6 +1,6 @@ -package it.aboutbits.springboot.toolbox.support; +package it.aboutbits.springboot.toolbox._support; -import it.aboutbits.springboot.toolbox.support.persistence.WithPostgres; +import it.aboutbits.springboot.toolbox._support.persistence.WithPostgres; import org.springframework.boot.test.context.SpringBootTest; import java.lang.annotation.ElementType; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/HttpTest.java b/src/test/java/it/aboutbits/springboot/toolbox/_support/HttpTest.java similarity index 84% rename from src/test/java/it/aboutbits/springboot/toolbox/support/HttpTest.java rename to src/test/java/it/aboutbits/springboot/toolbox/_support/HttpTest.java index 723081e..49e7427 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/support/HttpTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/_support/HttpTest.java @@ -1,4 +1,4 @@ -package it.aboutbits.springboot.toolbox.support; +package it.aboutbits.springboot.toolbox._support; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/WithPersistence.java b/src/test/java/it/aboutbits/springboot/toolbox/_support/WithPersistence.java similarity index 58% rename from src/test/java/it/aboutbits/springboot/toolbox/support/WithPersistence.java rename to src/test/java/it/aboutbits/springboot/toolbox/_support/WithPersistence.java index e42ca53..3dedf8e 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/support/WithPersistence.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/_support/WithPersistence.java @@ -1,6 +1,6 @@ -package it.aboutbits.springboot.toolbox.support; +package it.aboutbits.springboot.toolbox._support; -import it.aboutbits.springboot.toolbox.support.persistence.WithPostgres; +import it.aboutbits.springboot.toolbox._support.persistence.WithPostgres; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/PostgresTestcontainer.java b/src/test/java/it/aboutbits/springboot/toolbox/_support/persistence/PostgresTestcontainer.java similarity index 98% rename from src/test/java/it/aboutbits/springboot/toolbox/support/persistence/PostgresTestcontainer.java rename to src/test/java/it/aboutbits/springboot/toolbox/_support/persistence/PostgresTestcontainer.java index 8a21f17..e2b9b43 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/PostgresTestcontainer.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/_support/persistence/PostgresTestcontainer.java @@ -1,4 +1,4 @@ -package it.aboutbits.springboot.toolbox.support.persistence; +package it.aboutbits.springboot.toolbox._support.persistence; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.extension.AfterEachCallback; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/WithPostgres.java b/src/test/java/it/aboutbits/springboot/toolbox/_support/persistence/WithPostgres.java similarity index 87% rename from src/test/java/it/aboutbits/springboot/toolbox/support/persistence/WithPostgres.java rename to src/test/java/it/aboutbits/springboot/toolbox/_support/persistence/WithPostgres.java index 9f51713..be92a4a 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/WithPostgres.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/_support/persistence/WithPostgres.java @@ -1,4 +1,4 @@ -package it.aboutbits.springboot.toolbox.support.persistence; +package it.aboutbits.springboot.toolbox._support.persistence; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.parallel.ResourceAccessMode; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/CustomTypeBindingsForControllerTest.java b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/CustomTypeBindingsForControllerTest.java index 78e0e92..ca022ce 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/CustomTypeBindingsForControllerTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/CustomTypeBindingsForControllerTest.java @@ -1,11 +1,11 @@ package it.aboutbits.springboot.toolbox.autoconfiguration.mvc; import com.fasterxml.jackson.databind.ObjectMapper; +import it.aboutbits.springboot.toolbox._support.HttpTest; import it.aboutbits.springboot.toolbox.autoconfiguration.mvc.body.BodyWithEmailAddress; import it.aboutbits.springboot.toolbox.autoconfiguration.mvc.body.BodyWithIban; import it.aboutbits.springboot.toolbox.autoconfiguration.mvc.body.BodyWithScaledBigDecimal; import it.aboutbits.springboot.toolbox.autoconfiguration.mvc.body.BodyWithUUID; -import it.aboutbits.springboot.toolbox.support.HttpTest; import it.aboutbits.springboot.toolbox.type.EmailAddress; import it.aboutbits.springboot.toolbox.type.Iban; import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/EntityIdBindingsForControllerTest.java b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/EntityIdBindingsForControllerTest.java index b6c2a6c..2040a41 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/EntityIdBindingsForControllerTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/EntityIdBindingsForControllerTest.java @@ -1,9 +1,9 @@ package it.aboutbits.springboot.toolbox.autoconfiguration.mvc; import com.fasterxml.jackson.databind.ObjectMapper; +import it.aboutbits.springboot.toolbox._support.HttpTest; import it.aboutbits.springboot.toolbox.autoconfiguration.mvc.body.BodyWithEntityId; import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModel; -import it.aboutbits.springboot.toolbox.support.HttpTest; import lombok.NonNull; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/CustomTypeJpaTest.java b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/CustomTypeJpaTest.java index a704e5a..3dc2854 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/CustomTypeJpaTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/CustomTypeJpaTest.java @@ -1,8 +1,8 @@ package it.aboutbits.springboot.toolbox.autoconfiguration.persistence; +import it.aboutbits.springboot.toolbox._support.ApplicationTest; import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModel; import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModelRepository; -import it.aboutbits.springboot.toolbox.support.ApplicationTest; import it.aboutbits.springboot.toolbox.type.EmailAddress; import it.aboutbits.springboot.toolbox.type.Iban; import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/EntityIdJpaTest.java b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/EntityIdJpaTest.java index eafb95d..72f5ff9 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/EntityIdJpaTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/EntityIdJpaTest.java @@ -1,10 +1,10 @@ package it.aboutbits.springboot.toolbox.autoconfiguration.persistence; +import it.aboutbits.springboot.toolbox._support.ApplicationTest; +import it.aboutbits.springboot.toolbox._support.WithPersistence; import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModel; import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModelRepository; import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.ReferencedTestModel; -import it.aboutbits.springboot.toolbox.support.ApplicationTest; -import it.aboutbits.springboot.toolbox.support.WithPersistence; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/SwaggerTest.java b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/SwaggerTest.java index 0e57c3e..ca25e28 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/SwaggerTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/SwaggerTest.java @@ -1,6 +1,6 @@ package it.aboutbits.springboot.toolbox.autoconfiguration.swagger; -import it.aboutbits.springboot.toolbox.support.HttpTest; +import it.aboutbits.springboot.toolbox._support.HttpTest; import lombok.NonNull; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java index 3c1559d..5e64926 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java @@ -1,5 +1,6 @@ package it.aboutbits.springboot.toolbox.persistence.javatype; +import it.aboutbits.springboot.toolbox._support.ApplicationTest; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa.WrapperTypesModel; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa.WrapperTypesModelRepository; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimalClass; @@ -28,7 +29,6 @@ import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapStringRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapUUIDClass; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapUUIDRecord; -import it.aboutbits.springboot.toolbox.support.ApplicationTest; import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/transformer/QueryTransformerTest.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/transformer/QueryTransformerTest.java index 54a9361..c0223ad 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/persistence/transformer/QueryTransformerTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/transformer/QueryTransformerTest.java @@ -1,8 +1,8 @@ package it.aboutbits.springboot.toolbox.persistence.transformer; +import it.aboutbits.springboot.toolbox._support.ApplicationTest; import it.aboutbits.springboot.toolbox.persistence.transformer.impl.jpa.QueryTransformerTestModel; import it.aboutbits.springboot.toolbox.persistence.transformer.impl.jpa.QueryTransformerTestModelRepository; -import it.aboutbits.springboot.toolbox.support.ApplicationTest; import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityNotFoundException; @@ -170,10 +170,12 @@ void givenQueryWithOneResult_shouldPass() { void givenQueryWithOneResult_shouldFail() { var query = entityManager.createQuery("select q, 'xxx' from QueryTransformerTestModel q"); - assertThrows(EntityNotFoundException.class, () -> QueryTransformer - .of(entityManager, TestModelContainer.class) - .withQuery(query) - .asSingleResultOrFail()); + assertThrows( + EntityNotFoundException.class, () -> QueryTransformer + .of(entityManager, TestModelContainer.class) + .withQuery(query) + .asSingleResultOrFail() + ); } } From 806e6900b28bc432e0eb1a40d2fd488f3caa0f61 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 7 Aug 2025 12:29:27 +0200 Subject: [PATCH 4/6] update deps --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 5925184..e60f26a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.2 + 3.4.5 @@ -123,7 +123,7 @@ org.testcontainers testcontainers - 1.20.1 + 1.21.3 test @@ -145,13 +145,13 @@ org.testcontainers junit-jupiter - 1.20.1 + 1.21.3 test org.testcontainers postgresql - 1.20.1 + 1.21.3 test From 7981fba2d7e26244216bbf7598841cde8d9407f1 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 7 Aug 2025 13:39:28 +0200 Subject: [PATCH 5/6] remove comment --- .../custom_type/CustomTypeParameterCustomizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java index bb126e8..5b87a7d 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java @@ -64,7 +64,7 @@ public Parameter customize(Parameter parameter, MethodParameter methodParameter) if (optionalClass.isEmpty()) { log.error("Can not resolve type for schema reference: {}", schemaRef); - return parameter; // throw new RuntimeException(); + return parameter; } From 3885bff036d5d88d36a8f5c7893df3115f1e2688 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Fri, 8 Aug 2025 17:28:19 +0200 Subject: [PATCH 6/6] extract common function to util --- ...omTypeOpenApiCustomizerForCodeGenerator.java | 10 +--------- .../CustomTypeParameterCustomizer.java | 15 ++------------- ...TypeParameterCustomizerForCodeGenerator.java | 11 +---------- .../customization/custom_type/SchemaUtil.java | 17 +++++++++++++++++ 4 files changed, 21 insertions(+), 32 deletions(-) create mode 100644 src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/SchemaUtil.java diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeOpenApiCustomizerForCodeGenerator.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeOpenApiCustomizerForCodeGenerator.java index d2eca79..5ac7f00 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeOpenApiCustomizerForCodeGenerator.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeOpenApiCustomizerForCodeGenerator.java @@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j; import org.springdoc.core.customizers.OpenApiCustomizer; -import java.util.Optional; +import static it.aboutbits.springboot.toolbox.swagger.customization.custom_type.SchemaUtil.getClassFromSchemaReference; @Slf4j public class CustomTypeOpenApiCustomizerForCodeGenerator implements OpenApiCustomizer { @@ -17,14 +17,6 @@ public void customise(OpenAPI openApi) { openApi.getComponents().getSchemas().forEach(this::updateSchema); } - public Optional> getClassFromSchemaReference(String schemaRef) { - try { - var className = schemaRef.replace("#/components/schemas/", ""); - return Optional.of(Class.forName(className)); - } catch (ClassNotFoundException e) { - return Optional.empty(); - } - } private void updateSchema(String fqn, Schema schema) { var type = getClassFromSchemaReference(fqn); diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java index 5b87a7d..fe4e5d4 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizer.java @@ -15,23 +15,12 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Optional; import java.util.UUID; +import static it.aboutbits.springboot.toolbox.swagger.customization.custom_type.SchemaUtil.getClassFromSchemaReference; + @Slf4j public class CustomTypeParameterCustomizer implements ParameterCustomizer { - - public Optional> getClassFromSchemaReference(String schemaRef) { - try { - var className = schemaRef.replace("#/components/schemas/", ""); - return Optional.of(Class.forName(className)); - } catch (ClassNotFoundException e) { - // Log the error if needed - return Optional.empty(); - } - } - - @SuppressWarnings("checkstyle:MethodLength") @Override @SneakyThrows(NoSuchMethodException.class) diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizerForCodeGenerator.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizerForCodeGenerator.java index 79629c2..dfe39de 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizerForCodeGenerator.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/CustomTypeParameterCustomizerForCodeGenerator.java @@ -8,7 +8,7 @@ import org.springdoc.core.customizers.ParameterCustomizer; import org.springframework.core.MethodParameter; -import java.util.Optional; +import static it.aboutbits.springboot.toolbox.swagger.customization.custom_type.SchemaUtil.getClassFromSchemaReference; @Slf4j public class CustomTypeParameterCustomizerForCodeGenerator implements ParameterCustomizer { @@ -58,13 +58,4 @@ public Parameter customize(Parameter parameter, MethodParameter methodParameter) return parameter; } - - public Optional> getClassFromSchemaReference(String schemaRef) { - try { - var className = schemaRef.replace("#/components/schemas/", ""); - return Optional.of(Class.forName(className)); - } catch (ClassNotFoundException e) { - return Optional.empty(); - } - } } diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/SchemaUtil.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/SchemaUtil.java new file mode 100644 index 0000000..25e54ca --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/custom_type/SchemaUtil.java @@ -0,0 +1,17 @@ +package it.aboutbits.springboot.toolbox.swagger.customization.custom_type; + +import java.util.Optional; + +final class SchemaUtil { + private SchemaUtil() { + } + + static Optional> getClassFromSchemaReference(String schemaRef) { + try { + var className = schemaRef.replace("#/components/schemas/", ""); + return Optional.of(Class.forName(className)); + } catch (ClassNotFoundException e) { + return Optional.empty(); + } + } +}