Skip to content

Commit f5b6869

Browse files
committed
Merge branch 'gebezs-javadoc-enhancement'
2 parents b7380a6 + e4d0c0d commit f5b6869

File tree

170 files changed

+2864
-260
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

170 files changed

+2864
-260
lines changed

springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ protected synchronized OpenAPI getOpenApi(Locale locale) {
315315
Map<String, Object> findControllerAdvice = openAPIService.getControllerAdviceMap();
316316
// calculate generic responses
317317
openApi = openAPIService.getCalculatedOpenAPI();
318-
if (springDocConfigProperties.isOverrideWithGenericResponse()) {
318+
if (springDocConfigProperties.isDefaultOverrideWithGenericResponse()) {
319319
if (!CollectionUtils.isEmpty(mappingsMap))
320320
findControllerAdvice.putAll(mappingsMap);
321321
responseBuilder.buildGenericResponse(openApi.getComponents(), findControllerAdvice, finalLocale);
@@ -463,8 +463,18 @@ protected void calculatePath(HandlerMethod handlerMethod, RouterOperation router
463463
// get javadoc method description
464464
if (javadocProvider != null) {
465465
String description = javadocProvider.getMethodJavadocDescription(handlerMethod.getMethod());
466-
if (!StringUtils.isEmpty(description) && StringUtils.isEmpty(operation.getDescription()))
466+
String summary = javadocProvider.getFirstSentence(description);
467+
boolean emptyOverrideDescription = StringUtils.isEmpty(operation.getDescription());
468+
boolean emptyOverrideSummary = StringUtils.isEmpty(operation.getSummary());
469+
if (!StringUtils.isEmpty(description) && emptyOverrideDescription) {
467470
operation.setDescription(description);
471+
}
472+
// if there is a previously set description
473+
// but no summary then it is intentional
474+
// we keep it as is
475+
if (!StringUtils.isEmpty(summary) && emptyOverrideSummary && emptyOverrideDescription) {
476+
operation.setSummary(javadocProvider.getFirstSentence(description));
477+
}
468478
}
469479

470480
Set<io.swagger.v3.oas.annotations.callbacks.Callback> apiCallbacks = AnnotatedElementUtils.findMergedRepeatableAnnotations(method, io.swagger.v3.oas.annotations.callbacks.Callback.class);

springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222

2323
import java.lang.annotation.Annotation;
2424
import java.lang.reflect.Method;
25+
import java.lang.reflect.Parameter;
2526
import java.lang.reflect.ParameterizedType;
2627
import java.lang.reflect.Type;
2728
import java.util.ArrayList;
2829
import java.util.Arrays;
30+
import java.util.HashMap;
2931
import java.util.HashSet;
3032
import java.util.LinkedHashMap;
3133
import java.util.List;
@@ -62,6 +64,7 @@
6264
import org.springframework.web.method.ControllerAdviceBean;
6365
import org.springframework.web.method.HandlerMethod;
6466

67+
import static java.util.Arrays.asList;
6568
import static org.springdoc.core.Constants.DEFAULT_DESCRIPTION;
6669
import static org.springdoc.core.SpringDocAnnotationsUtils.extractSchema;
6770
import static org.springdoc.core.SpringDocAnnotationsUtils.getContent;
@@ -73,6 +76,11 @@
7376
* @author bnasslahsen
7477
*/
7578
public class GenericResponseService {
79+
/**
80+
* This extension name is used to temporary store
81+
* the exception classes.
82+
*/
83+
private static final String EXTENSION_EXCEPTION_CLASSES = "x-exception-class";
7684

7785
/**
7886
* The Operation builder.
@@ -133,7 +141,11 @@ public GenericResponseService(OperationService operationService, List<ReturnType
133141
*/
134142
public ApiResponses build(Components components, HandlerMethod handlerMethod, Operation operation,
135143
MethodAttributes methodAttributes) {
136-
ApiResponses apiResponses = methodAttributes.calculateGenericMapResponse(getGenericMapResponse(handlerMethod.getBeanType()));
144+
Map<String, ApiResponse> genericMapResponse = getGenericMapResponse(handlerMethod.getBeanType());
145+
if (springDocConfigProperties.isOverrideWithGenericResponse()) {
146+
genericMapResponse = filterAndEnrichGenericMapResponseByDeclarations(handlerMethod, genericMapResponse);
147+
}
148+
ApiResponses apiResponses = methodAttributes.calculateGenericMapResponse(genericMapResponse);
137149
//Then use the apiResponses from documentation
138150
ApiResponses apiResponsesFromDoc = operation.getResponses();
139151
if (!CollectionUtils.isEmpty(apiResponsesFromDoc))
@@ -145,6 +157,41 @@ public ApiResponses build(Components components, HandlerMethod handlerMethod, Op
145157
return apiResponses;
146158
}
147159

160+
/**
161+
* Filters the generic API responses by the declared exceptions.
162+
* If Javadoc comment found for the declaration than it overrides the default description.
163+
*
164+
* @param handlerMethod the method which can have exception declarations
165+
* @param genericMapResponse the default generic API responses
166+
* @return the filtered and enriched responses
167+
*/
168+
private Map<String, ApiResponse> filterAndEnrichGenericMapResponseByDeclarations(HandlerMethod handlerMethod, Map<String, ApiResponse> genericMapResponse) {
169+
Map<String, ApiResponse> result = new HashMap<>();
170+
for (Map.Entry<String, ApiResponse> genericResponse : genericMapResponse.entrySet()) {
171+
Map<String, Object> extensions = genericResponse.getValue().getExtensions();
172+
Set<Class<?>> genericExceptions = (Set<Class<?>>) extensions.get(EXTENSION_EXCEPTION_CLASSES);
173+
for (Class<?> declaredException : handlerMethod.getMethod().getExceptionTypes()) {
174+
if (genericExceptions.contains(declaredException)) {
175+
ApiResponse clone = cloneApiResponse(genericResponse.getValue());
176+
clone.getExtensions().remove(EXTENSION_EXCEPTION_CLASSES);
177+
if (operationService.getJavadocProvider() != null) {
178+
JavadocProvider javadocProvider = operationService.getJavadocProvider();
179+
Map<String, String> javadocThrows = javadocProvider.getMethodJavadocThrows(handlerMethod.getMethod());
180+
String description = javadocThrows.get(declaredException.getName());
181+
if (description == null) {
182+
description = javadocThrows.get(declaredException.getSimpleName());
183+
}
184+
if (description != null && !description.trim().isEmpty()) {
185+
clone.setDescription(description);
186+
}
187+
}
188+
result.put(genericResponse.getKey(), clone);
189+
}
190+
}
191+
}
192+
return result;
193+
}
194+
148195
/**
149196
* Build generic response.
150197
*
@@ -502,6 +549,23 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent()))
502549
if (schemaN != null && ArrayUtils.isNotEmpty(methodAttributes.getMethodProduces()))
503550
Arrays.stream(methodAttributes.getMethodProduces()).forEach(mediaTypeStr -> mergeSchema(existingContent, schemaN, mediaTypeStr));
504551
}
552+
if (springDocConfigProperties.isOverrideWithGenericResponse()
553+
&& methodParameter.getExecutable().isAnnotationPresent(ExceptionHandler.class)) {
554+
// ExceptionHandler's exception class resolution is non-trivial
555+
// more info on its javadoc
556+
ExceptionHandler exceptionHandler = methodParameter.getExecutable().getAnnotation(ExceptionHandler.class);
557+
Set<Class<?>> exceptions = new HashSet<>();
558+
if (exceptionHandler.value().length == 0) {
559+
for (Parameter parameter : methodParameter.getExecutable().getParameters()) {
560+
if (Throwable.class.isAssignableFrom(parameter.getType())) {
561+
exceptions.add(parameter.getType());
562+
}
563+
}
564+
} else {
565+
exceptions.addAll(asList(exceptionHandler.value()));
566+
}
567+
apiResponse.addExtension(EXTENSION_EXCEPTION_CLASSES, exceptions);
568+
}
505569
apiResponsesOp.addApiResponse(httpCode, apiResponse);
506570
}
507571

@@ -620,4 +684,15 @@ private boolean isHttpCodePresent(String httpCode, Set<io.swagger.v3.oas.annotat
620684
public static void setResponseEntityExceptionHandlerClass(Class<?> responseEntityExceptionHandlerClass) {
621685
GenericResponseService.responseEntityExceptionHandlerClass = responseEntityExceptionHandlerClass;
622686
}
687+
688+
private ApiResponse cloneApiResponse(ApiResponse original) {
689+
ApiResponse clone = new ApiResponse();
690+
clone.set$ref(original.get$ref());
691+
clone.setDescription(original.getDescription());
692+
clone.setContent(original.getContent());
693+
clone.setHeaders(original.getHeaders() == null ? null : new HashMap<>(original.getHeaders()));
694+
clone.setExtensions(original.getExtensions() == null ? null : new HashMap<>(original.getExtensions()));
695+
clone.setLinks(original.getLinks() == null ? null : new HashMap<>(original.getLinks()));
696+
return clone;
697+
}
623698
}

springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.slf4j.LoggerFactory;
5858
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
5959
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
60+
import org.springdoc.core.providers.JavadocProvider;
6061

6162
import org.springframework.beans.BeansException;
6263
import org.springframework.beans.factory.config.BeanDefinition;
@@ -155,6 +156,11 @@ public class OpenAPIService implements ApplicationContextAware {
155156
*/
156157
private PropertyResolverUtils propertyResolverUtils;
157158

159+
/**
160+
* The javadoc provider.
161+
*/
162+
private Optional<JavadocProvider> javadocProvider;
163+
158164
/**
159165
* The Basic error controller.
160166
*/
@@ -189,7 +195,8 @@ public class OpenAPIService implements ApplicationContextAware {
189195
public OpenAPIService(Optional<OpenAPI> openAPI, SecurityService securityParser,
190196
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
191197
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
192-
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers) {
198+
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
199+
Optional<JavadocProvider> javadocProvider) {
193200
if (openAPI.isPresent()) {
194201
this.openAPI = openAPI.get();
195202
if (this.openAPI.getComponents() == null)
@@ -204,6 +211,7 @@ public OpenAPIService(Optional<OpenAPI> openAPI, SecurityService securityParser,
204211
this.springDocConfigProperties = springDocConfigProperties;
205212
this.openApiBuilderCustomisers = openApiBuilderCustomizers;
206213
this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
214+
this.javadocProvider = javadocProvider;
207215
if (springDocConfigProperties.isUseFqn())
208216
TypeNameResolver.std.setUseFqn(true);
209217
}
@@ -348,8 +356,21 @@ public Operation buildTags(HandlerMethod handlerMethod, Operation operation, Ope
348356
}
349357
}
350358

351-
if (isAutoTagClasses(operation))
352-
operation.addTagsItem(splitCamelCase(handlerMethod.getBeanType().getSimpleName()));
359+
if (isAutoTagClasses(operation)) {
360+
String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
361+
operation.addTagsItem(tagAutoName);
362+
if (javadocProvider.isPresent()) {
363+
String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
364+
if (StringUtils.isNotBlank(description)) {
365+
io.swagger.v3.oas.models.tags.Tag tag = new io.swagger.v3.oas.models.tags.Tag();
366+
tag.setName(tagAutoName);
367+
tag.setDescription(description);
368+
if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
369+
openAPI.addTagsItem(tag);
370+
}
371+
}
372+
}
373+
}
353374

354375
if (!CollectionUtils.isEmpty(tags)) {
355376
// Existing tags
@@ -733,7 +754,7 @@ public SecurityService getSecurityParser() {
733754

734755
/**
735756
* Gets server base URL
736-
*
757+
*
737758
* @return the server base URL
738759
*/
739760
public String getServerBaseUrl() {

springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public class SpringDocConfigProperties {
115115
/**
116116
* The Override with generic response.
117117
*/
118-
private boolean overrideWithGenericResponse = true;
118+
private Boolean overrideWithGenericResponse;
119119

120120
/**
121121
* The Remove broken reference definitions.
@@ -579,16 +579,28 @@ public void setDefaultProducesMediaType(String defaultProducesMediaType) {
579579
*
580580
* @return the boolean
581581
*/
582-
public boolean isOverrideWithGenericResponse() {
583-
return overrideWithGenericResponse;
582+
public Boolean isOverrideWithGenericResponse() {
583+
return overrideWithGenericResponse != null && overrideWithGenericResponse;
584+
}
585+
586+
/**
587+
* Gets default override with generic response.
588+
*
589+
* @return the default override with generic response
590+
*/
591+
public boolean isDefaultOverrideWithGenericResponse() {
592+
if (overrideWithGenericResponse == null)
593+
return true;
594+
else
595+
return overrideWithGenericResponse;
584596
}
585597

586598
/**
587599
* Sets override with generic response.
588600
*
589601
* @param overrideWithGenericResponse the override with generic response
590602
*/
591-
public void setOverrideWithGenericResponse(boolean overrideWithGenericResponse) {
603+
public void setOverrideWithGenericResponse(Boolean overrideWithGenericResponse) {
592604
this.overrideWithGenericResponse = overrideWithGenericResponse;
593605
}
594606

springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ OpenAPIService openAPIBuilder(Optional<OpenAPI> openAPI,
228228
SecurityService securityParser,
229229
SpringDocConfigProperties springDocConfigProperties,PropertyResolverUtils propertyResolverUtils,
230230
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
231-
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers) {
232-
return new OpenAPIService(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers);
231+
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
232+
return new OpenAPIService(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
233233
}
234234

235235
/**

springdoc-openapi-common/src/main/java/org/springdoc/core/providers/JavadocProvider.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,22 @@
2222

2323
import java.lang.reflect.Field;
2424
import java.lang.reflect.Method;
25+
import java.util.Map;
2526

2627
/**
2728
* The interface Javadoc provider.
2829
* @author bnasslashen
2930
*/
3031
public interface JavadocProvider {
3132

33+
/**
34+
* Gets class description.
35+
*
36+
* @param cl the class
37+
* @return the class description
38+
*/
39+
String getClassJavadoc(Class<?> cl);
40+
3241
/**
3342
* Gets method description.
3443
*
@@ -45,6 +54,14 @@ public interface JavadocProvider {
4554
*/
4655
String getMethodJavadocReturn(Method method);
4756

57+
/**
58+
* Gets method throws declaration.
59+
*
60+
* @param method the method
61+
* @return the method throws (name-description map)
62+
*/
63+
Map<String, String> getMethodJavadocThrows(Method method);
64+
4865
/**
4966
* Gets param javadoc.
5067
*
@@ -55,5 +72,12 @@ public interface JavadocProvider {
5572
String getParamJavadoc(Method method, String name);
5673

5774
String getFieldJavadoc(Field field);
75+
76+
/**
77+
* Returns the first sentence of a javadoc comment.
78+
* @param text the javadoc comment's text
79+
* @return the first sentence based on javadoc guidelines
80+
*/
81+
String getFirstSentence(String text);
5882
}
5983

springdoc-openapi-javadoc/src/main/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
7676
if (!CollectionUtils.isEmpty(fields)) {
7777
if (!type.isSchemaProperty()) {
7878
Schema existingSchema = context.resolve(type);
79-
setJavadocDescription(fields, existingSchema);
79+
setJavadocDescription(cls, fields, existingSchema);
8080
}
8181
else if (resolvedSchema != null && resolvedSchema.get$ref() != null && resolvedSchema.get$ref().contains(AnnotationsUtils.COMPONENTS_REF)) {
8282
String schemaName = resolvedSchema.get$ref().substring(21);
8383
Schema existingSchema = context.getDefinedModels().get(schemaName);
84-
setJavadocDescription(fields, existingSchema);
84+
setJavadocDescription(cls, fields, existingSchema);
8585
}
8686
}
8787
return resolvedSchema;
@@ -96,8 +96,11 @@ else if (resolvedSchema != null && resolvedSchema.get$ref() != null && resolvedS
9696
* @param fields the fields
9797
* @param existingSchema the existing schema
9898
*/
99-
private void setJavadocDescription(List<Field> fields, Schema existingSchema) {
99+
private void setJavadocDescription(Class<?> cls, List<Field> fields, Schema existingSchema) {
100100
if (existingSchema != null) {
101+
if (StringUtils.isBlank(existingSchema.getDescription())) {
102+
existingSchema.setDescription(javadocProvider.getClassJavadoc(cls));
103+
}
101104
Map<String, Schema> properties = existingSchema.getProperties();
102105
if (!CollectionUtils.isEmpty(properties))
103106
properties.entrySet().stream()

0 commit comments

Comments
 (0)