diff --git a/pom.xml b/pom.xml
index e3ef8a0..776fd98 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,11 @@
spring-boot-starter-json
+
+ org.springframework.security
+ spring-security-core
+
+
org.projectlombok
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
index ed99c70..cb2cc8e 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/RegisterCustomTypesWithSwagger.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/RegisterCustomTypesWithSwagger.java
@@ -1,7 +1,7 @@
package it.aboutbits.springboot.toolbox.autoconfiguration.swagger;
-import it.aboutbits.springboot.toolbox.swagger.CustomTypeModelConverter;
-import it.aboutbits.springboot.toolbox.swagger.CustomTypePropertyCustomizer;
+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;
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
new file mode 100644
index 0000000..cdf039c
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/annotations/SwaggerScopedAuth.java
@@ -0,0 +1,12 @@
+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/alphabetical_model_order/OrderModelsCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/alphabetical_model_order/OrderModelsCustomizer.java
new file mode 100644
index 0000000..79d9006
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/alphabetical_model_order/OrderModelsCustomizer.java
@@ -0,0 +1,15 @@
+package it.aboutbits.springboot.toolbox.swagger.customization.alphabetical_model_order;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+
+import java.util.TreeMap;
+
+public class OrderModelsCustomizer implements OpenApiCustomizer {
+ @Override
+ public void customise(OpenAPI openApi) {
+ var components = openApi.getComponents();
+
+ components.schemas(new TreeMap<>(components.getSchemas()));
+ }
+}
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
new file mode 100644
index 0000000..48c548e
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/authorization_docs/AuthorizationDescriptor.java
@@ -0,0 +1,50 @@
+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/customization/default_not_null/NullableCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullableCustomizer.java
new file mode 100644
index 0000000..41413a2
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullableCustomizer.java
@@ -0,0 +1,24 @@
+package it.aboutbits.springboot.toolbox.swagger.customization.default_not_null;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.Schema;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+
+import java.util.ArrayList;
+
+public class NullableCustomizer implements OpenApiCustomizer {
+ @Override
+ @SuppressWarnings("unchecked")
+ public void customise(OpenAPI openApi) {
+ openApi.getComponents().getSchemas().values()
+ .forEach(schema -> {
+ var requiredProperties = new ArrayList();
+ ((Schema>) schema).getProperties().forEach((propertyName, property) -> {
+ if (property.getNullable() == null || !property.getNullable()) {
+ requiredProperties.add(propertyName);
+ }
+ });
+ schema.setRequired(requiredProperties);
+ });
+ }
+}
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
new file mode 100644
index 0000000..a8fdf57
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/default_not_null/NullablePropertyCustomizer.java
@@ -0,0 +1,39 @@
+package it.aboutbits.springboot.toolbox.swagger.customization.default_not_null;
+
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.core.jackson.ModelResolver;
+import io.swagger.v3.oas.models.media.Schema;
+import org.springdoc.core.customizers.PropertyCustomizer;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@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.
+ */
+
+ var list = new ArrayList<>(ModelResolver.NOT_NULL_ANNOTATIONS);
+ list.add("Nullable");
+
+ ModelResolver.NOT_NULL_ANNOTATIONS = list;
+ }
+
+ @Override
+ public Schema> customize(Schema property, AnnotatedType annotatedType) {
+ /*
+ Mark the nullable ones as nullable.
+ */
+
+ if (annotatedType.getCtxAnnotations() != null && Arrays.stream(annotatedType.getCtxAnnotations())
+ .anyMatch(a -> "Nullable".equals(a.annotationType().getSimpleName()))) {
+ property.setNullable(true);
+ }
+
+ return property;
+ }
+}
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
new file mode 100644
index 0000000..798d0e8
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/error_response/ErrorCustomizer.java
@@ -0,0 +1,66 @@
+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.mvc.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/logout_route/LogoutCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/logout_route/LogoutCustomizer.java
new file mode 100644
index 0000000..3bf3ee5
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/customization/logout_route/LogoutCustomizer.java
@@ -0,0 +1,26 @@
+package it.aboutbits.springboot.toolbox.swagger.customization.logout_route;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import lombok.RequiredArgsConstructor;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+
+@RequiredArgsConstructor
+public class LogoutCustomizer implements OpenApiCustomizer {
+ private final String logoutUrl;
+
+ @Override
+ public void customise(OpenAPI openApi) {
+ var operation = new Operation();
+ operation.addTagsItem("Authentication API");
+ operation.summary("Logout the current user");
+ operation.responses(new ApiResponses());
+
+ var pathItem = new PathItem();
+ pathItem.setPost(operation);
+
+ openApi.getPaths().addPathItem(logoutUrl, pathItem);
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/swagger/CustomTypeModelConverter.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypeModelConverter.java
similarity index 97%
rename from src/main/java/it/aboutbits/springboot/toolbox/swagger/CustomTypeModelConverter.java
rename to src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypeModelConverter.java
index 803937f..f0aa8cf 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/CustomTypeModelConverter.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypeModelConverter.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.swagger;
+package it.aboutbits.springboot.toolbox.swagger.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/CustomTypePropertyCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypePropertyCustomizer.java
similarity index 91%
rename from src/main/java/it/aboutbits/springboot/toolbox/swagger/CustomTypePropertyCustomizer.java
rename to src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypePropertyCustomizer.java
index fd3f81e..810f906 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/swagger/CustomTypePropertyCustomizer.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypePropertyCustomizer.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.swagger;
+package it.aboutbits.springboot.toolbox.swagger.type;
import com.fasterxml.jackson.databind.type.SimpleType;
import io.swagger.v3.core.converter.AnnotatedType;
@@ -25,13 +25,17 @@ public Schema> customize(Schema property, AnnotatedType annotatedType) {
var displayName = rawClass.getSimpleName();
+ Class> wrappedType;
if (EntityId.class.isAssignableFrom(rawClass)) {
displayName = resolveEntityIdDisplayName(rawClass);
}
- var constructor = RecordReflectionUtil.getCanonicalConstructor(rawClass);
- var wrappedType = constructor.getParameters()[0].getType();
-
+ if (rawClass.equals(EntityId.class)) {
+ wrappedType = simpleType.getBindings().getBoundType(0).getRawClass();
+ } else {
+ var constructor = RecordReflectionUtil.getCanonicalConstructor(rawClass);
+ wrappedType = constructor.getParameters()[0].getType();
+ }
if (Short.class.isAssignableFrom(wrappedType)) {
property.type("integer");