Repository: springdoc/springdoc-openapi
Pull request #628 (2020-04-29 12:24:16)

Make use of @Deprecated annotations for parameters and model fields
Currently `@Deprecated` annotation affects only whole API operations.

Within this PR I'm introducing a mechanism to mark model attributes and operation parameters as deprecated as well, using the standard `@Deprecated` annotation.


Patch:
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java b/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java
index a9433a0b9..2dd7e26b7 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java
@@ -19,6 +19,8 @@
 package org.springdoc.api;
 
 import java.io.UnsupportedEncodingException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
@@ -33,6 +35,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import com.fasterxml.jackson.annotation.JsonView;
 import io.swagger.v3.core.filter.SpecFilter;
@@ -57,6 +60,7 @@ import org.springdoc.core.SpringDocConfigProperties.GroupConfig;
 import org.springdoc.core.customizers.OpenApiCustomiser;
 import org.springdoc.core.customizers.OperationCustomizer;
 
+import org.springframework.core.MethodParameter;
 import org.springframework.core.annotation.AnnotatedElementUtils;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.util.AntPathMatcher;
@@ -74,10 +78,10 @@ public abstract class AbstractOpenApiResource extends SpecFilter {
 
 	private static final List<Class<?>> HIDDEN_REST_CONTROLLERS = new ArrayList<>();
 
-	private static final List<Class> DEPRECATED_TYPES = new ArrayList<>();
+	private static final List<Class<? extends Annotation>> DEPRECATED_ANNOTATIONS = new ArrayList<>();
 
 	static {
-		DEPRECATED_TYPES.add(Deprecated.class);
+		DEPRECATED_ANNOTATIONS.add(Deprecated.class);
 	}
 
 	protected final OpenAPIBuilder openAPIBuilder;
@@ -125,8 +129,8 @@ public abstract class AbstractOpenApiResource extends SpecFilter {
 		HIDDEN_REST_CONTROLLERS.addAll(Arrays.asList(classes));
 	}
 
-	public static void addDeprecatedType(Class<?> cls) {
-		DEPRECATED_TYPES.add(cls);
+	public static void addDeprecatedType(Class<? extends Annotation> cls) {
+		DEPRECATED_ANNOTATIONS.add(cls);
 	}
 
 	protected synchronized OpenAPI getOpenApi() {
@@ -196,7 +200,7 @@ public abstract class AbstractOpenApiResource extends SpecFilter {
 
 			Operation operation = (existingOperation != null) ? existingOperation : new Operation();
 
-			if (isDeprecatedType(method))
+			if (isDeprecated(method))
 				operation.setDeprecated(true);
 
 			// Add documentation from operation annotation
@@ -411,8 +415,11 @@ public abstract class AbstractOpenApiResource extends SpecFilter {
 		return operation;
 	}
 
-	private boolean isDeprecatedType(Method method) {
-		return DEPRECATED_TYPES.stream().anyMatch(clazz -> (AnnotatedElementUtils.findMergedAnnotation(method, clazz) != null));
+	private static boolean isDeprecated(AnnotatedElement annotatedElement) {
+		return DEPRECATED_ANNOTATIONS.stream().anyMatch(annoClass -> AnnotatedElementUtils.findMergedAnnotation(annotatedElement, annoClass) != null);
 	}
 
+	public static boolean containsDeprecatedAnnotation(Annotation[] annotations) {
+		return annotations != null && Stream.of(annotations).map(Annotation::annotationType).anyMatch(DEPRECATED_ANNOTATIONS::contains);
+	}
 }
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java
index a5dd5102f..f23da257b 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java
@@ -53,6 +53,7 @@ import io.swagger.v3.oas.models.media.StringSchema;
 import io.swagger.v3.oas.models.parameters.Parameter;
 import io.swagger.v3.oas.models.parameters.RequestBody;
 import org.apache.commons.lang3.StringUtils;
+import org.springdoc.api.AbstractOpenApiResource;
 import org.springdoc.core.customizers.ParameterCustomizer;
 
 import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
@@ -338,6 +339,10 @@ public abstract class AbstractRequestBuilder {
 		if (required != null && parameter.getRequired() == null)
 			parameter.setRequired(required);
 
+		if (AbstractOpenApiResource.containsDeprecatedAnnotation(parameterInfo.getMethodParameter().getParameterAnnotations())) {
+			parameter.setDeprecated(true);
+		}
+
 		if (parameter.getSchema() == null) {
 			Schema<?> schema = parameterBuilder.calculateSchema(components, parameterInfo, null,
 					jsonView);
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java
index 289112264..b5c137cc8 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java
@@ -31,6 +31,7 @@ import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.media.ObjectSchema;
 import io.swagger.v3.oas.models.media.Schema;
 import org.springdoc.core.converters.AdditionalModelsConverter;
+import org.springdoc.core.converters.SchemaPropertyDeprecatingConverter;
 import org.springdoc.core.converters.FileSupportConverter;
 import org.springdoc.core.converters.ModelConverterRegistrar;
 import org.springdoc.core.converters.PropertyCustomizingConverter;
@@ -105,6 +106,13 @@ public class SpringDocConfiguration {
 		return new ResponseSupportConverter();
 	}
 
+	@Bean
+	@ConditionalOnMissingBean
+	@Lazy(false)
+	SchemaPropertyDeprecatingConverter schemaPropertyDeprecatingConverter() {
+		return new SchemaPropertyDeprecatingConverter();
+	}
+
 	@Bean
 	@ConditionalOnMissingBean
 	OpenAPIBuilder openAPIBuilder(Optional<OpenAPI> openAPI, ApplicationContext context,
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocUtils.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocUtils.java
index 0872971dd..97c399f93 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocUtils.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocUtils.java
@@ -18,6 +18,7 @@
 
 package org.springdoc.core;
 
+import java.lang.annotation.Annotation;
 import java.util.function.Predicate;
 
 import io.swagger.v3.oas.models.media.Schema;
@@ -36,7 +37,7 @@ public class SpringDocUtils {
 		return springDocConfig;
 	}
 
-	public SpringDocUtils addDeprecatedType(Class<?> cls) {
+	public SpringDocUtils addDeprecatedType(Class<? extends Annotation> cls) {
 		AbstractOpenApiResource.addDeprecatedType(cls);
 		return this;
 	}
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/SchemaPropertyDeprecatingConverter.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/SchemaPropertyDeprecatingConverter.java
new file mode 100644
index 000000000..3dba92118
--- /dev/null
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/SchemaPropertyDeprecatingConverter.java
@@ -0,0 +1,23 @@
+package org.springdoc.core.converters;
+
+import java.util.Iterator;
+
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.core.converter.ModelConverter;
+import io.swagger.v3.core.converter.ModelConverterContext;
+import io.swagger.v3.oas.models.media.Schema;
+import org.springdoc.api.AbstractOpenApiResource;
+
+public class SchemaPropertyDeprecatingConverter implements ModelConverter {
+	@Override
+	public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
+		if (chain.hasNext()) {
+			Schema<?> resolvedSchema = chain.next().resolve(type, context, chain);
+			if (type.isSchemaProperty() && AbstractOpenApiResource.containsDeprecatedAnnotation(type.getCtxAnnotations())) {
+				resolvedSchema.setDeprecated(true);
+			}
+			return resolvedSchema;
+		}
+		return null;
+	}
+}


Test patch:
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app1/ItemDTO.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app1/ItemDTO.java
index 2edc6ec8f..ebb3eacda 100644
--- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app1/ItemDTO.java
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app1/ItemDTO.java
@@ -45,6 +45,9 @@ public class ItemDTO implements Serializable {
 	 */
 	private int price;
 
+	@Deprecated
+	private int deprecatedPrice;
+
 	/**
 	 *
 	 */
@@ -113,4 +116,11 @@ public class ItemDTO implements Serializable {
 		this.price = price;
 	}
 
+	public int getDeprecatedPrice() {
+		return deprecatedPrice;
+	}
+
+	public void setDeprecatedPrice(int deprecatedPrice) {
+		this.deprecatedPrice = deprecatedPrice;
+	}
 }
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app1/ItemLightDTO.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app1/ItemLightDTO.java
index b8906010d..d078a3d6c 100644
--- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app1/ItemLightDTO.java
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app1/ItemLightDTO.java
@@ -40,6 +40,9 @@ public class ItemLightDTO implements Serializable {
 	 */
 	private int price;
 
+	@Deprecated
+	private int deprecatedPrice;
+
 	/**
 	 *
 	 */
@@ -80,4 +83,11 @@ public class ItemLightDTO implements Serializable {
 		this.price = price;
 	}
 
+	public int getDeprecatedPrice() {
+		return deprecatedPrice;
+	}
+
+	public void setDeprecatedPrice(int deprecatedPrice) {
+		this.deprecatedPrice = deprecatedPrice;
+	}
 }
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app102/RequestParams.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app102/RequestParams.java
index db54c1df6..9c40ff49f 100644
--- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app102/RequestParams.java
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app102/RequestParams.java
@@ -13,6 +13,7 @@ public class RequestParams {
 	@Parameter(description = "string parameter")
 	private String stringParam;
 
+	@Deprecated
 	private String stringParam1;
 
 	@Parameter(description = "string parameter2", required = true)
diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/app1.json b/springdoc-openapi-webmvc-core/src/test/resources/results/app1.json
index 5ae32e9c9..3c20de298 100644
--- a/springdoc-openapi-webmvc-core/src/test/resources/results/app1.json
+++ b/springdoc-openapi-webmvc-core/src/test/resources/results/app1.json
@@ -561,30 +561,40 @@
           }
         }
       },
-      "ItemDTO": {
+      "ItemLightDTO": {
         "type": "object",
         "properties": {
-          "itemID": {
-            "type": "string"
-          },
           "description": {
             "type": "string"
           },
           "price": {
             "type": "integer",
             "format": "int32"
+          },
+          "deprecatedPrice": {
+            "type": "integer",
+            "format": "int32",
+            "deprecated": true
           }
         }
       },
-      "ItemLightDTO": {
+      "ItemDTO": {
         "type": "object",
         "properties": {
+          "itemID": {
+            "type": "string"
+          },
           "description": {
             "type": "string"
           },
           "price": {
             "type": "integer",
             "format": "int32"
+          },
+          "deprecatedPrice": {
+            "type": "integer",
+            "format": "int32",
+            "deprecated": true
           }
         }
       },
@@ -610,4 +620,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/app102.json b/springdoc-openapi-webmvc-core/src/test/resources/results/app102.json
index fa88bbfe3..e7f657cbc 100644
--- a/springdoc-openapi-webmvc-core/src/test/resources/results/app102.json
+++ b/springdoc-openapi-webmvc-core/src/test/resources/results/app102.json
@@ -48,6 +48,7 @@
             "name": "stringParam1",
             "in": "query",
             "required": false,
+            "deprecated": true,
             "schema": {
               "type": "string"
             }
@@ -138,4 +139,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file