Skip to content

Commit 0db5e5b

Browse files
committed
add more common swagger code
1 parent 06d6563 commit 0db5e5b

5 files changed

Lines changed: 192 additions & 0 deletions

File tree

src/main/java/it/aboutbits/springboot/toolbox/reflection/util/ClassScannerUtil.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package it.aboutbits.springboot.toolbox.reflection.util;
22

33
import io.github.classgraph.ClassGraph;
4+
import io.github.classgraph.ClassInfo;
45
import io.github.classgraph.ScanResult;
56
import lombok.NonNull;
67

8+
import java.lang.annotation.Annotation;
79
import java.util.Set;
810
import java.util.stream.Collectors;
911

@@ -33,6 +35,13 @@ public <T> Set<Class<? extends T>> getSubTypesOf(@NonNull Class<T> clazz) {
3335
.collect(Collectors.toSet());
3436
}
3537

38+
public Set<Class<?>> getClassesAnnotatedWith(@NonNull Class<? extends Annotation> clazz) {
39+
var result = scanResult.getClassesWithAnnotation(clazz);
40+
return result.stream().map(
41+
ClassInfo::loadClass
42+
).collect(Collectors.toSet());
43+
}
44+
3645
@Override
3746
public void close() {
3847
scanResult.close();
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.annotation;
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+
/**
9+
* Annotation to mark classes that should be included in Swagger schema even if unused.
10+
*/
11+
@Target(ElementType.TYPE)
12+
@Retention(RetentionPolicy.RUNTIME)
13+
public @interface ForceSwaggerSchema {
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.force_schema;
2+
3+
import io.swagger.v3.core.converter.ModelConverters;
4+
import io.swagger.v3.core.jackson.ModelResolver;
5+
import io.swagger.v3.oas.models.Components;
6+
import io.swagger.v3.oas.models.OpenAPI;
7+
import it.aboutbits.springboot.toolbox.reflection.util.ClassScannerUtil;
8+
import it.aboutbits.springboot.toolbox.swagger.annotation.ForceSwaggerSchema;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springdoc.core.customizers.OpenApiCustomizer;
11+
12+
import java.util.LinkedHashMap;
13+
14+
@RequiredArgsConstructor
15+
public class ForceSchemaCustomizer implements OpenApiCustomizer {
16+
private final ModelResolver modelResolver;
17+
private final ClassScannerUtil.ClassScanner classScanner;
18+
19+
@Override
20+
public void customise(OpenAPI openApi) {
21+
addAnnotatedSchemas(openApi);
22+
}
23+
24+
private void addAnnotatedSchemas(OpenAPI openAPI) {
25+
if (openAPI.getComponents() == null) {
26+
openAPI.setComponents(new Components());
27+
}
28+
if (openAPI.getComponents().getSchemas() == null) {
29+
openAPI.getComponents().setSchemas(new LinkedHashMap<>());
30+
}
31+
32+
// Create a custom ModelConverters instance with the same configuration
33+
var customModelConverters = new ModelConverters();
34+
customModelConverters.addConverter(modelResolver);
35+
36+
// Scan for classes with @ForceSwaggerSchema annotation
37+
var annotatedClasses = classScanner.getClassesAnnotatedWith(ForceSwaggerSchema.class);
38+
39+
for (var clazz : annotatedClasses) {
40+
var schemas = customModelConverters.read(clazz);
41+
// Only add if not already present (to avoid overriding naturally discovered schemas)
42+
schemas.forEach((key, schema) -> {
43+
if (!openAPI.getComponents().getSchemas().containsKey(key)) {
44+
openAPI.getComponents().getSchemas().put(key, schema);
45+
}
46+
});
47+
}
48+
}
49+
}
50+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package it.aboutbits.springboot.toolbox.swagger.resolver;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import io.swagger.v3.core.jackson.ModelResolver;
5+
import io.swagger.v3.core.jackson.TypeNameResolver;
6+
7+
public class CustomModelResolver extends ModelResolver {
8+
public CustomModelResolver(ObjectMapper mapper) {
9+
super(mapper, new CustomTypeNameResolver());
10+
}
11+
12+
public static class CustomTypeNameResolver extends TypeNameResolver {
13+
@Override
14+
protected String getNameOfClass(Class<?> cls) {
15+
return cls.getName();
16+
}
17+
}
18+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package it.aboutbits.springboot.toolbox.swagger.sort_parameter;
2+
3+
import io.swagger.v3.oas.models.OpenAPI;
4+
import io.swagger.v3.oas.models.media.ArraySchema;
5+
import io.swagger.v3.oas.models.media.StringSchema;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springdoc.core.customizers.OpenApiCustomizer;
8+
9+
@RequiredArgsConstructor
10+
public class SortParameterCustomizer implements OpenApiCustomizer {
11+
private final Class<?> sortParameterSortFieldClass;
12+
13+
@Override
14+
public void customise(OpenAPI openApi) {
15+
if (openApi.getPaths() != null) {
16+
for (var path : openApi.getPaths().entrySet()) {
17+
var pathItemOperations = path.getValue().readOperations();
18+
if (pathItemOperations == null) {
19+
continue;
20+
}
21+
22+
for (var operation : pathItemOperations) {
23+
if (operation.getParameters() == null) {
24+
continue;
25+
}
26+
27+
for (var parameter : operation.getParameters()) {
28+
if (parameter.getSchema() == null || parameter.getSchema().get$ref() == null) {
29+
continue;
30+
}
31+
32+
if (parameter.getSchema().get$ref().endsWith(".SortParameter")) {
33+
parameter.required(false);
34+
parameter.description(
35+
"""
36+
Defines the sort order and if left empty the implementation specific default order will be used.<br>
37+
Also supports multiple order fields by specifying the order query parameter multiple times.<br>
38+
The format is as follows:<br><br>
39+
<code>\\<property\\>[:direction][:nullHandling]</code><br>
40+
<ul>
41+
<li>
42+
<code>property</code> is the field name you want to sort on the response Object<br>
43+
</li><br>
44+
<li>
45+
<code>direction</code> is the optional sort direction (case insensitive)
46+
<ul>
47+
<br>
48+
<li>
49+
<code>asc</code> (default)
50+
</li><br>
51+
<li>
52+
<code>desc</code>
53+
</li>
54+
</ul>
55+
</li><br>
56+
<li>
57+
<code>nullHandling</code> is the optional null handling strategy (case insensitive)<br>
58+
<ul>
59+
<br>
60+
<li>
61+
<code>native</code> (default)
62+
</li><br>
63+
<li>
64+
<code>first</code>
65+
</li><br>
66+
<li>
67+
<code>last</code>
68+
</li>
69+
</ul>
70+
</li>
71+
</ul>
72+
Examples:
73+
<ul>
74+
<li>
75+
<code>typeFamily</code> (uses the default sort of asc and native null handling)
76+
</li><br>
77+
<li>
78+
<code>typeFamily:desc</code> (sorts desc and uses the default native null handling)
79+
</li><br>
80+
<li>
81+
<code>typeFamily:asc:last</code> (sorts asc and uses the null handling strategy last)
82+
</li><br>
83+
</ul
84+
"""
85+
);
86+
var itemSchema = new StringSchema()._default("property:asc:last");
87+
itemSchema.setDescription(
88+
"{\"originalTypeFqn\": \"%s\"}".formatted(sortParameterSortFieldClass.getName())
89+
);
90+
91+
parameter.setSchema(new ArraySchema().items(
92+
itemSchema
93+
));
94+
}
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)