Repository: cjbooms/fabrikt
Pull request #146 (2022-09-14 19:30:10)

Expand allOf permutation to validate required properties are inherited
Expand allOf permutation to validate required properties are inherited. Issue #128 suggests there is a problem here


Patch:
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 c254ca6..5e9e9d4 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerInterfaceGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerInterfaceGenerator.kt
@@ -14,6 +14,7 @@ import com.cjbooms.fabrikt.model.BodyParameter
 import com.cjbooms.fabrikt.model.ControllerType
 import com.cjbooms.fabrikt.model.Controllers
 import com.cjbooms.fabrikt.model.HeaderParam
+import com.cjbooms.fabrikt.model.KotlinTypeInfo
 import com.cjbooms.fabrikt.model.PathParam
 import com.cjbooms.fabrikt.model.QueryParam
 import com.cjbooms.fabrikt.model.RequestParameter
@@ -28,6 +29,9 @@ import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.ParameterSpec
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asTypeName
+import java.time.LocalDate
+import java.time.OffsetDateTime
 
 class SpringControllerInterfaceGenerator(
     private val packages: Packages,
@@ -164,6 +168,11 @@ class SpringControllerInterfaceGenerator(
             if (parameter.defaultValue != null)
                 it.addMember("defaultValue = %S", parameter.defaultValue)
 
+            if ( parameter.typeInfo is KotlinTypeInfo.Date )
+                this.addAnnotation( SpringAnnotations.dateTimeFormat( SpringImports.DateTimeFormat.ISO_DATE ) )
+            else if (parameter.typeInfo is KotlinTypeInfo.DateTime )
+                this.addAnnotation( SpringAnnotations.dateTimeFormat( SpringImports.DateTimeFormat.ISO_DATE_TIME ) )
+
             this.addAnnotation(it.build())
         }
 
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/metadata/Spring.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/metadata/Spring.kt
index c9dfe74..ebf6b30 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/metadata/Spring.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/metadata/Spring.kt
@@ -15,6 +15,8 @@ object SpringImports {
         const val WEB_BIND_ANNOTATION = "$SPRING_BASE.web.bind.annotation"
 
         const val VALIDATION_ANNOTATION = "$SPRING_BASE.validation.annotation"
+
+        const val DATE_TIME_FORMAT = "org.springframework.format.annotation"
     }
 
     val CONTROLLER = ClassName(Packages.STEREOTYPE, "Controller")
@@ -33,6 +35,14 @@ object SpringImports {
 
     val VALIDATED = ClassName(Packages.VALIDATION_ANNOTATION, "Validated")
 
+    val DATE_TIME_FORMAT = ClassName(Packages.DATE_TIME_FORMAT, "DateTimeFormat")
+
+    object DateTimeFormat {
+        val ISO_DATE = "${DATE_TIME_FORMAT.simpleName}.ISO.DATE"
+
+        val ISO_DATE_TIME = "${DATE_TIME_FORMAT.simpleName}.ISO.DATE_TIME"
+    }
+
     object Static {
         val RESPONSE_STATUS = Pair(Packages.HTTP, "HttpStatus")
 
@@ -51,6 +61,12 @@ object SpringAnnotations {
             .builder(SpringImports.VALIDATED)
             .build()
 
+    fun dateTimeFormat( iso: String ): AnnotationSpec =
+        AnnotationSpec
+            .builder(SpringImports.DATE_TIME_FORMAT)
+            .addMember( "iso = %L", iso )
+            .build()
+
     fun requestMappingBuilder(): AnnotationSpec.Builder =
         AnnotationSpec
             .builder(SpringImports.REQUEST_MAPPING)


Test patch:
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
index 0e60e72..328702a 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
@@ -175,4 +175,13 @@ class SpringControllerGeneratorTest {
         }
         return Linter.lintString(singleFileBuilder.build().toString())
     }
+
+    @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 expectedControllers = readTextResource("/examples/springFormatDateAndDateTime/controllers/Controllers.kt")
+
+        assertThat(controllers.trim()).isEqualTo(expectedControllers.trim())
+    }
 }
diff --git a/src/test/resources/examples/anyOfOneOfAllOf/api.yaml b/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
index c7ac182..01941ea 100644
--- a/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
+++ b/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
@@ -9,11 +9,25 @@ components:
         primitive_oneof_property:
           $ref: '#/components/schemas/ContainsPrimitiveOneOf'
 
+    SimpleTypeWithRequiredProps:
+      type: object
+      required:
+        - required_int
+        - required_string
+      properties:
+        required_string:
+          type: string
+        required_int:
+          type: integer
+
     ComplexParent:
       allOf:
         - $ref: '#/components/schemas/ContainsNestedOneOf'
         - $ref: '#/components/schemas/ContainsNestedAnyOf'
+        - $ref: '#/components/schemas/SimpleTypeWithRequiredProps'
         - type: object
+          required:
+            - top_level_prop
           properties:
             top_level_prop:
               type: number
@@ -47,6 +61,8 @@ components:
 
     MoreNesting:
       type: object
+      required:
+        - more_nested_prop_one
       properties:
         more_nested_prop_one:
           type: string
diff --git a/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt b/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
index 293d0f2..03dcb51 100644
--- a/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
+++ b/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
@@ -5,7 +5,9 @@ import com.fasterxml.jackson.annotation.JsonAnySetter
 import com.fasterxml.jackson.annotation.JsonIgnore
 import com.fasterxml.jackson.annotation.JsonProperty
 import java.math.BigDecimal
+import javax.validation.constraints.NotNull
 import kotlin.Any
+import kotlin.Int
 import kotlin.String
 import kotlin.collections.Map
 import kotlin.collections.MutableMap
@@ -20,15 +22,25 @@ data class ComplexParent(
     @param:JsonProperty("second_nested_any_of_prop")
     @get:JsonProperty("second_nested_any_of_prop")
     val secondNestedAnyOfProp: String? = null,
+    @param:JsonProperty("required_string")
+    @get:JsonProperty("required_string")
+    @get:NotNull
+    val requiredString: String,
+    @param:JsonProperty("required_int")
+    @get:JsonProperty("required_int")
+    @get:NotNull
+    val requiredInt: Int,
     @param:JsonProperty("top_level_prop")
     @get:JsonProperty("top_level_prop")
-    val topLevelProp: BigDecimal? = null
+    @get:NotNull
+    val topLevelProp: BigDecimal
 )
 
 data class ComplexSecondOneA(
     @param:JsonProperty("more_nested_prop_one")
     @get:JsonProperty("more_nested_prop_one")
-    val moreNestedPropOne: String? = null
+    @get:NotNull
+    val moreNestedPropOne: String
 )
 
 data class ContainsNestedAnyOf(
@@ -61,7 +73,8 @@ data class FirstOneB(
 data class MoreNesting(
     @param:JsonProperty("more_nested_prop_one")
     @get:JsonProperty("more_nested_prop_one")
-    val moreNestedPropOne: String? = null
+    @get:NotNull
+    val moreNestedPropOne: String
 )
 
 data class OneOfAdditionalProps(
@@ -107,3 +120,14 @@ data class SimpleOneOfs(
     @get:JsonProperty("primitive_oneof_property")
     val primitiveOneofProperty: Any? = null
 )
+
+data class SimpleTypeWithRequiredProps(
+    @param:JsonProperty("required_string")
+    @get:JsonProperty("required_string")
+    @get:NotNull
+    val requiredString: String,
+    @param:JsonProperty("required_int")
+    @get:JsonProperty("required_int")
+    @get:NotNull
+    val requiredInt: Int
+)
diff --git a/src/test/resources/examples/springFormatDateAndDateTime/api.yaml b/src/test/resources/examples/springFormatDateAndDateTime/api.yaml
new file mode 100644
index 0000000..d10ab54
--- /dev/null
+++ b/src/test/resources/examples/springFormatDateAndDateTime/api.yaml
@@ -0,0 +1,28 @@
+openapi: 3.0.0
+info:
+  version: '1'
+  title: queryLevelParameters
+paths:
+  /example:
+    get:
+      parameters:
+        - in: query
+          name: aDate
+          required: false
+          schema:
+            type: string
+            format: date
+        - in: query
+          name: bDateTime
+          required: true
+          schema:
+            type: string
+            format: date-time
+        - in: query
+          name: cInt
+          required: true
+          schema:
+            type: integer
+      responses:
+        '204':
+          description: example
diff --git a/src/test/resources/examples/springFormatDateAndDateTime/controllers/Controllers.kt b/src/test/resources/examples/springFormatDateAndDateTime/controllers/Controllers.kt
new file mode 100644
index 0000000..cb7bec7
--- /dev/null
+++ b/src/test/resources/examples/springFormatDateAndDateTime/controllers/Controllers.kt
@@ -0,0 +1,42 @@
+package ie.zalando.controllers
+
+import org.springframework.format.annotation.DateTimeFormat
+import org.springframework.http.ResponseEntity
+import org.springframework.stereotype.Controller
+import org.springframework.validation.annotation.Validated
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestMethod
+import org.springframework.web.bind.annotation.RequestParam
+import java.time.LocalDate
+import java.time.OffsetDateTime
+import kotlin.Int
+import kotlin.Unit
+
+@Controller
+@Validated
+@RequestMapping("")
+interface ExampleController {
+    /**
+     *
+     *
+     * @param bDateTime
+     * @param cInt
+     * @param aDate
+     */
+    @RequestMapping(
+        value = ["/example"],
+        produces = [],
+        method = [RequestMethod.GET]
+    )
+    fun get(
+        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @RequestParam(
+            value = "bDateTime",
+            required =
+            true
+        )
+        bDateTime: OffsetDateTime,
+        @RequestParam(value = "cInt", required = true) cInt: Int,
+        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @RequestParam(value = "aDate", required = false)
+        aDate: LocalDate?
+    ): ResponseEntity<Unit>
+}