From 599f102d98344d685a20311494a048dff5dc2ccc Mon Sep 17 00:00:00 2001 From: "magnus.raaum@fremtind.no" Date: Wed, 15 Apr 2026 08:36:41 +0200 Subject: [PATCH] fix: issue 23548 (add support for sealed response interfaces for spring-declarative-http-interface) --- .../httpInterfaceReturnTypes.mustache | 16 ++++----- .../spring/KotlinSpringServerCodegenTest.java | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache index 9ea0fdf05f16..4f36af09bfba 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache @@ -1,26 +1,26 @@ {{! handle reactive map and array}} {{#reactive}} {{#isMap}} -{{#reactiveModeReactor}}Mono<{{/reactiveModeReactor}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}Map{{#useResponseEntity}}>{{/useResponseEntity}}{{#reactiveModeReactor}}>{{/reactiveModeReactor}} +{{#reactiveModeReactor}}Mono<{{/reactiveModeReactor}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}Map{{#useResponseEntity}}>{{/useResponseEntity}}{{#reactiveModeReactor}}>{{/reactiveModeReactor}} {{/isMap}} {{#isArray}} {{! array handle reactive - reactor with/without ResponseEntity wrapper}} {{#reactiveModeReactor}} -{{#useResponseEntity}}Mono{{#useResponseEntity}}>>{{/useResponseEntity}} +{{#useResponseEntity}}Mono{{#useResponseEntity}}>>{{/useResponseEntity}} {{/reactiveModeReactor}} {{! array handle reactive - coroutines with/without ResponseEntity wrapper}} {{#reactiveModeCoroutines}} -{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{{returnContainer}}}<{{{returnType}}}>{{#useResponseEntity}}>{{/useResponseEntity}} +{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{#useResponseEntity}}>{{/useResponseEntity}} {{/reactiveModeCoroutines}} {{/isArray}} {{! handle reactive non-container - with/without ResponseEntity wrapper}} {{^returnContainer}} {{#reactiveModeReactor}} -Mono<{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{{returnType}}}{{#useResponseEntity}}>{{/useResponseEntity}}> +Mono<{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{#useResponseEntity}}>{{/useResponseEntity}}> {{/reactiveModeReactor}} {{#reactiveModeCoroutines}} {{#useResponseEntity}} -ResponseEntity<{{/useResponseEntity}}{{{returnType}}}{{#useResponseEntity}}> +ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{#useResponseEntity}}> {{/useResponseEntity}} {{/reactiveModeCoroutines}} {{/returnContainer}} @@ -29,19 +29,19 @@ ResponseEntity<{{/useResponseEntity}}{{{returnType}}}{{#useResponseEntity}}> {{! handle non-reactive map and array}} {{#isMap}} {{#useResponseEntity}} -ResponseEntity<{{/useResponseEntity}}Map{{#useResponseEntity}}> +ResponseEntity<{{/useResponseEntity}}Map{{#useResponseEntity}}> {{/useResponseEntity}} {{/isMap}} {{#isArray}} {{! array handle non-reactive - with/without ResponseEntity wrapper}} {{#useResponseEntity}} -ResponseEntity<{{/useResponseEntity}}{{{returnContainer}}}<{{{returnType}}}>{{#useResponseEntity}}> +ResponseEntity<{{/useResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{#useResponseEntity}}> {{/useResponseEntity}} {{/isArray}} {{! handle reactive non-container - with/without ResponseEntity wrapper}} {{^returnContainer}} {{#useResponseEntity}} -ResponseEntity<{{/useResponseEntity}}{{{returnType}}}{{#useResponseEntity}}> +ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{#useResponseEntity}}> {{/useResponseEntity}} {{/returnContainer}} {{/reactive}} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java index 504583c759dd..55b876202126 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java @@ -5054,4 +5054,40 @@ public void shouldAddParameterWithInHeaderWhenImplicitHeadersIsTrue() throws IOE Assert.assertTrue(content.contains("testHeader"), "Header name 'testHeader' should appear in the annotation"); } + + @Test + public void testSealedResponseInterfacesWithDeclarativeHttpInterface() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/kotlin/sealed-response-interfaces.yaml", null, new ParseOptions()).getOpenAPI(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "org.openapitools.model"); + codegen.additionalProperties().put(CodegenConstants.API_PACKAGE, "org.openapitools.api"); + codegen.additionalProperties().put(CodegenConstants.LIBRARY, "spring-declarative-http-interface"); + codegen.additionalProperties().put(USE_SEALED_RESPONSE_INTERFACES, "true"); + codegen.additionalProperties().put(USE_RESPONSE_ENTITY, "true"); + codegen.additionalProperties().put(REACTIVE, "false"); + codegen.additionalProperties().put(USE_FLOW_FOR_ARRAY_RETURN_TYPE, "false"); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(input).generate(); + + assertFileContains(Paths.get(outputPath + "/src/main/kotlin/org/openapitools/api/DefaultApi.kt"), + "import org.openapitools.model.CreateUserResponse", + "import org.openapitools.model.GetUserResponse", + "fun createUser(", + "): ResponseEntity", + "fun getUser(", + "): ResponseEntity"); + } + }