Repository: cjbooms/fabrikt (kotlin)

Issue:
Support String format UUID
A string with format type UUID should generate a data class using UUID instead of String.
```
   properties:
        stock_location_id:
          description: >
            The ID of the Stock Location from which this reservation should be fulfilled.
          type: string
          format: uuid
```
Should result in:
`val stockLocationId: UUID`

Pull request:
Closes #56

Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
index e2417055..850a3f0a 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
@@ -10,6 +10,7 @@ import com.reprezen.kaizen.oasparser.model3.Schema
 import java.math.BigDecimal
 import java.time.LocalDate
 import java.time.OffsetDateTime
+import java.util.UUID
 import kotlin.reflect.KClass
 
 sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassName: String? = null) {
@@ -22,6 +23,7 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
     object Numeric : KotlinTypeInfo(BigDecimal::class)
     object Integer : KotlinTypeInfo(Int::class)
     object BigInt : KotlinTypeInfo(Long::class)
+    object Uuid : KotlinTypeInfo(UUID::class)
     object Boolean : KotlinTypeInfo(kotlin.Boolean::class)
     object UntypedObject : KotlinTypeInfo(Any::class)
     data class Object(val simpleClassName: String) : KotlinTypeInfo(GeneratedType::class, simpleClassName)
@@ -47,6 +49,7 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
                 OasType.Text -> Text
                 OasType.Enum ->
                     Enum(schema.getEnumValues(), schema.toModelClassName(enclosingName.toModelClassName()))
+                OasType.Uuid -> Uuid
                 OasType.Double -> Double
                 OasType.Float -> Float
                 OasType.Number -> Numeric
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt
index f313425c..ead95460 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt
@@ -6,6 +6,7 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSchemaLess
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isTypedAdditionalProperties
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isUnknownAdditionalProperties
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isUntypedAdditionalProperties
+import com.cjbooms.fabrikt.util.KaizenParserExtensions.isUuidDefinition
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeType
 import com.reprezen.kaizen.oasparser.model3.Schema
 import kotlin.reflect.KClass
@@ -30,6 +31,7 @@ sealed class OasType(
     object Array : OasType("array")
     object UntypedObject : OasType("object", specialization = Specialization.UNTYPED_OBJECT)
     object Enum : OasType("string", specialization = Specialization.ENUM)
+    object Uuid : OasType("string", specialization = Specialization.UUID)
     object Map : OasType("object", specialization = Specialization.MAP)
     object UnknownProperties : OasType("object", specialization = Specialization.UNKNOWN_ADDITIONAL_PROPERTIES)
     object UntypedProperties : OasType("object", specialization = Specialization.UNTYPED_OBJECT_ADDITIONAL_PROPERTIES)
@@ -51,6 +53,7 @@ sealed class OasType(
 
         private fun Schema.getSpecialization(oasKey: String): Specialization =
             when {
+                isUuidDefinition() -> Specialization.UUID
                 isEnumDefinition() -> Specialization.ENUM
                 isInlineableMapDefinition() -> Specialization.MAP
                 isTypedAdditionalProperties(oasKey) -> Specialization.TYPED_OBJECT_ADDITIONAL_PROPERTIES
@@ -68,6 +71,7 @@ sealed class OasType(
         TYPED_OBJECT_ADDITIONAL_PROPERTIES,
         UNTYPED_OBJECT_ADDITIONAL_PROPERTIES,
         UNTYPED_OBJECT,
+        UUID,
         NONE
     }
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
index bf7af924..23eafcf5 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
@@ -66,6 +66,9 @@ object KaizenParserExtensions {
             )
         )
 
+    fun Schema.isUuidDefinition(): Boolean =
+        this.type == OasType.Text.type && (this.format?.equals("uuid", ignoreCase = true) == true)
+
     @Suppress("UNCHECKED_CAST")
     fun Schema.getEnumValues(): List<String> = when {
         this.hasEnums() -> this.enums.map { it.toString() }


Test patch:
diff --git a/src/test/resources/examples/githubApi/api.yaml b/src/test/resources/examples/githubApi/api.yaml
index 630ef0db..b490ad6b 100644
--- a/src/test/resources/examples/githubApi/api.yaml
+++ b/src/test/resources/examples/githubApi/api.yaml
@@ -716,6 +716,7 @@ components:
       required: false
       schema:
         type: string
+        format: uuid
       example: 84035561-4e44-4a06-98d2-1744eeefca6d
     FlowId:
       name: X-Flow-Id
diff --git a/src/test/resources/examples/githubApi/controllers/Controllers.kt b/src/test/resources/examples/githubApi/controllers/Controllers.kt
index 33eb27d3..8aad7e99 100644
--- a/src/test/resources/examples/githubApi/controllers/Controllers.kt
+++ b/src/test/resources/examples/githubApi/controllers/Controllers.kt
@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestHeader
 import org.springframework.web.bind.annotation.RequestMapping
 import org.springframework.web.bind.annotation.RequestMethod
 import org.springframework.web.bind.annotation.RequestParam
+import java.util.UUID
 import javax.validation.Valid
 import javax.validation.constraints.Max
 import javax.validation.constraints.Min
@@ -105,7 +106,7 @@ interface ContributorsController {
         @RequestBody @Valid
         contributor: Contributor,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 
     /**
@@ -169,7 +170,7 @@ interface ContributorsController {
         @PathVariable(value = "id", required = true) id: String,
         @RequestHeader(value = "If-Match", required = true) ifMatch: String,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 }
 
@@ -230,7 +231,7 @@ interface OrganisationsController {
         @RequestBody @Valid
         organisation: Organisation,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 
     /**
@@ -294,7 +295,7 @@ interface OrganisationsController {
         @PathVariable(value = "id", required = true) id: String,
         @RequestHeader(value = "If-Match", required = true) ifMatch: String,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 }
 
@@ -390,7 +391,7 @@ interface OrganisationsContributorsController {
         @PathVariable(value = "id", required = true) id: String,
         @RequestHeader(value = "If-Match", required = true) ifMatch: String,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 
     /**
@@ -479,7 +480,7 @@ interface RepositoriesController {
         @RequestBody @Valid
         repository: Repository,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 
     /**
@@ -543,7 +544,7 @@ interface RepositoriesController {
         @PathVariable(value = "id", required = true) id: String,
         @RequestHeader(value = "If-Match", required = true) ifMatch: String,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 }
 
@@ -609,7 +610,7 @@ interface RepositoriesPullRequestsController {
         pullRequest: PullRequest,
         @PathVariable(value = "parent-id", required = true) parentId: String,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 
     /**
@@ -672,6 +673,6 @@ interface RepositoriesPullRequestsController {
         @PathVariable(value = "id", required = true) id: String,
         @RequestHeader(value = "If-Match", required = true) ifMatch: String,
         @RequestHeader(value = "X-Flow-Id", required = false) xFlowId: String?,
-        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: String?
+        @RequestHeader(value = "Idempotency-Key", required = false) idempotencyKey: UUID?
     ): ResponseEntity<Unit>
 }
diff --git a/src/test/resources/examples/optionalVsRequired/api.yaml b/src/test/resources/examples/optionalVsRequired/api.yaml
index d27b77ae..884c21d6 100644
--- a/src/test/resources/examples/optionalVsRequired/api.yaml
+++ b/src/test/resources/examples/optionalVsRequired/api.yaml
@@ -9,4 +9,5 @@ components:
         name:
           type: string
         gender:
-          type: string
\ No newline at end of file
+          type: string
+          format: uuid
\ No newline at end of file
diff --git a/src/test/resources/examples/optionalVsRequired/models/Models.kt b/src/test/resources/examples/optionalVsRequired/models/Models.kt
index 863636ee..e046c6c6 100644
--- a/src/test/resources/examples/optionalVsRequired/models/Models.kt
+++ b/src/test/resources/examples/optionalVsRequired/models/Models.kt
@@ -1,6 +1,7 @@
 package examples.optionalVsRequired.models
 
 import com.fasterxml.jackson.annotation.JsonProperty
+import java.util.UUID
 import javax.validation.constraints.NotNull
 import kotlin.String
 
@@ -11,5 +12,5 @@ data class OptionalVsRequired(
     val name: String,
     @param:JsonProperty("gender")
     @get:JsonProperty("gender")
-    val gender: String? = null
+    val gender: UUID? = null
 )