From c09135adeb98ad43bbba6420f2455343ba5df88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Tue, 8 Jul 2025 01:00:11 +0900 Subject: [PATCH] Fix: Property resolution not working for extensions in `@OpenAPIDefinition` --- .../core/service/OpenAPIService.java | 9 +++--- .../core/utils/PropertyResolverUtils.java | 14 ++++++--- .../api/v31/app245/HelloController.java | 30 ++++++++++++++++++ .../api/v31/app245/SpringDocApp245Test.java | 31 +++++++++++++++++++ 4 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app245/HelloController.java create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app245/SpringDocApp245Test.java diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OpenAPIService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OpenAPIService.java index f6397b7a2..4514815d2 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OpenAPIService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OpenAPIService.java @@ -636,9 +636,11 @@ public Class annotationType() { */ private void buildOpenAPIWithOpenAPIDefinition(OpenAPI openAPI, OpenAPIDefinition apiDef, Locale locale) { boolean isOpenapi3 = propertyResolverUtils.isOpenapi31(); - Map extensions = AnnotationsUtils.getExtensions(isOpenapi3, apiDef.info().extensions()); // info - AnnotationsUtils.getInfo(apiDef.info(), true).map(info -> resolveProperties(info, extensions, locale)).ifPresent(openAPI::setInfo); + AnnotationsUtils.getInfo(apiDef.info(), true).map(info -> { + Map extensions = AnnotationsUtils.getExtensions(isOpenapi3, apiDef.info().extensions()); + return resolveProperties(info, extensions, locale); + }).ifPresent(openAPI::setInfo); // OpenApiDefinition security requirements securityParser.getSecurityRequirements(apiDef.security()).ifPresent(openAPI::setSecurity); // OpenApiDefinition external docs @@ -702,7 +704,7 @@ private Info resolveProperties(Info info, Map extensions, Locale resolveProperty(contact::getUrl, contact::url, propertyResolverUtils, locale); } - if (propertyResolverUtils.isResolveExtensionsProperties() && extensions != null) { + if (extensions != null) { Map extensionsResolved = propertyResolverUtils.resolveExtensions(locale, extensions); if (propertyResolverUtils.isOpenapi31()) extensionsResolved.forEach(info::addExtension31); @@ -713,7 +715,6 @@ private Info resolveProperties(Info info, Map extensions, Locale return info; } - /** * Resolve property. * diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/PropertyResolverUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/PropertyResolverUtils.java index 6d2a0b805..d8f3149d7 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/PropertyResolverUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/PropertyResolverUtils.java @@ -224,21 +224,27 @@ public Map resolveExtensions(Locale locale, Map Map extensionsResolved = new HashMap<>(); extensions.forEach((key, value) -> { String keyResolved = resolve(key, locale); - if (value instanceof HashMap) { + if (value instanceof HashMap valueAsMap) { Map valueResolved = new HashMap<>(); - ((HashMap) value).forEach((key1, value1) -> { + valueAsMap.forEach((key1, value1) -> { String key1Resolved = resolve(key1.toString(), locale); String value1Resolved = resolve(value1.toString(), locale); valueResolved.put(key1Resolved, value1Resolved); }); extensionsResolved.put(keyResolved, valueResolved); } - else + else if (value instanceof String valueAsString) { + String valueResolved = resolve(valueAsString, locale); + extensionsResolved.put(keyResolved, valueResolved); + } + else { extensionsResolved.put(keyResolved, value); + } }); return extensionsResolved; } - else + else { return extensions; + } } } diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app245/HelloController.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app245/HelloController.java new file mode 100644 index 000000000..2092d52d3 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app245/HelloController.java @@ -0,0 +1,30 @@ +package test.org.springdoc.api.v31.app245; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; +import io.swagger.v3.oas.annotations.info.Info; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication +@OpenAPIDefinition( + info = @Info( + title = "My App", + version = "${springdoc.version}", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = "x-build-time", value = "${git.build.time}") + }) + } + ) +) +@RestController +public class HelloController { + + @GetMapping("/hello") + public String hello() { + return "hello"; + } +} diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app245/SpringDocApp245Test.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app245/SpringDocApp245Test.java new file mode 100644 index 000000000..21fa4fe3f --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app245/SpringDocApp245Test.java @@ -0,0 +1,31 @@ +package test.org.springdoc.api.v31.app245; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest(properties = { + "springdoc.version=v1", + "git.build.time=2025-07-08T12:00:00Z" +}) +@AutoConfigureMockMvc +class SpringDocApp245Test { + + @Autowired + private MockMvc mockMvc; + + @Test + void SpringDocTestApp() throws Exception { + mockMvc.perform(get("/v3/api-docs")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.info.version", is("v1"))) + .andExpect(jsonPath("$.info.x-build-time", is("2025-07-08T12:00:00Z"))); + } +}