Skip to content

Commit af7de53

Browse files
committed
add swagger tools
1 parent c5dd449 commit af7de53

11 files changed

Lines changed: 248 additions & 7 deletions

File tree

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
<artifactId>spring-boot-starter-json</artifactId>
3737
</dependency>
3838

39+
<dependency>
40+
<groupId>org.springframework.security</groupId>
41+
<artifactId>spring-security-core</artifactId>
42+
</dependency>
43+
3944
<!-- Utilities -->
4045
<dependency>
4146
<groupId>org.projectlombok</groupId>

src/main/java/it/aboutbits/springboot/toolbox/autoconfiguration/swagger/RegisterCustomTypesWithSwagger.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package it.aboutbits.springboot.toolbox.autoconfiguration.swagger;
22

3-
import it.aboutbits.springboot.toolbox.swagger.CustomTypeModelConverter;
4-
import it.aboutbits.springboot.toolbox.swagger.CustomTypePropertyCustomizer;
3+
import it.aboutbits.springboot.toolbox.swagger.type.CustomTypeModelConverter;
4+
import it.aboutbits.springboot.toolbox.swagger.type.CustomTypePropertyCustomizer;
55
import org.springframework.context.annotation.Import;
66

77
import java.lang.annotation.ElementType;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package it.aboutbits.springboot.toolbox.swagger.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target({ElementType.METHOD})
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface SwaggerScopedAuth {
11+
String value();
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package it.aboutbits.springboot.toolbox.swagger.customization.alphabetical_model_order;
2+
3+
import io.swagger.v3.oas.models.OpenAPI;
4+
import org.springdoc.core.customizers.OpenApiCustomizer;
5+
6+
import java.util.TreeMap;
7+
8+
public class OrderModelsCustomizer implements OpenApiCustomizer {
9+
@Override
10+
public void customise(OpenAPI openApi) {
11+
var components = openApi.getComponents();
12+
13+
components.schemas(new TreeMap<>(components.getSchemas()));
14+
}
15+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package it.aboutbits.springboot.toolbox.swagger.customization.authorization_docs;
2+
3+
import io.swagger.v3.oas.models.Operation;
4+
import it.aboutbits.springboot.toolbox.swagger.annotations.SwaggerScopedAuth;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springdoc.core.customizers.OperationCustomizer;
7+
import org.springframework.security.access.prepost.PreAuthorize;
8+
import org.springframework.web.method.HandlerMethod;
9+
10+
import java.util.ArrayList;
11+
import java.util.Optional;
12+
13+
@Slf4j
14+
public class AuthorizationDescriptor implements OperationCustomizer {
15+
@Override
16+
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
17+
try {
18+
var additionalDescription = new ArrayList<String>();
19+
20+
var maybeAnnotation = Optional.ofNullable(handlerMethod.getMethodAnnotation(PreAuthorize.class));
21+
if (maybeAnnotation.isPresent()) {
22+
var annotation = maybeAnnotation.get();
23+
additionalDescription.add("<b>Authorization:</b> " + annotation.value());
24+
}
25+
26+
var maybeAnnotation2 = Optional.ofNullable(handlerMethod.getMethodAnnotation(SwaggerScopedAuth.class));
27+
if (maybeAnnotation2.isPresent()) {
28+
var annotation = maybeAnnotation2.get();
29+
additionalDescription.add("<b>Scoped Authorization:</b> " + annotation.value());
30+
}
31+
32+
if (!additionalDescription.isEmpty()) {
33+
34+
var currentDescription = Optional.ofNullable(operation.getDescription());
35+
36+
var description = String.join("<br />", additionalDescription);
37+
if (currentDescription.isPresent()) {
38+
description = "<p>" + description + "</p>" + currentDescription.get();
39+
}
40+
41+
operation.description(
42+
description
43+
);
44+
}
45+
} catch (Exception e) {
46+
log.error("Error when creating swagger documentation for authorities.", e);
47+
}
48+
return operation;
49+
}
50+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package it.aboutbits.springboot.toolbox.swagger.customization.default_not_null;
2+
3+
import io.swagger.v3.oas.models.OpenAPI;
4+
import io.swagger.v3.oas.models.media.Schema;
5+
import org.springdoc.core.customizers.OpenApiCustomizer;
6+
7+
import java.util.ArrayList;
8+
9+
public class NullableCustomizer implements OpenApiCustomizer {
10+
@Override
11+
@SuppressWarnings("unchecked")
12+
public void customise(OpenAPI openApi) {
13+
openApi.getComponents().getSchemas().values()
14+
.forEach(schema -> {
15+
var requiredProperties = new ArrayList<String>();
16+
((Schema<?>) schema).getProperties().forEach((propertyName, property) -> {
17+
if (property.getNullable() == null || !property.getNullable()) {
18+
requiredProperties.add(propertyName);
19+
}
20+
});
21+
schema.setRequired(requiredProperties);
22+
});
23+
}
24+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package it.aboutbits.springboot.toolbox.swagger.customization.default_not_null;
2+
3+
import io.swagger.v3.core.converter.AnnotatedType;
4+
import io.swagger.v3.core.jackson.ModelResolver;
5+
import io.swagger.v3.oas.models.media.Schema;
6+
import org.springdoc.core.customizers.PropertyCustomizer;
7+
import org.springframework.stereotype.Component;
8+
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
12+
@Component
13+
public class NullablePropertyCustomizer implements PropertyCustomizer {
14+
static {
15+
/*
16+
We need this because the ModelResolver will only process these whitelisted annotations.
17+
We will then be able to manipulate each property based on the set annotations.
18+
*/
19+
20+
var list = new ArrayList<>(ModelResolver.NOT_NULL_ANNOTATIONS);
21+
list.add("Nullable");
22+
23+
ModelResolver.NOT_NULL_ANNOTATIONS = list;
24+
}
25+
26+
@Override
27+
public Schema<?> customize(Schema property, AnnotatedType annotatedType) {
28+
/*
29+
Mark the nullable ones as nullable.
30+
*/
31+
32+
if (annotatedType.getCtxAnnotations() != null && Arrays.stream(annotatedType.getCtxAnnotations())
33+
.anyMatch(a -> "Nullable".equals(a.annotationType().getSimpleName()))) {
34+
property.setNullable(true);
35+
}
36+
37+
return property;
38+
}
39+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package it.aboutbits.springboot.toolbox.swagger.customization.error_response;
2+
3+
import io.swagger.v3.core.converter.ModelConverters;
4+
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.media.Content;
6+
import io.swagger.v3.oas.models.media.MediaType;
7+
import io.swagger.v3.oas.models.media.Schema;
8+
import io.swagger.v3.oas.models.responses.ApiResponse;
9+
import io.swagger.v3.oas.models.responses.ApiResponses;
10+
import it.aboutbits.springboot.toolbox.mvc.response.ErrorResponse;
11+
import org.springdoc.core.customizers.OpenApiCustomizer;
12+
13+
import java.util.Map;
14+
15+
public class ErrorCustomizer implements OpenApiCustomizer {
16+
@Override
17+
public void customise(OpenAPI openApi) {
18+
openApi.getComponents()
19+
.getSchemas()
20+
.putAll(
21+
ModelConverters.getInstance().read(ErrorResponse.class)
22+
);
23+
24+
var errorResponseSchema = openApi.getComponents().getSchemas().get("ErrorResponse");
25+
@SuppressWarnings("unchecked")
26+
Map<String, Schema<?>> props = errorResponseSchema.getProperties();
27+
for (var prop : props.values()) {
28+
prop.nullable(true);
29+
}
30+
31+
openApi.getPaths()
32+
.values()
33+
.forEach(
34+
pathItem -> pathItem.readOperations()
35+
.forEach(
36+
operation -> {
37+
ApiResponses apiResponses = operation.getResponses();
38+
apiResponses.addApiResponse(
39+
"400",
40+
createApiResponse(
41+
"Bad Request",
42+
errorResponseSchema
43+
)
44+
);
45+
apiResponses.addApiResponse(
46+
"404",
47+
createApiResponse(
48+
"Not Found",
49+
errorResponseSchema
50+
)
51+
);
52+
}
53+
)
54+
);
55+
}
56+
57+
private ApiResponse createApiResponse(String message, Schema<?> schema) {
58+
var mediaType = new MediaType();
59+
mediaType.schema(schema);
60+
return new ApiResponse().description(message)
61+
.content(new Content().addMediaType(
62+
org.springframework.http.MediaType.APPLICATION_JSON_VALUE,
63+
mediaType
64+
));
65+
}
66+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package it.aboutbits.springboot.toolbox.swagger.customization.logout_route;
2+
3+
import io.swagger.v3.oas.models.OpenAPI;
4+
import io.swagger.v3.oas.models.Operation;
5+
import io.swagger.v3.oas.models.PathItem;
6+
import io.swagger.v3.oas.models.responses.ApiResponses;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springdoc.core.customizers.OpenApiCustomizer;
9+
10+
@RequiredArgsConstructor
11+
public class LogoutCustomizer implements OpenApiCustomizer {
12+
private final String logoutUrl;
13+
14+
@Override
15+
public void customise(OpenAPI openApi) {
16+
var operation = new Operation();
17+
operation.addTagsItem("Authentication API");
18+
operation.summary("Logout the current user");
19+
operation.responses(new ApiResponses());
20+
21+
var pathItem = new PathItem();
22+
pathItem.setPost(operation);
23+
24+
openApi.getPaths().addPathItem(logoutUrl, pathItem);
25+
}
26+
}

src/main/java/it/aboutbits/springboot/toolbox/swagger/CustomTypeModelConverter.java renamed to src/main/java/it/aboutbits/springboot/toolbox/swagger/type/CustomTypeModelConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package it.aboutbits.springboot.toolbox.swagger;
1+
package it.aboutbits.springboot.toolbox.swagger.type;
22

33
import io.swagger.v3.core.converter.AnnotatedType;
44
import io.swagger.v3.core.converter.ModelConverter;

0 commit comments

Comments
 (0)