diff --git a/docs/generators/kotlin-spring.md b/docs/generators/kotlin-spring.md index bf53ef9945e8..a636658d17f4 100644 --- a/docs/generators/kotlin-spring.md +++ b/docs/generators/kotlin-spring.md @@ -71,6 +71,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useSealedResponseInterfaces|Generate sealed interfaces for endpoint responses that all possible response types implement. Allows controllers to return any valid response type in a type-safe manner (e.g., sealed interface CreateUserResponse implemented by User, ConflictResponse, ErrorResponse)| |false| |useSpringBoot3|Generate code and provide dependencies for use with Spring Boot ≥ 3 (use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false| |useSpringBoot4|Generate code and provide dependencies for use with Spring Boot 4.x. Enabling this option will also enable `useJakartaEe`.| |false| +|useSpringBuiltInValidation|Disable `@Validated` at the class level when using built-in validation.| |false| |useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true| |useTags|Whether to use tags for creating interface and controller class names| |false| |xKotlinImplementsFieldsSkip|A list of fields per schema name that should NOT be created with `override` keyword despite their presence in vendor extension `x-kotlin-implements-fields` for the schema. Example: yaml `xKotlinImplementsFieldsSkip: Pet: [photoUrls]` skips `override` for `photoUrls` in schema `Pet`| |empty map| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java index b94c7c42d2bc..619d44868dc4 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java @@ -98,6 +98,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen public static final String USE_SPRING_BOOT3 = "useSpringBoot3"; public static final String USE_SPRING_BOOT4 = "useSpringBoot4"; + public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation"; public static final String INCLUDE_HTTP_REQUEST_CONTEXT = "includeHttpRequestContext"; public static final String USE_FLOW_FOR_ARRAY_RETURN_TYPE = "useFlowForArrayReturnType"; public static final String REQUEST_MAPPING_OPTION = "requestMappingMode"; @@ -190,6 +191,8 @@ public String getDescription() { protected boolean useSpringBoot3 = false; @Getter @Setter protected boolean useSpringBoot4 = false; + @Getter @Setter + protected boolean useSpringBuiltInValidation = false; protected RequestMappingMode requestMappingMode = RequestMappingMode.controller; private DocumentationProvider documentationProvider; private AnnotationLibrary annotationLibrary; @@ -286,6 +289,7 @@ public KotlinSpringServerCodegen() { " (contexts) added to single project.", beanQualifiers); addSwitch(USE_SPRING_BOOT3, "Generate code and provide dependencies for use with Spring Boot ≥ 3 (use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.", useSpringBoot3); addSwitch(USE_SPRING_BOOT4, "Generate code and provide dependencies for use with Spring Boot 4.x. Enabling this option will also enable `useJakartaEe`.", useSpringBoot4); + addSwitch(USE_SPRING_BUILT_IN_VALIDATION, "Disable `@Validated` at the class level when using built-in validation.", useSpringBuiltInValidation); addSwitch(USE_JACKSON_3, "Use Jackson 3 dependencies (tools.jackson package). Only available with `useSpringBoot4`. Defaults to true when `useSpringBoot4` is enabled.", useJackson3); addSwitch(USE_FLOW_FOR_ARRAY_RETURN_TYPE, "Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.", useFlowForArrayReturnType); addSwitch(INCLUDE_HTTP_REQUEST_CONTEXT, "Whether to include HttpServletRequest (blocking) or ServerWebExchange (reactive) as additional parameter in generated methods.", includeHttpRequestContext); @@ -526,6 +530,10 @@ public void processOpts() { if (additionalProperties.containsKey(USE_SPRING_BOOT4)) { this.setUseSpringBoot4(convertPropertyToBoolean(USE_SPRING_BOOT4)); } + if (additionalProperties.containsKey(USE_SPRING_BUILT_IN_VALIDATION)) { + this.setUseSpringBuiltInValidation(convertPropertyToBoolean(USE_SPRING_BUILT_IN_VALIDATION)); + writePropertyBack(USE_SPRING_BUILT_IN_VALIDATION, useSpringBuiltInValidation); + } if (additionalProperties.containsKey(INCLUDE_HTTP_REQUEST_CONTEXT)) { this.setIncludeHttpRequestContext(convertPropertyToBoolean(INCLUDE_HTTP_REQUEST_CONTEXT)); } diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache index dcc950cdeb75..5b9f530282d0 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache @@ -30,7 +30,9 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* {{#useBeanValidation}} +{{^useSpringBuiltInValidation}} import org.springframework.validation.annotation.Validated +{{/useSpringBuiltInValidation}} {{/useBeanValidation}} import org.springframework.web.context.request.NativeWebRequest import org.springframework.beans.factory.annotation.Autowired @@ -55,7 +57,9 @@ import kotlin.collections.Map @RestController{{#beanQualifiers}}("{{package}}.{{classname}}Controller"){{/beanQualifiers}} {{#useBeanValidation}} +{{^useSpringBuiltInValidation}} @Validated +{{/useSpringBuiltInValidation}} {{/useBeanValidation}} {{#swagger1AnnotationLibrary}} @Api(value = "{{{baseName}}}", description = "The {{{baseName}}} API") diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache index 851b07427a6f..82e091bd1aca 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache @@ -35,7 +35,9 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* {{#useBeanValidation}} +{{^useSpringBuiltInValidation}} import org.springframework.validation.annotation.Validated +{{/useSpringBuiltInValidation}} {{/useBeanValidation}} import org.springframework.web.context.request.NativeWebRequest import org.springframework.beans.factory.annotation.Autowired @@ -62,7 +64,9 @@ import kotlin.collections.Map @RestController {{/useFeignClient}} {{#useBeanValidation}} +{{^useSpringBuiltInValidation}} @Validated +{{/useSpringBuiltInValidation}} {{/useBeanValidation}} {{#swagger1AnnotationLibrary}} @Api(value = "{{{baseName}}}", description = "The {{{baseName}}} API") diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java index e66638589e7a..96e82d56e864 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java @@ -105,4 +105,101 @@ public void polymorphicJacksonSerialization() throws IOException { // derived doesn't contain disciminator TestUtils.assertFileNotContains(birdKt, "val discriminator"); } + + @Test(description = "should not generate @Validated annotation on interface when built-in validation option is set to true") + public void shouldEnableBuiltInValidationOptionWhenSetToTrueInterface() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ; + codegen.setOutputDir(output.getAbsolutePath()); + + codegen.setUseSpringBoot3(true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_BEANVALIDATION, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_SPRING_BUILT_IN_VALIDATION, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.INTERFACE_ONLY, true); + + new DefaultGenerator().opts( + new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/3_0/petstore.yaml")) + .config(codegen) + ).generate(); + + final Path userApiKt = Paths.get(output + "/src/main/kotlin/org/openapitools/api/UserApi.kt"); + TestUtils.assertFileNotContains(userApiKt, "import org.springframework.validation.annotation.Validated"); + TestUtils.assertFileNotContains(userApiKt, "@org.springframework.validation.annotation.Validated"); + TestUtils.assertFileNotContains(userApiKt, "@Validated"); + } + + @Test(description = "should not generate @Validated annotation on controller when built-in validation option is set to true") + public void shouldEnableBuiltInValidationOptionWhenSetToTrueController() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ; + codegen.setOutputDir(output.getAbsolutePath()); + + codegen.setUseSpringBoot3(true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_BEANVALIDATION, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_SPRING_BUILT_IN_VALIDATION, true); + + new DefaultGenerator().opts( + new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/3_0/petstore.yaml")) + .config(codegen) + ).generate(); + + final Path userApiKt = Paths.get(output + "/src/main/kotlin/org/openapitools/api/UserApiController.kt"); + TestUtils.assertFileNotContains(userApiKt, "import org.springframework.validation.annotation.Validated"); + TestUtils.assertFileNotContains(userApiKt, "@org.springframework.validation.annotation.Validated"); + TestUtils.assertFileNotContains(userApiKt, "@Validated"); + } + + @Test(description = "should generate @Validated annotation when built-in validation option is set to false") + public void shouldDisableBuiltInValidationOptionWhenSetToFalse() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ; + codegen.setOutputDir(output.getAbsolutePath()); + + codegen.setUseSpringBoot3(true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_BEANVALIDATION, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_SPRING_BUILT_IN_VALIDATION, false); + codegen.additionalProperties().put(KotlinSpringServerCodegen.INTERFACE_ONLY, true); + + new DefaultGenerator().opts( + new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/3_0/petstore.yaml")) + .config(codegen) + ).generate(); + + final Path userApiKt = Paths.get(output + "/src/main/kotlin/org/openapitools/api/UserApi.kt"); + TestUtils.assertFileContains(userApiKt, "import org.springframework.validation.annotation.Validated"); + TestUtils.assertFileContains(userApiKt, "@Validated"); + } + + @Test(description = "should generate @Validated annotation when built-in validation option has default value") + public void shouldDisableBuiltInValidationOptionByDefault() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ; + codegen.setOutputDir(output.getAbsolutePath()); + + codegen.additionalProperties().put(KotlinSpringServerCodegen.INTERFACE_ONLY, true); + + codegen.setUseSpringBoot3(true); + + new DefaultGenerator().opts( + new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/3_0/petstore.yaml")) + .config(codegen) + ).generate(); + + final Path userApiKt = Paths.get(output + "/src/main/kotlin/org/openapitools/api/UserApi.kt"); + TestUtils.assertFileContains(userApiKt, "import org.springframework.validation.annotation.Validated"); + TestUtils.assertFileContains(userApiKt, "@Validated"); + } + }