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 5142042c1..2f7248809 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 @@ -18,44 +18,19 @@ package org.springdoc.api; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Method; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - import com.fasterxml.jackson.annotation.JsonView; import io.swagger.v3.core.filter.SpecFilter; import io.swagger.v3.core.util.ReflectionUtils; import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.Operation; -import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.*; import io.swagger.v3.oas.models.PathItem.HttpMethod; -import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.responses.ApiResponses; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springdoc.core.AbstractRequestBuilder; -import org.springdoc.core.GenericResponseBuilder; -import org.springdoc.core.MethodAttributes; -import org.springdoc.core.OpenAPIBuilder; -import org.springdoc.core.OperationBuilder; -import org.springdoc.core.SpringDocConfigProperties; +import org.springdoc.core.*; import org.springdoc.core.SpringDocConfigProperties.GroupConfig; import org.springdoc.core.customizers.OpenApiCustomiser; - import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.AntPathMatcher; @@ -65,6 +40,15 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.method.HandlerMethod; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + public abstract class AbstractOpenApiResource extends SpecFilter { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOpenApiResource.class); @@ -128,8 +112,12 @@ protected synchronized OpenAPI getOpenApi() { getPaths(mappingsMap); openApi = openAPIBuilder.getCalculatedOpenAPI(); + // run the optional customisers openApiCustomisers.ifPresent(apiCustomisers -> apiCustomisers.forEach(openApiCustomiser -> openApiCustomiser.customise(openApi))); + + getModelSchemas(openAPIBuilder.getCalculatedOpenAPI().getComponents()); + computeDone = true; if (springDocConfigProperties.isRemoveBrokenReferenceDefinitions()) this.removeBrokenReferenceDefinitions(openApi); @@ -146,6 +134,19 @@ protected synchronized OpenAPI getOpenApi() { return openApi; } + protected void getModelSchemas(Components components) { + if (components != null) { + Map mapSchemas = components.getSchemas(); + if (!CollectionUtils.isEmpty(mapSchemas)) { + Map resolvedSchemas = mapSchemas.entrySet().stream().map(es -> { + es.setValue(openAPIBuilder.resolveProperties(es.getValue())); + return es; + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + components.setSchemas(resolvedSchemas); + } + } + } + protected abstract void getPaths(Map findRestControllers); protected void calculatePath(HandlerMethod handlerMethod, String operationPath, diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIBuilder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIBuilder.java index 0cfc57764..0c36848d3 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIBuilder.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIBuilder.java @@ -18,20 +18,6 @@ package org.springdoc.core; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import io.swagger.v3.core.util.AnnotationsUtils; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.OpenAPIDefinition; @@ -44,13 +30,13 @@ import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springdoc.core.customizers.OpenApiBuilderCustomiser; - import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.context.ApplicationContext; @@ -65,9 +51,13 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.method.HandlerMethod; -import static org.springdoc.core.Constants.DEFAULT_SERVER_DESCRIPTION; -import static org.springdoc.core.Constants.DEFAULT_TITLE; -import static org.springdoc.core.Constants.DEFAULT_VERSION; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.springdoc.core.Constants.*; public class OpenAPIBuilder { @@ -319,6 +309,25 @@ private Info resolveProperties(Info info) { return info; } + public Schema resolveProperties(Schema schema) { + PropertyResolverUtils propertyResolverUtils = context.getBean(PropertyResolverUtils.class); + resolveProperty(schema::getName, schema::name, propertyResolverUtils); + resolveProperty(schema::getTitle, schema::title, propertyResolverUtils); + resolveProperty(schema::getDescription, schema::description, propertyResolverUtils); + + Map properties = schema.getProperties(); + if (!CollectionUtils.isEmpty(properties)) { + Map resolvedSchemas = properties.entrySet().stream().map(es -> { + es.setValue(resolveProperties(es.getValue())); + return es; + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + schema.setProperties(resolvedSchemas); + } + + return schema; + } + + private void resolveProperty(Supplier getProperty, Consumer setProperty, PropertyResolverUtils propertyResolverUtils) { String value = getProperty.get(); diff --git a/springdoc-openapi-data-rest/pom.xml b/springdoc-openapi-data-rest/pom.xml index 8f2126c53..22d669ce0 100644 --- a/springdoc-openapi-data-rest/pom.xml +++ b/springdoc-openapi-data-rest/pom.xml @@ -9,6 +9,10 @@ springdoc-openapi-data-rest + + jakarta.annotation + jakarta.annotation-api + org.springdoc diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/HelloController.java new file mode 100644 index 000000000..8c00cf04c --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/HelloController.java @@ -0,0 +1,22 @@ +package test.org.springdoc.api.app101; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/hello") +public class HelloController { + + @GetMapping + @ApiResponse(content = @Content(schema = @Schema( + description = "${test.app101.operation.hello.response.schema.description}", + implementation = HelloDTO.class))) + public HelloDTO hello() { + return new HelloDTO(); + } + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/HelloDTO.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/HelloDTO.java new file mode 100644 index 000000000..7a991440c --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/HelloDTO.java @@ -0,0 +1,21 @@ +package test.org.springdoc.api.app101; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "${test.app101.schema.hello.description}") +public class HelloDTO { + + @Schema(description = "${test.app101.schema.hello.param.id.description}") + private String id; + + @JsonProperty("id") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/SpringDocApp101Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/SpringDocApp101Test.java new file mode 100644 index 000000000..4f41204b3 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app101/SpringDocApp101Test.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2019-2020 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 test.org.springdoc.api.app101; + + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.ActiveProfiles; +import test.org.springdoc.api.AbstractSpringDocTest; + +@ActiveProfiles("101") +public class SpringDocApp101Test extends AbstractSpringDocTest { + + @SpringBootApplication + static class SpringDocTestApp {} + +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/resources/application-101.yml b/springdoc-openapi-webmvc-core/src/test/resources/application-101.yml new file mode 100644 index 000000000..cb81164a5 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/application-101.yml @@ -0,0 +1,13 @@ +test: + app101: + operation: + hello: + response: + schema: + description: Description of schema of api response hello + schema: + hello: + description: Description of schema of hello entity + param: + id: + description: Description of schema of param id for api hello diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/app101.json b/springdoc-openapi-webmvc-core/src/test/resources/results/app101.json new file mode 100644 index 000000000..3664897b8 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/app101.json @@ -0,0 +1,49 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/hello": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "hello", + "responses": { + "default": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/HelloDTO" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "HelloDTO": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Description of schema of param id for api hello" + } + }, + "description": "Description of schema of hello entity" + } + } + } +} \ No newline at end of file