diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSpecificationStringPropertiesConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSpecificationStringPropertiesConfiguration.java new file mode 100644 index 000000000..ff2581f78 --- /dev/null +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSpecificationStringPropertiesConfiguration.java @@ -0,0 +1,103 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2022 the original author or authors. + * * * * * + * * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * * you may not use this file except in compliance with the License. + * * * * * You may obtain a copy of the License at + * * * * * + * * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * * + * * * * * Unless required by applicable law or agreed to in writing, software + * * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * * See the License for the specific language governing permissions and + * * * * * limitations under the License. + * * * * + * * * + * * + * + */ + +package org.springdoc.core.configuration; + +import org.springdoc.core.customizers.SpecificationStringPropertiesCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.PropertyResolver; + +import java.util.List; + +/** + * The type Spring doc specification string properties configuration. + * + * @author Anton Tkachenko tkachenkoas@gmail.com + */ +@Lazy(false) +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "springdoc.api-docs.specification-string-properties") +@ConditionalOnBean(SpringDocConfiguration.class) +public class SpringDocSpecificationStringPropertiesConfiguration { + + /** + * Springdoc customizer that takes care of the specification string properties customization. + * Will be applied to general openapi schema. + * + * @return the springdoc customizer + */ + @Bean + @ConditionalOnMissingBean + @Lazy(false) + SpecificationStringPropertiesCustomizer specificationStringPropertiesCustomizer( + PropertyResolver propertyResolverUtils + ) { + return new SpecificationStringPropertiesCustomizer(propertyResolverUtils); + } + + /** + * Bean post processor that applies the specification string properties customization to + * grouped openapi schemas by using group name as a prefix for properties. + * + * @return the bean post processor + */ + @Bean + @ConditionalOnMissingBean + @Lazy(false) + SpecificationStringPropertiesCustomizerBeanPostProcessor specificationStringPropertiesCustomizerBeanPostProcessor( + PropertyResolver propertyResolverUtils + ) { + return new SpecificationStringPropertiesCustomizerBeanPostProcessor(propertyResolverUtils); + } + + + private static class SpecificationStringPropertiesCustomizerBeanPostProcessor implements BeanPostProcessor { + + private final PropertyResolver propertyResolverUtils; + + public SpecificationStringPropertiesCustomizerBeanPostProcessor( + PropertyResolver propertyResolverUtils + ) { + this.propertyResolverUtils = propertyResolverUtils; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof GroupedOpenApi groupedOpenApi) { + groupedOpenApi.addAllOpenApiCustomizer(List.of(new SpecificationStringPropertiesCustomizer( + propertyResolverUtils, groupedOpenApi.getGroup() + ))); + } + return bean; + } + } + +} \ No newline at end of file diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/customizers/SpecificationStringPropertiesCustomizer.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/customizers/SpecificationStringPropertiesCustomizer.java new file mode 100644 index 000000000..a7a2c743a --- /dev/null +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/customizers/SpecificationStringPropertiesCustomizer.java @@ -0,0 +1,165 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2022 the original author or authors. + * * * * * + * * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * * you may not use this file except in compliance with the License. + * * * * * You may obtain a copy of the License at + * * * * * + * * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * * + * * * * * Unless required by applicable law or agreed to in writing, software + * * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * * See the License for the specific language governing permissions and + * * * * * limitations under the License. + * * * * + * * * + * * + * + */ + +package org.springdoc.core.customizers; + +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.env.PropertyResolver; +import org.springframework.util.CollectionUtils; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Allows externalizing strings in generated openapi schema via properties that follow + * conventional naming similar or identical to openapi schema + *
+ * To set value of a string in schema, define an application property that matches the target node + * with springdoc.specification-strings prefix. + *
+ * Sample supported properties for api-info customization: + *
+ * Sample supported properties for components customization: + *
+ * Sample supported properties for paths/operationIds customization: + *
+ * Support for groped openapi customization is similar to the above, but with a group name prefix. + * E.g. + *
+ * A test for {@link org.springdoc.core.customizers.SpecificationStringPropertiesCustomizer} + */ +@ActiveProfiles("212") +public class SpringDocApp212Test extends AbstractSpringDocTest { + + /** + * The type Spring doc test app. + */ + @SpringBootApplication + static class SpringDocTestApp { + + @Bean + GroupedOpenApi apiGroupBeanName() { + return GroupedOpenApi.builder() + .group("apiGroupName") + .packagesToScan("test.org.springdoc.api.app212") + .build(); + } + } + + @Test + void getGroupedOpenapi_shouldCustomizeFromPropertiesWithGroupNamePrefix() throws Exception { + String result = mockMvc.perform(get("/v3/api-docs/apiGroupName")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + String expected = getContent("results/app212-grouped.json"); + assertEquals(expected, result, true); + } + +} diff --git a/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/application-212.yml b/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/application-212.yml new file mode 100644 index 000000000..7936144b8 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/application-212.yml @@ -0,0 +1,37 @@ +springdoc: + api-docs: + specification-string-properties: true + specification-strings: + info: + title: Api info title + description: Api info description + version: Api info version + components: + schemas: + PersonDTO: + description: Description for PersonDTO component + properties: + name: + description: Description for 'name' property + example: Example value for 'name' property + paths: + persons: + description: Description of operationId 'persons' + summary: Summary of operationId 'persons' + apiGroupName: + info: + title: ApiGroupName info title + description: ApiGroupName info description + version: ApiGroupName info version + components: + schemas: + PersonDTO: + description: Description for PersonDTO component in ApiGroupName + properties: + name: + description: Description for 'name' property in ApiGroupName + example: Example value for 'name' property in ApiGroupName + paths: + persons: + description: Description of operationId 'persons' in ApiGroupName + summary: Summary of operationId 'persons' in ApiGroupName \ No newline at end of file diff --git a/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/results/app212-grouped.json b/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/results/app212-grouped.json new file mode 100644 index 000000000..72ac0c1f1 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/results/app212-grouped.json @@ -0,0 +1,53 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "ApiGroupName info title", + "description": "ApiGroupName info description", + "version": "ApiGroupName info version" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/persons": { + "get": { + "tags": [ + "hello-controller" + ], + "summary": "Summary of operationId 'persons' in ApiGroupName", + "description": "Description of operationId 'persons' in ApiGroupName", + "operationId": "persons", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/PersonDTO" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PersonDTO": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Description for 'name' property in ApiGroupName", + "example": "Example value for 'name' property in ApiGroupName" + } + }, + "description": "Description for PersonDTO component in ApiGroupName" + } + } + } +} \ No newline at end of file diff --git a/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/results/app212.json b/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/results/app212.json new file mode 100644 index 000000000..4576d3406 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-groovy-tests/src/test/resources/results/app212.json @@ -0,0 +1,54 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Api info title", + "description": "Api info description", + "version": "Api info version" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/persons": { + "get": { + "tags": [ + "hello-controller" + ], + "summary": "Summary of operationId 'persons'", + "description": "Description of operationId 'persons'", + "operationId": "persons", + "responses": { + "200": { + "description":"OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/PersonDTO" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PersonDTO": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Description for 'name' property", + "example": "Example value for 'name' property" + } + }, + "description": "Description for PersonDTO component" + } + } + } +} +