Repository: cjbooms/fabrikt
Pull request #178 (2023-03-17 20:40:48)

Allow builders to configure the kotlin types generated 
Adds a configuration parameter to allow builders to override the types generated by fabrikt. 
| | |
| --- | --- |
|   `--type-overrides`         | Specify non-default kotlin types for certain OAS types. For example, generate `Instant` instead of `OffsetDateTime` |
|                              | CHOOSE ANY OF: |
|                              |   `DATETIME_AS_INSTANT` - Use `Instant` as the datetime type. Defaults to `OffsetDateTime` |
|                              |   `DATETIME_AS_LOCALDATETIME` - Use `LocalDateTime` as the datetime type. Defaults to `OffsetDateTime` |



Patch:
diff --git a/README.md b/README.md
index b61672c..5f4b914 100644
--- a/README.md
+++ b/README.md
@@ -123,38 +123,42 @@ data class ConcreteImplTwo(
 
 This section documents the available CLI parameters for controlling what gets generated. This documentation is generated using: `./gradlew printCodeGenUsage`
 
-| Parameter                    | Description
-| ---------------------------- | ----------------------------
-|   `--api-file`               | This must be a valid Open API v3 spec. All code generation will be based off this input.
-|   `--api-fragment`           | A partial Open API v3 fragment, to be combined with the primary API for code generation purposes.
-| * `--base-package`           | The base package which all code will be generated under.
-|   `--http-client-opts`       | Select the options for the http client code that you want to be generated.
-|                              | CHOOSE ANY OF:
-|                              |   `RESILIENCE4J` - Generates a fault tolerance service for the client using the following library "io.github.resilience4j:resilience4j-all:+"
-|   `--http-controller-opts`   | Select the options for the controllers that you want to be generated.
-|                              | CHOOSE ANY OF:
-|                              |   `SUSPEND_MODIFIER` - This option adds the suspend modifier to the generated controller functions
-|   `--http-controller-target` | Optionally select the target framework for the controllers that you want to be generated. Defaults to Spring Controllers
-|                              | CHOOSE ONE OF:
-|                              |   `SPRING` - Generate for Spring framework.
-|                              |   `MICRONAUT` - Generate for Micronaut framework.
-|   `--http-model-opts`        | Select the options for the http models that you want to be generated.
-|                              | CHOOSE ANY OF:
-|                              |   `X_EXTENSIBLE_ENUMS` - This option treats x-extensible-enums as enums
-|                              |   `JAVA_SERIALIZATION` - This option adds Java Serializable interface to the generated models
-|                              |   `QUARKUS_REFLECTION` - This option adds @RegisterForReflection to the generated models. Requires dependency "'io.quarkus:quarkus-core:+"
-|                              |   `MICRONAUT_INTROSPECTION` - This option adds @Introspected to the generated models. Requires dependency "'io.micronaut:micronaut-core:+"
-|                              |   `MICRONAUT_REFLECTION` - This option adds @ReflectiveAccess to the generated models. Requires dependency "'io.micronaut:micronaut-core:+"
-|                              |   `INCLUDE_COMPANION_OBJECT` - This option adds a companion object to the generated models.
-|   `--output-directory`       | Allows the generation dir to be overridden. Defaults to current dir
-|   `--resources-path`         | Allows the path for generated resources to be overridden. Defaults to `src/main/resources`
-|   `--src-path`               | Allows the path for generated source files to be overridden. Defaults to `src/main/kotlin`
-|   `--targets`                | Targets are the parts of the application that you want to be generated.
-|                              | CHOOSE ANY OF:
-|                              |   `HTTP_MODELS` - Jackson annotated data classes to represent the schema objects defined in the input.
-|                              |   `CONTROLLERS` - Spring / Micronaut annotated HTTP controllers for each of the endpoints defined in the input.
-|                              |   `CLIENT` - Simple http rest client.
-|                              |   `QUARKUS_REFLECTION_CONFIG` - This options generates the reflection-config.json file for quarkus integration projects
+| Parameter                    | Description |
+| ---------------------------- | ---------------------------- |
+|   `--api-file`               | This must be a valid Open API v3 spec. All code generation will be based off this input. |
+|   `--api-fragment`           | A partial Open API v3 fragment, to be combined with the primary API for code generation purposes. |
+| * `--base-package`           | The base package which all code will be generated under. |
+|   `--http-client-opts`       | Select the options for the http client code that you want to be generated. |
+|                              | CHOOSE ANY OF: |
+|                              |   `RESILIENCE4J` - Generates a fault tolerance service for the client using the following library "io.github.resilience4j:resilience4j-all:+" |
+|   `--http-controller-opts`   | Select the options for the controllers that you want to be generated. |
+|                              | CHOOSE ANY OF: |
+|                              |   `SUSPEND_MODIFIER` - This option adds the suspend modifier to the generated controller functions |
+|   `--http-controller-target` | Optionally select the target framework for the controllers that you want to be generated. Defaults to Spring Controllers |
+|                              | CHOOSE ONE OF: |
+|                              |   `SPRING` - Generate for Spring framework. |
+|                              |   `MICRONAUT` - Generate for Micronaut framework. |
+|   `--http-model-opts`        | Select the options for the http models that you want to be generated. |
+|                              | CHOOSE ANY OF: |
+|                              |   `X_EXTENSIBLE_ENUMS` - This option treats x-extensible-enums as enums |
+|                              |   `JAVA_SERIALIZATION` - This option adds Java Serializable interface to the generated models |
+|                              |   `QUARKUS_REFLECTION` - This option adds @RegisterForReflection to the generated models. Requires dependency "'io.quarkus:quarkus-core:+" |
+|                              |   `MICRONAUT_INTROSPECTION` - This option adds @Introspected to the generated models. Requires dependency "'io.micronaut:micronaut-core:+" |
+|                              |   `MICRONAUT_REFLECTION` - This option adds @ReflectiveAccess to the generated models. Requires dependency "'io.micronaut:micronaut-core:+" |
+|                              |   `INCLUDE_COMPANION_OBJECT` - This option adds a companion object to the generated models. |
+|   `--output-directory`       | Allows the generation dir to be overridden. Defaults to current dir |
+|   `--resources-path`         | Allows the path for generated resources to be overridden. Defaults to `src/main/resources` |
+|   `--src-path`               | Allows the path for generated source files to be overridden. Defaults to `src/main/kotlin` |
+|   `--targets`                | Targets are the parts of the application that you want to be generated. |
+|                              | CHOOSE ANY OF: |
+|                              |   `HTTP_MODELS` - Jackson annotated data classes to represent the schema objects defined in the input. |
+|                              |   `CONTROLLERS` - Spring / Micronaut annotated HTTP controllers for each of the endpoints defined in the input. |
+|                              |   `CLIENT` - Simple http rest client. |
+|                              |   `QUARKUS_REFLECTION_CONFIG` - This options generates the reflection-config.json file for quarkus integration projects |
+|   `--type-overrides`         | Specify non-default kotlin types for certain OAS types. For example, generate `Instant` instead of `OffsetDateTime` |
+|                              | CHOOSE ANY OF: |
+|                              |   `DATETIME_AS_INSTANT` - Use `Instant` as the datetime type. Defaults to `OffsetDateTime` |
+|                              |   `DATETIME_AS_LOCALDATETIME` - Use `LocalDateTime` as the datetime type. Defaults to `OffsetDateTime` |
 
 ### Command Line
 Fabrikt is packaged as an executable jar, allowing it to be integrated into any build tool. The CLI can be invoked as follows:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
index 4f85dcf..cdd50a7 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
@@ -29,6 +29,7 @@ object CodeGen {
             codeGenArgs.controllerTarget,
             codeGenArgs.modelOptions,
             codeGenArgs.clientOptions,
+            codeGenArgs.typeOverrides,
             codeGenArgs.srcPath,
             codeGenArgs.resourcesPath
         )
@@ -44,10 +45,11 @@ object CodeGen {
         controllerTarget: ControllerCodeGenTargetType,
         modelOptions: Set<ModelCodeGenOptionType>,
         clientOptions: Set<ClientCodeGenOptionType>,
+        typeOverrides: Set<CodeGenTypeOverride>,
         srcPath: Path,
         resourcesPath: Path,
     ) {
-        MutableSettings.updateSettings(codeGenTypes, controllerOptions, controllerTarget, modelOptions, clientOptions)
+        MutableSettings.updateSettings(codeGenTypes, controllerOptions, controllerTarget, modelOptions, clientOptions, typeOverrides)
 
         val suppliedApi = pathToApi.toFile().readText()
         val baseDir = pathToApi.parent
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
index 43bcb6f..281adab 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
@@ -119,6 +119,13 @@ class CodeGenArgs {
         converter = PathConverter::class
     )
     var resourcesPath: Path = Destinations.MAIN_RESOURCES
+
+    @Parameter(
+        names = ["--type-overrides"],
+        description = "Specify non-default kotlin types for certain OAS types. For example, generate `Instant` instead of `OffsetDateTime`",
+        converter = TypeCodeGenOptionsConverter::class
+    )
+    var typeOverrides: Set<CodeGenTypeOverride> = emptySet()
 }
 
 class CodeGenerationTypesConverter : IStringConverter<CodeGenerationType> {
@@ -143,6 +150,10 @@ class ClientCodeGenOptionConverter : IStringConverter<ClientCodeGenOptionType> {
         convertToEnumValue(value)
 }
 
+class TypeCodeGenOptionsConverter: IStringConverter<CodeGenTypeOverride> {
+    override fun convert(value: String): CodeGenTypeOverride = convertToEnumValue(value)
+}
+
 class PackageNameValidator : IValueValidator<String> {
     override fun validate(name: String, value: String) {
         if (!value.isValidJavaPackage()) {
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
index e09b0b2..3f93d3d 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
@@ -44,5 +44,11 @@ enum class ControllerCodeGenTargetType(val description: String) {
     SPRING("Generate for Spring framework."),
     MICRONAUT("Generate for Micronaut framework.");
 
+    override fun toString() = "`${super.toString()}` - $description"
+}
+
+enum class CodeGenTypeOverride(val description: String) {
+    DATETIME_AS_INSTANT("Use `Instant` as the datetime type. Defaults to `OffsetDateTime`"),
+    DATETIME_AS_LOCALDATETIME("Use `LocalDateTime` as the datetime type. Defaults to `OffsetDateTime`");
     override fun toString() = "`${super.toString()}` - $description"
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/MarkdownUsageFormatter.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/MarkdownUsageFormatter.kt
index e704223..d7753f0 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/MarkdownUsageFormatter.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/MarkdownUsageFormatter.kt
@@ -14,7 +14,9 @@ class MarkdownUsageFormatter(commander: JCommander) : DefaultUsageFormatter(comm
         const val SECOND_COL_HEADER = "Description"
         const val MULTI_VALUE_HEADER = "CHOOSE ANY OF:"
         const val SINGLE_VALUE_HEADER = "CHOOSE ONE OF:"
+        const val START = "| "
         const val BORDER = " | "
+        const val END = " |"
         const val SEPARATOR = "-"
         const val NEW_LINE = "\n"
     }
@@ -29,30 +31,30 @@ class MarkdownUsageFormatter(commander: JCommander) : DefaultUsageFormatter(comm
 
         val firstColumnWidth = getFirstColumnWidth(sortedParameters)
 
-        out.append(BORDER).append(FIRST_COL_HEADER).append(" ".repeat(firstColumnWidth - FIRST_COL_HEADER.length))
-            .append(BORDER).append(SECOND_COL_HEADER).append(NEW_LINE)
+        out.append(START).append(FIRST_COL_HEADER).append(" ".repeat(firstColumnWidth - FIRST_COL_HEADER.length))
+            .append(BORDER).append(SECOND_COL_HEADER).append(END).append(NEW_LINE)
 
         val titleSeparator = SEPARATOR.repeat(firstColumnWidth)
-        out.append(BORDER).append(titleSeparator).append(" ".repeat(firstColumnWidth - titleSeparator.length))
-            .append(BORDER).append(titleSeparator).append(NEW_LINE)
+        out.append(START).append(titleSeparator).append(" ".repeat(firstColumnWidth - titleSeparator.length))
+            .append(BORDER).append(titleSeparator).append(END).append(NEW_LINE)
 
         sortedParameters
             .filter { !it.isHelp }
             .forEach { param ->
 
                 val parameterKey = param.getParameterKey()
-                out.append(BORDER).append(parameterKey).append(" ".repeat(firstColumnWidth - parameterKey.length))
-                    .append(BORDER).append(param.description).append(NEW_LINE)
+                out.append(START).append(parameterKey).append(" ".repeat(firstColumnWidth - parameterKey.length))
+                    .append(BORDER).append(param.description).append(END).append(NEW_LINE)
 
                 val options = getEnumValues(param.parameterized)
                 if (options.isNotEmpty()) {
                     val valueHeader = if (param.parameterized.isSingleEnum()) SINGLE_VALUE_HEADER else MULTI_VALUE_HEADER
-                    out.append(BORDER).append(" ".repeat(firstColumnWidth))
-                        .append(BORDER).append(valueHeader).append(NEW_LINE)
+                    out.append(START).append(" ".repeat(firstColumnWidth))
+                        .append(BORDER).append(valueHeader).append(END).append(NEW_LINE)
 
                     options.forEach { option ->
-                        out.append(BORDER).append(" ".repeat(firstColumnWidth))
-                            .append(BORDER).append("  $option").append(NEW_LINE)
+                        out.append(START).append(" ".repeat(firstColumnWidth))
+                            .append(BORDER).append("  $option").append(END).append(NEW_LINE)
                     }
                 }
             }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
index c5b1d79..bddc213 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
@@ -1,10 +1,11 @@
 package com.cjbooms.fabrikt.generators
 
-import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType
 import com.cjbooms.fabrikt.cli.CodeGenerationType
 import com.cjbooms.fabrikt.cli.ControllerCodeGenOptionType
 import com.cjbooms.fabrikt.cli.ControllerCodeGenTargetType
 import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
+import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType
+import com.cjbooms.fabrikt.cli.CodeGenTypeOverride
 
 object MutableSettings {
     private lateinit var generationTypes: MutableSet<CodeGenerationType>
@@ -12,26 +13,31 @@ object MutableSettings {
     private lateinit var controllerTarget: ControllerCodeGenTargetType
     private lateinit var modelOptions: MutableSet<ModelCodeGenOptionType>
     private lateinit var clientOptions: MutableSet<ClientCodeGenOptionType>
+    private lateinit var typeOverrides: MutableSet<CodeGenTypeOverride>
 
     fun updateSettings(
         genTypes: Set<CodeGenerationType> = emptySet(),
         controllerOptions: Set<ControllerCodeGenOptionType> = emptySet(),
         controllerTarget: ControllerCodeGenTargetType = ControllerCodeGenTargetType.SPRING,
         modelOptions: Set<ModelCodeGenOptionType> = emptySet(),
-        clientOptions: Set<ClientCodeGenOptionType> = emptySet()
+        clientOptions: Set<ClientCodeGenOptionType> = emptySet(),
+        typeOverrides: Set<CodeGenTypeOverride> = emptySet()
     ) {
         this.generationTypes = genTypes.toMutableSet()
         this.controllerOptions = controllerOptions.toMutableSet()
         this.controllerTarget = controllerTarget
         this.modelOptions = modelOptions.toMutableSet()
         this.clientOptions = clientOptions.toMutableSet()
+        this.typeOverrides = typeOverrides.toMutableSet()
     }
 
     fun addOption(option: ModelCodeGenOptionType) = modelOptions.add(option)
+    fun addOption(override: CodeGenTypeOverride) = typeOverrides.add(override)
 
     fun generationTypes() = this.generationTypes.toSet()
     fun controllerOptions() = this.controllerOptions.toSet()
     fun controllerTarget() = this.controllerTarget
     fun modelOptions() = this.modelOptions.toSet()
     fun clientOptions() = this.clientOptions.toSet()
+    fun typeOverrides() = this.typeOverrides.toSet()
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
index 44c2e73..95128aa 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
@@ -1,5 +1,7 @@
 package com.cjbooms.fabrikt.model
 
+import com.cjbooms.fabrikt.cli.CodeGenTypeOverride
+import com.cjbooms.fabrikt.generators.MutableSettings
 import com.cjbooms.fabrikt.model.OasType.Companion.toOasType
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.getEnumValues
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlinedTypedAdditionalProperties
@@ -20,6 +22,8 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
     object Text : KotlinTypeInfo(String::class)
     object Date : KotlinTypeInfo(LocalDate::class)
     object DateTime : KotlinTypeInfo(OffsetDateTime::class)
+    object Instant: KotlinTypeInfo(java.time.Instant::class)
+    object LocalDateTime: KotlinTypeInfo(java.time.LocalDateTime::class)
     object Double : KotlinTypeInfo(kotlin.Double::class)
     object Float : KotlinTypeInfo(kotlin.Float::class)
     object Numeric : KotlinTypeInfo(BigDecimal::class)
@@ -55,7 +59,7 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
         fun from(schema: Schema, oasKey: String = "", enclosingName: String = ""): KotlinTypeInfo =
             when (schema.toOasType(oasKey)) {
                 OasType.Date -> Date
-                OasType.DateTime -> DateTime
+                OasType.DateTime -> getOverridableDateTimeType()
                 OasType.Text -> Text
                 OasType.Enum ->
                     Enum(schema.getEnumValues(), schema.toModelClassName(enclosingName.toModelClassName()))
@@ -90,5 +94,14 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
                 OasType.Any -> AnyType
                 OasType.OneOfAny -> AnyType
             }
+
+        private fun getOverridableDateTimeType(): KotlinTypeInfo {
+            val typeOverrides = MutableSettings.typeOverrides()
+            return when {
+                CodeGenTypeOverride.DATETIME_AS_INSTANT in typeOverrides -> Instant
+                CodeGenTypeOverride.DATETIME_AS_LOCALDATETIME in typeOverrides -> LocalDateTime
+                else -> DateTime
+            }
+        }
     }
 }


Test patch:
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
index 9061fce..1f59da3 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
@@ -3,6 +3,7 @@ package com.cjbooms.fabrikt.generators
 import com.beust.jcommander.ParameterException
 import com.cjbooms.fabrikt.cli.CodeGenerationType
 import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
+import com.cjbooms.fabrikt.cli.CodeGenTypeOverride
 import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.model.JacksonModelGenerator
 import com.cjbooms.fabrikt.model.Models
@@ -46,6 +47,7 @@ class ModelGeneratorTest {
         "singleAllOf",
         "responsesSchema",
         "webhook",
+        "instantDateTime"
     )
 
     @BeforeEach
@@ -63,6 +65,9 @@ class ModelGeneratorTest {
     fun `correct models are generated for different OpenApi Specifications`(testCaseName: String) {
         print("Testcase: $testCaseName")
         MutableSettings.addOption(ModelCodeGenOptionType.X_EXTENSIBLE_ENUMS)
+        if (testCaseName == "instantDateTime") {
+            MutableSettings.addOption(CodeGenTypeOverride.DATETIME_AS_INSTANT)
+        }
         val basePackage = "examples.$testCaseName"
         val apiLocation = javaClass.getResource("/examples/$testCaseName/api.yaml")!!
         val sourceApi = SourceApi(apiLocation.readText(), baseDir = Paths.get(apiLocation.toURI()))
diff --git a/src/test/resources/examples/instantDateTime/api.yaml b/src/test/resources/examples/instantDateTime/api.yaml
new file mode 100644
index 0000000..5b7110a
--- /dev/null
+++ b/src/test/resources/examples/instantDateTime/api.yaml
@@ -0,0 +1,99 @@
+openapi: "3.0.0"
+info:
+  title: "API Example"
+  version: "1.0"
+paths:
+  /example-path-1:
+    get:
+      summary: "GET example path 1"
+      parameters:
+        - $ref: "#/components/parameters/ListQueryParamExploded"
+        - $ref: "#/components/parameters/QueryParam"
+      responses:
+        200:
+          description: "successful operation"
+          headers:
+            Cache-Control:
+              $ref: "#/components/headers/CacheControl"
+          content:
+            application/vnd.custom.media+json:
+              schema:
+                $ref: "#/components/schemas/QueryResult"
+
+components:
+  parameters:
+    ListQueryParamExploded:
+      name: "explode_list_query_param"
+      in: "query"
+      schema:
+        type: "array"
+        items:
+          type: "string"
+          format: "date-time"
+          example: "2016-01-27T10:52:46.406Z"
+      required: false
+      allowEmptyValue: false
+
+
+    QueryParam:
+      name: "query_param2"
+      in: "query"
+      schema:
+        type: "string"
+        format: "date-time"
+        example: "2016-01-27T10:52:46.406Z"
+      required: false
+
+
+  headers:
+    Location:
+      description: "The Location header indicates the URL of a newly created resource"
+      schema:
+        type: "string"
+
+    CacheControl:
+      description: "The RFC7234 Cache Control header"
+      schema:
+        type: "string"
+      example: "must-revalidate, max-age=5"
+
+  requestBodies:
+    PostBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            $ref: "#/components/schemas/FirstModel"
+    PutBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            $ref: "#/components/schemas/FirstModel"
+
+  schemas:
+    QueryResult:
+      type: "object"
+      required:
+        - "items"
+      properties:
+        items:
+          type: "array"
+          minItems: 0
+          items:
+            $ref: "#/components/schemas/FirstModel"
+    FirstModel:
+      type: "object"
+      properties:
+        date:
+          type: "string"
+          format: "date-time"
+          example: "2016-01-27T10:52:46.406Z"
+        extra_first_attr:
+          description: "The attribute 1 for model 1"
+          type: array
+          items:
+            type: "string"
+            format: "date-time"
+            example: "2016-01-27T10:52:46.406Z"
+          readOnly: true
diff --git a/src/test/resources/examples/instantDateTime/models/Models.kt b/src/test/resources/examples/instantDateTime/models/Models.kt
new file mode 100644
index 0000000..1e6084c
--- /dev/null
+++ b/src/test/resources/examples/instantDateTime/models/Models.kt
@@ -0,0 +1,26 @@
+package examples.instantDateTime.models
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import java.time.Instant
+import javax.validation.Valid
+import javax.validation.constraints.NotNull
+import javax.validation.constraints.Size
+import kotlin.collections.List
+
+data class FirstModel(
+    @param:JsonProperty("date")
+    @get:JsonProperty("date")
+    val date: Instant? = null,
+    @param:JsonProperty("extra_first_attr")
+    @get:JsonProperty("extra_first_attr")
+    val extraFirstAttr: List<Instant>? = null
+)
+
+data class QueryResult(
+    @param:JsonProperty("items")
+    @get:JsonProperty("items")
+    @get:NotNull
+    @get:Size(min = 0)
+    @get:Valid
+    val items: List<FirstModel>
+)