Repository: cjbooms/fabrikt
Pull request #228 (2023-08-21 14:51:17)

Use selected validation library for controllers too
This is a follow up for #179 I guess.

Right now if you pick jakarta as validation library for the http models the controllers still will always be generated with javax validation.


Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
index 4da27b7..a29de27 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
@@ -74,12 +74,14 @@ class CodeGenerator(
                 ControllerCodeGenTargetType.SPRING -> SpringControllerInterfaceGenerator(
                     packages,
                     sourceApi,
+                    MutableSettings.validationLibrary().annotations,
                     MutableSettings.controllerOptions()
                 )
 
                 ControllerCodeGenTargetType.MICRONAUT -> MicronautControllerInterfaceGenerator(
                     packages,
                     sourceApi,
+                    MutableSettings.validationLibrary().annotations,
                     MutableSettings.controllerOptions()
                 )
             }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/JavaxValidationAnnotations.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/JavaxValidationAnnotations.kt
index 0c65a1a..7360bfb 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/JavaxValidationAnnotations.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/JavaxValidationAnnotations.kt
@@ -23,6 +23,10 @@ abstract class ValidationAnnotations(packageName: String) {
         .useSiteTarget(AnnotationSpec.UseSiteTarget.GET)
         .build()
 
+    fun parameterValid() = AnnotationSpec
+        .builder(validClass)
+        .build()
+
     fun min(value: Int) = AnnotationSpec
         .builder(minClass)
         .addMember("%L", value)
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/ControllerInterfaceGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/ControllerInterfaceGenerator.kt
index 92f7dfd..6c64662 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/ControllerInterfaceGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/ControllerInterfaceGenerator.kt
@@ -1,7 +1,7 @@
 package com.cjbooms.fabrikt.generators.controller
 
 import com.cjbooms.fabrikt.configurations.Packages
-import com.cjbooms.fabrikt.generators.controller.metadata.JavaXAnnotations
+import com.cjbooms.fabrikt.generators.ValidationAnnotations
 import com.cjbooms.fabrikt.model.ControllerType
 import com.cjbooms.fabrikt.model.KotlinTypes
 import com.cjbooms.fabrikt.model.RequestParameter
@@ -16,7 +16,8 @@ import com.squareup.kotlinpoet.TypeSpec
 
 abstract class ControllerInterfaceGenerator(
     private val packages: Packages,
-    private val api: SourceApi
+    private val api: SourceApi,
+    private val validationAnnotations: ValidationAnnotations,
 ) {
     abstract fun generate(): KotlinTypes
     abstract fun buildFunction(path: Path, op: Operation, verb: String): FunSpec
@@ -45,9 +46,9 @@ abstract class ControllerInterfaceGenerator(
         )
     }
     fun ParameterSpec.Builder.addValidationAnnotations(parameter: RequestParameter): ParameterSpec.Builder {
-        if (parameter.minimum != null) this.addAnnotation(JavaXAnnotations.min(parameter.minimum.toInt()))
-        if (parameter.maximum != null) this.addAnnotation(JavaXAnnotations.max(parameter.maximum.toInt()))
-        if (parameter.typeInfo.isComplexType) this.addAnnotation(JavaXAnnotations.validBuilder().build())
+        if (parameter.minimum != null) this.addAnnotation(validationAnnotations.min(parameter.minimum.toInt()))
+        if (parameter.maximum != null) this.addAnnotation(validationAnnotations.max(parameter.maximum.toInt()))
+        if (parameter.typeInfo.isComplexType) this.addAnnotation(validationAnnotations.parameterValid())
         return this
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/MicronautControllerInterfaceGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/MicronautControllerInterfaceGenerator.kt
index 328e095..5b24036 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/MicronautControllerInterfaceGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/MicronautControllerInterfaceGenerator.kt
@@ -4,11 +4,11 @@ import com.cjbooms.fabrikt.cli.ControllerCodeGenOptionType
 import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toIncomingParameters
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toKdoc
+import com.cjbooms.fabrikt.generators.ValidationAnnotations
 import com.cjbooms.fabrikt.generators.controller.ControllerGeneratorUtils.SecuritySupport
 import com.cjbooms.fabrikt.generators.controller.ControllerGeneratorUtils.happyPathResponse
 import com.cjbooms.fabrikt.generators.controller.ControllerGeneratorUtils.methodName
 import com.cjbooms.fabrikt.generators.controller.ControllerGeneratorUtils.securitySupport
-import com.cjbooms.fabrikt.generators.controller.metadata.JavaXAnnotations
 import com.cjbooms.fabrikt.generators.controller.metadata.MicronautImports
 import com.cjbooms.fabrikt.generators.controller.metadata.MicronautImports.SECURITY_RULE_IS_ANONYMOUS
 import com.cjbooms.fabrikt.generators.controller.metadata.MicronautImports.SECURITY_RULE_IS_AUTHENTICATED
@@ -35,8 +35,9 @@ import com.squareup.kotlinpoet.TypeSpec
 class MicronautControllerInterfaceGenerator(
     private val packages: Packages,
     private val api: SourceApi,
+    private val validationAnnotations: ValidationAnnotations,
     private val options: Set<ControllerCodeGenOptionType> = emptySet(),
-) : ControllerInterfaceGenerator(packages, api) {
+) : ControllerInterfaceGenerator(packages, api, validationAnnotations) {
 
     private val useSuspendModifier: Boolean
         get() = options.any { it == ControllerCodeGenOptionType.SUSPEND_MODIFIER }
@@ -97,7 +98,7 @@ class MicronautControllerInterfaceGenerator(
                                 AnnotationSpec
                                     .builder(MicronautImports.BODY).build(),
                             )
-                            .addAnnotation(JavaXAnnotations.validBuilder().build())
+                            .addAnnotation(validationAnnotations.parameterValid())
                             .build()
 
                     is RequestParameter ->
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerInterfaceGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerInterfaceGenerator.kt
index 3b8b43f..792ef96 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerInterfaceGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerInterfaceGenerator.kt
@@ -4,10 +4,10 @@ import com.cjbooms.fabrikt.cli.ControllerCodeGenOptionType
 import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toIncomingParameters
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toKdoc
+import com.cjbooms.fabrikt.generators.ValidationAnnotations
 import com.cjbooms.fabrikt.generators.controller.ControllerGeneratorUtils.happyPathResponse
 import com.cjbooms.fabrikt.generators.controller.ControllerGeneratorUtils.methodName
 import com.cjbooms.fabrikt.generators.controller.ControllerGeneratorUtils.securitySupport
-import com.cjbooms.fabrikt.generators.controller.metadata.JavaXAnnotations
 import com.cjbooms.fabrikt.generators.controller.metadata.SpringAnnotations
 import com.cjbooms.fabrikt.generators.controller.metadata.SpringImports
 import com.cjbooms.fabrikt.model.BodyParameter
@@ -34,8 +34,9 @@ import com.squareup.kotlinpoet.TypeSpec
 class SpringControllerInterfaceGenerator(
     private val packages: Packages,
     private val api: SourceApi,
+    private val validationAnnotations: ValidationAnnotations,
     private val options: Set<ControllerCodeGenOptionType> = emptySet(),
-) : ControllerInterfaceGenerator(packages, api) {
+) : ControllerInterfaceGenerator(packages, api, validationAnnotations) {
 
     private val addAuthenticationParameter: Boolean
         get() = options.any { it == ControllerCodeGenOptionType.AUTHENTICATION }
@@ -82,7 +83,7 @@ class SpringControllerInterfaceGenerator(
                         it
                             .toParameterSpecBuilder()
                             .addAnnotation(SpringAnnotations.requestBodyBuilder().build())
-                            .addAnnotation(JavaXAnnotations.validBuilder().build())
+                            .addAnnotation(validationAnnotations.parameterValid())
                             .build()
                     is RequestParameter ->
                         it
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/metadata/Java.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/metadata/Java.kt
deleted file mode 100644
index a87ce01..0000000
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/metadata/Java.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.cjbooms.fabrikt.generators.controller.metadata
-
-import com.squareup.kotlinpoet.AnnotationSpec
-import com.squareup.kotlinpoet.ClassName
-
-object Imports {
-    val MIN =
-        ClassName("javax.validation.constraints", "Min")
-
-    val MAX =
-        ClassName("javax.validation.constraints", "Max")
-
-    val VALID =
-        ClassName("javax.validation", "Valid")
-}
-
-object JavaXAnnotations {
-    fun validBuilder(): AnnotationSpec.Builder =
-        AnnotationSpec
-            .builder(Imports.VALID)
-
-    private fun minBuilder(): AnnotationSpec.Builder =
-        AnnotationSpec
-            .builder(Imports.MIN)
-
-    private fun maxBuilder(): AnnotationSpec.Builder =
-        AnnotationSpec
-            .builder(Imports.MAX)
-
-    fun min(value: Int): AnnotationSpec =
-        minBuilder()
-            .addMember("%L", value)
-            .build()
-
-    fun max(value: Int): AnnotationSpec =
-        maxBuilder()
-            .addMember("%L", value)
-            .build()
-}


Test patch:
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt
index 6e10496..bcd4406 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt
@@ -30,7 +30,12 @@ class MicronautAuthenticationTest {
 
     private fun setupTest(testPath: String): Collection<FileSpec> {
         val api = SourceApi(readTextResource("/authenticationTest/$testPath"))
-        return MicronautControllerInterfaceGenerator(Packages(basePackage), api, setOf(ControllerCodeGenOptionType.AUTHENTICATION)).generate().files
+        return MicronautControllerInterfaceGenerator(
+            Packages(basePackage),
+            api,
+            JavaxValidationAnnotations,
+            setOf(ControllerCodeGenOptionType.AUTHENTICATION)
+        ).generate().files
     }
 
     @BeforeEach
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautControllerGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautControllerGeneratorTest.kt
index 6e60ca7..07d5581 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautControllerGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautControllerGeneratorTest.kt
@@ -3,6 +3,7 @@ package com.cjbooms.fabrikt.generators
 import com.cjbooms.fabrikt.cli.CodeGenerationType
 import com.cjbooms.fabrikt.cli.ControllerCodeGenOptionType
 import com.cjbooms.fabrikt.cli.ControllerCodeGenTargetType
+import com.cjbooms.fabrikt.cli.ValidationLibrary
 import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.controller.MicronautControllerInterfaceGenerator
 import com.cjbooms.fabrikt.generators.controller.MicronautControllers
@@ -20,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
 import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.EnumSource
 import org.junit.jupiter.params.provider.MethodSource
 import java.util.stream.Stream
 
@@ -36,9 +38,9 @@ class MicronautControllerGeneratorTest {
         "parameterNameClash",
     )
 
-    private fun setupGithubApiTestEnv() {
+    private fun setupGithubApiTestEnv(validationAnnotations: ValidationAnnotations = JavaxValidationAnnotations) {
         val api = SourceApi(readTextResource("/examples/githubApi/api.yaml"))
-        generated = MicronautControllerInterfaceGenerator(Packages(basePackage), api).generate().files
+        generated = MicronautControllerInterfaceGenerator(Packages(basePackage), api, validationAnnotations).generate().files
     }
 
     @BeforeEach
@@ -62,6 +64,7 @@ class MicronautControllerGeneratorTest {
         val controllers = MicronautControllerInterfaceGenerator(
             Packages(basePackage),
             api,
+            JavaxValidationAnnotations,
         ).generate().toSingleFile()
 
         assertThat(controllers).isEqualTo(expectedControllers)
@@ -76,6 +79,7 @@ class MicronautControllerGeneratorTest {
         val controllers = MicronautControllerInterfaceGenerator(
             Packages(basePackage),
             api,
+            JavaxValidationAnnotations,
             setOf(ControllerCodeGenOptionType.AUTHENTICATION),
         ).generate().toSingleFile()
 
@@ -121,10 +125,30 @@ class MicronautControllerGeneratorTest {
         )
     }
 
+    @ParameterizedTest
+    @EnumSource(ValidationLibrary::class)
+    fun `ensure controller method parameters have correct validation annotations`(library: ValidationLibrary) {
+        setupGithubApiTestEnv(library.annotations)
+        val desiredPackagePrefix = when (library) {
+            ValidationLibrary.JAVAX_VALIDATION -> "javax.validation."
+            ValidationLibrary.JAKARTA_VALIDATION -> "jakarta.validation."
+        }
+        val parameterValidationAnnotations = generated
+            .flatMap { it.members }
+            .filterIsInstance<TypeSpec>()
+            .flatMap { it.funSpecs }
+            .flatMap { it.parameters }
+            .flatMap { it.annotations }
+            .map { it.className.canonicalName }
+            .filter { ".validation." in it }
+            .distinct()
+        assertThat(parameterValidationAnnotations).allMatch { it.startsWith(desiredPackagePrefix) }
+    }
+
     @Test
     fun `ensure that subresource specific controllers are created`() {
         val api = SourceApi(readTextResource("/examples/githubApi/api.yaml"))
-        val controllers = MicronautControllerInterfaceGenerator(Packages(basePackage), api).generate()
+        val controllers = MicronautControllerInterfaceGenerator(Packages(basePackage), api, JavaxValidationAnnotations).generate()
 
         assertThat(controllers.files).size().isEqualTo(6)
         assertThat(controllers.files.map { it.name }).containsAll(
@@ -165,6 +189,7 @@ class MicronautControllerGeneratorTest {
         val controllers = MicronautControllerInterfaceGenerator(
             Packages(basePackage),
             api,
+            JavaxValidationAnnotations,
             setOf(ControllerCodeGenOptionType.SUSPEND_MODIFIER),
         ).generate()
 
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringAuthenticationTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringAuthenticationTest.kt
index 6e004df..2f32bf8 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringAuthenticationTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringAuthenticationTest.kt
@@ -29,7 +29,12 @@ class SpringAuthenticationTest {
 
     private fun setupTest(testPath: String): Collection<FileSpec> {
         val api = SourceApi(readTextResource("/authenticationTest/$testPath"))
-        return SpringControllerInterfaceGenerator(Packages(basePackage), api, setOf(ControllerCodeGenOptionType.AUTHENTICATION)).generate().files
+        return SpringControllerInterfaceGenerator(
+            Packages(basePackage),
+            api,
+            JavaxValidationAnnotations,
+            setOf(ControllerCodeGenOptionType.AUTHENTICATION)
+        ).generate().files
     }
 
     @BeforeEach
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
index b2e5480..6a4343d 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
@@ -2,6 +2,7 @@ package com.cjbooms.fabrikt.generators
 
 import com.cjbooms.fabrikt.cli.CodeGenerationType
 import com.cjbooms.fabrikt.cli.ControllerCodeGenOptionType
+import com.cjbooms.fabrikt.cli.ValidationLibrary
 import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.controller.SpringControllerInterfaceGenerator
 import com.cjbooms.fabrikt.generators.controller.SpringControllers
@@ -19,6 +20,7 @@ import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
 import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.EnumSource
 import org.junit.jupiter.params.provider.MethodSource
 import java.util.stream.Stream
 
@@ -35,9 +37,9 @@ class SpringControllerGeneratorTest {
         "parameterNameClash",
     )
 
-    private fun setupGithubApiTestEnv() {
+    private fun setupGithubApiTestEnv(annotations: ValidationAnnotations = JavaxValidationAnnotations) {
         val api = SourceApi(readTextResource("/examples/githubApi/api.yaml"))
-        generated = SpringControllerInterfaceGenerator(Packages(basePackage), api).generate().files
+        generated = SpringControllerInterfaceGenerator(Packages(basePackage), api, annotations).generate().files
     }
 
     @BeforeEach
@@ -58,6 +60,7 @@ class SpringControllerGeneratorTest {
         val controllers = SpringControllerInterfaceGenerator(
             Packages(basePackage),
             api,
+            JavaxValidationAnnotations,
         ).generate().toSingleFile()
 
         assertThat(controllers).isEqualTo(expectedControllers)
@@ -72,6 +75,7 @@ class SpringControllerGeneratorTest {
         val controllers = SpringControllerInterfaceGenerator(
             Packages(basePackage),
             api,
+            JavaxValidationAnnotations,
             setOf(ControllerCodeGenOptionType.AUTHENTICATION),
         ).generate().toSingleFile()
 
@@ -119,10 +123,30 @@ class SpringControllerGeneratorTest {
         )
     }
 
+    @ParameterizedTest
+    @EnumSource(ValidationLibrary::class)
+    fun `ensure controller method parameters have correct validation annotations`(library: ValidationLibrary) {
+        setupGithubApiTestEnv(library.annotations)
+        val desiredPackagePrefix = when (library) {
+            ValidationLibrary.JAVAX_VALIDATION -> "javax.validation."
+            ValidationLibrary.JAKARTA_VALIDATION -> "jakarta.validation."
+        }
+        val parameterValidationAnnotations = generated
+            .flatMap { it.members }
+            .filterIsInstance<TypeSpec>()
+            .flatMap { it.funSpecs }
+            .flatMap { it.parameters }
+            .flatMap { it.annotations }
+            .map { it.className.canonicalName }
+            .filter { ".validation." in it }
+            .distinct()
+        assertThat(parameterValidationAnnotations).allMatch { it.startsWith(desiredPackagePrefix) }
+    }
+
     @Test
     fun `ensure that subresource specific controllers are created`() {
         val api = SourceApi(readTextResource("/examples/githubApi/api.yaml"))
-        val controllers = SpringControllerInterfaceGenerator(Packages(basePackage), api).generate()
+        val controllers = SpringControllerInterfaceGenerator(Packages(basePackage), api, JavaxValidationAnnotations).generate()
 
         assertThat(controllers.files).size().isEqualTo(6)
         assertThat(controllers.files.map { it.name }).containsAll(
@@ -163,6 +187,7 @@ class SpringControllerGeneratorTest {
         val controllers = SpringControllerInterfaceGenerator(
             Packages(basePackage),
             api,
+            JavaxValidationAnnotations,
             setOf(ControllerCodeGenOptionType.SUSPEND_MODIFIER),
         ).generate()
 
@@ -194,7 +219,7 @@ class SpringControllerGeneratorTest {
     @Test
     fun `controller parameters should have spring DateTimeFormat annotations`() {
         val api = SourceApi(readTextResource("/examples/springFormatDateAndDateTime/api.yaml"))
-        val controllers = SpringControllerInterfaceGenerator(Packages(basePackage), api).generate().toSingleFile()
+        val controllers = SpringControllerInterfaceGenerator(Packages(basePackage), api, JavaxValidationAnnotations).generate().toSingleFile()
         val expectedControllers = readTextResource("/examples/springFormatDateAndDateTime/controllers/Controllers.kt")
 
         assertThat(controllers.trim()).isEqualTo(expectedControllers.trim())