From ef02bc99655ee244a11561d419c853ba66a7a010 Mon Sep 17 00:00:00 2001 From: Koen Bos Date: Thu, 25 Jan 2024 12:02:32 +0100 Subject: [PATCH] Support dynamic evaluation of description field in the RequestBody annotation Fixes #2488 --- .../api/AbstractOpenApiResource.java | 2 +- .../configuration/SpringDocConfiguration.java | 4 +- .../core/service/OperationService.java | 3 +- .../core/service/RequestBodyService.java | 29 ++-- .../org/springdoc/api/app173/Example.java | 21 +++ .../api/app173/ExampleController.java | 38 +++++ .../api/app173/SpringDocApp173Test.java | 20 +++ .../src/test/resources/results/app173.json | 132 ++++++++++++++++++ 8 files changed, 235 insertions(+), 14 deletions(-) create mode 100644 springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/Example.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/ExampleController.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/SpringDocApp173Test.java create mode 100644 springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/resources/results/app173.json diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java index e32175ac4..fbdc15a59 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java @@ -490,7 +490,7 @@ protected void calculatePath(HandlerMethod handlerMethod, RouterOperation router // RequestBody in Operation requestBuilder.getRequestBodyBuilder() .buildRequestBodyFromDoc(requestBodyDoc, methodAttributes, components, - methodAttributes.getJsonViewAnnotationForRequestBody()) + methodAttributes.getJsonViewAnnotationForRequestBody(), locale) .ifPresent(operation::setRequestBody); // requests operation = requestBuilder.build(handlerMethod, requestMethod, operation, methodAttributes, openAPI); diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java index 8a892d064..f20ea572c 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java @@ -343,8 +343,8 @@ PropertyResolverUtils propertyResolverUtils(ConfigurableBeanFactory factory, Mes @Bean @ConditionalOnMissingBean @Lazy(false) - RequestBodyService requestBodyBuilder(GenericParameterService parameterBuilder) { - return new RequestBodyService(parameterBuilder); + RequestBodyService requestBodyBuilder(GenericParameterService parameterBuilder, PropertyResolverUtils propertyResolverUtils) { + return new RequestBodyService(parameterBuilder, propertyResolverUtils); } /** diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OperationService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OperationService.java index 5cb3b6750..00c206508 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OperationService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OperationService.java @@ -153,7 +153,8 @@ public OpenAPI parse(io.swagger.v3.oas.annotations.Operation apiOperation, } // RequestBody in Operation - requestBodyService.buildRequestBodyFromDoc(apiOperation.requestBody(), operation.getRequestBody(), methodAttributes, components).ifPresent(operation::setRequestBody); + requestBodyService.buildRequestBodyFromDoc(apiOperation.requestBody(), operation.getRequestBody(), methodAttributes, components, locale) + .ifPresent(operation::setRequestBody); // build response buildResponse(components, apiOperation, operation, methodAttributes); diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/RequestBodyService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/RequestBodyService.java index 5ea0f0bfb..9d53727eb 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/RequestBodyService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/RequestBodyService.java @@ -25,6 +25,7 @@ package org.springdoc.core.service; import java.util.Arrays; +import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -39,6 +40,7 @@ import org.springdoc.core.models.MethodAttributes; import org.springdoc.core.models.ParameterInfo; import org.springdoc.core.models.RequestBodyInfo; +import org.springdoc.core.utils.PropertyResolverUtils; import org.springdoc.core.utils.SpringDocAnnotationsUtils; import org.springframework.core.MethodParameter; @@ -58,14 +60,21 @@ public class RequestBodyService { */ private final GenericParameterService parameterBuilder; + /** + * The Property resolver utils. + */ + private final PropertyResolverUtils propertyResolverUtils; + /** * Instantiates a new Request body builder. * * @param parameterBuilder the parameter builder + * @param propertyResolverUtils the property resolver utils */ - public RequestBodyService(GenericParameterService parameterBuilder) { + public RequestBodyService(GenericParameterService parameterBuilder, PropertyResolverUtils propertyResolverUtils) { super(); this.parameterBuilder = parameterBuilder; + this.propertyResolverUtils = propertyResolverUtils; } /** @@ -76,11 +85,12 @@ public RequestBodyService(GenericParameterService parameterBuilder) { * @param methodAttributes the method attributes * @param components the components * @param jsonViewAnnotation the json view annotation + * @param locale the locale * @return the optional */ public Optional buildRequestBodyFromDoc( io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes, - Components components, JsonView jsonViewAnnotation) { + Components components, JsonView jsonViewAnnotation, Locale locale) { String[] classConsumes = methodAttributes.getClassConsumes(); String[] methodConsumes = methodAttributes.getMethodConsumes(); @@ -95,7 +105,7 @@ public Optional buildRequestBodyFromDoc( } if (StringUtils.isNotBlank(requestBody.description())) { - requestBodyObject.setDescription(requestBody.description()); + requestBodyObject.setDescription(propertyResolverUtils.resolve(requestBody.description(), locale)); isEmpty = false; } @@ -191,8 +201,7 @@ private String[] getConsumes(String[] classConsumes) { */ public Optional buildRequestBodyFromDoc(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, MethodAttributes methodAttributes, Components components) { - return this.buildRequestBodyFromDoc(requestBody, null, methodAttributes, - components, null); + return this.buildRequestBodyFromDoc(requestBody, null, methodAttributes, components, null, null); } /** @@ -202,12 +211,13 @@ public Optional buildRequestBodyFromDoc(io.swagger.v3.oas.annotatio * @param methodAttributes the method attributes * @param components the components * @param jsonViewAnnotation the json view annotation + * @param locale the locale * @return the optional */ public Optional buildRequestBodyFromDoc(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, - MethodAttributes methodAttributes, Components components, JsonView jsonViewAnnotation) { + MethodAttributes methodAttributes, Components components, JsonView jsonViewAnnotation, Locale locale) { return this.buildRequestBodyFromDoc(requestBody, null, methodAttributes, - components, jsonViewAnnotation); + components, jsonViewAnnotation, locale); } /** @@ -221,9 +231,8 @@ public Optional buildRequestBodyFromDoc(io.swagger.v3.oas.annotatio */ public Optional buildRequestBodyFromDoc( io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes, - Components components) { - return this.buildRequestBodyFromDoc(requestBody, requestBodyOp, methodAttributes, - components, null); + Components components, Locale locale) { + return this.buildRequestBodyFromDoc(requestBody, requestBodyOp, methodAttributes, components, null, locale); } /** diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/Example.java b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/Example.java new file mode 100644 index 000000000..b464b8493 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/Example.java @@ -0,0 +1,21 @@ +package test.org.springdoc.api.app173; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * The Example object + */ +@Schema +public class Example { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/ExampleController.java b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/ExampleController.java new file mode 100644 index 000000000..6853dffbb --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/ExampleController.java @@ -0,0 +1,38 @@ +package test.org.springdoc.api.app173; + +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +import static org.springframework.http.HttpStatus.OK; + +/** + * The Example Controller + */ +@RestController +public class ExampleController { + + @PostMapping("/example") + @Operation(summary = "insert example", description = "Allows to insert an example") + public ResponseEntity postExample(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "${example.description}") @RequestBody Example example) { + return new ResponseEntity<>(UUID.randomUUID(), OK); + } + + @PutMapping("/example") + @Operation(summary = "update example", description = "Allows to update an example") + public ResponseEntity putExample(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "${example2.description:Default description for example}") @RequestBody Example example) { + return new ResponseEntity<>(UUID.randomUUID(), OK); + } + + @PatchMapping("/example") + @Operation(summary = "patch example", description = "Allows to patch an example") + public ResponseEntity patchExample(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Description without the use of variables") @RequestBody Example example) { + return new ResponseEntity<>(UUID.randomUUID(), OK); + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/SpringDocApp173Test.java b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/SpringDocApp173Test.java new file mode 100644 index 000000000..499778a52 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app173/SpringDocApp173Test.java @@ -0,0 +1,20 @@ +package test.org.springdoc.api.app173; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; +import test.org.springdoc.api.AbstractSpringDocTest; + +/** + * The type Spring doc app 173 test. + */ +@TestPropertySource(properties = "example.description=The example object") +public class SpringDocApp173Test extends AbstractSpringDocTest { + + /** + * The type Spring doc test app. + */ + @SpringBootApplication + static class SpringDocTestApp { + } + +} diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/resources/results/app173.json b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/resources/results/app173.json new file mode 100644 index 000000000..d73d621c4 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/resources/results/app173.json @@ -0,0 +1,132 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "tags": [ + { + "name": "example-controller", + "description": "The Example Controller" + } + ], + "paths": { + "/example": { + "put": { + "tags": [ + "example-controller" + ], + "summary": "update example", + "description": "Allows to update an example", + "operationId": "putExample", + "requestBody": { + "description": "Default description for example", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Example" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "post": { + "tags": [ + "example-controller" + ], + "summary": "insert example", + "description": "Allows to insert an example", + "operationId": "postExample", + "requestBody": { + "description": "The example object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Example" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "patch": { + "tags": [ + "example-controller" + ], + "summary": "patch example", + "description": "Allows to patch an example", + "operationId": "patchExample", + "requestBody": { + "description": "Description without the use of variables", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Example" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Example": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "description": "The Example object" + } + } + } +}