diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt index 4da27b73..a29de27a 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 0c65a1a7..7360bfb2 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 92f7dfdd..6c64662a 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 328e0959..5b240363 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 = 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 3b8b43f3..792ef96e 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 = 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 a87ce01d..00000000 --- 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() -} diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt index 6e104962..bcd4406d 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 { 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 6e60ca75..07d5581e 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() + .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 6e004dfe..2f32bf85 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 { 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 b2e54800..6a4343d8 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() + .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())