Repository: cjbooms/fabrikt
Pull request #39 (2021-04-30 11:11:13)

Issue #34: Optional properties with a default should not be nullable
Issue #34: Optional properties with a default should not be nullable

cjbooms: 👍 

Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/GeneratorUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/GeneratorUtils.kt
index aa078ef..42d5b77 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/GeneratorUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/GeneratorUtils.kt
@@ -25,9 +25,9 @@ object GeneratorUtils {
     fun RequestBody.toBodyParameterSpec(basePackage: String): List<ParameterSpec> =
         this.toBodyRequestSchema().map {
             val modelType = toModelType(
-                basePackage,
-                KotlinTypeInfo.from(it),
-                this.isRequired
+                basePackage = basePackage,
+                typeInfo = KotlinTypeInfo.from(it),
+                isNullable = !this.isRequired
             )
             ParameterSpec.builder(
                 it.toVarName(),
@@ -41,7 +41,11 @@ object GeneratorUtils {
     fun Parameter.toParameterSpec(basePackage: String): ParameterSpec =
         ParameterSpec.builder(
             this.name.toKCodeName(),
-            toModelType(basePackage, KotlinTypeInfo.from(this.schema), this.isRequired)
+            toModelType(
+                basePackage = basePackage,
+                typeInfo = KotlinTypeInfo.from(this.schema),
+                isNullable = !this.isRequired && this.schema.default == null
+            )
         ).build()
 
     /**
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
index 8419c54..0750fc5 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
@@ -122,6 +122,11 @@ object PropertyUtils {
         }
     }
 
+    fun PropertyInfo.isNullable() = when (this) {
+        is PropertyInfo.Field -> !isRequired && schema.default == null
+        else -> !isRequired
+    }
+
     /**
      * Current Open API v3 Spec validation keys:
      *
@@ -141,7 +146,7 @@ object PropertyUtils {
      *   enum                   - Not currently supported. Possible to do as a regex maybe.
      */
     private fun PropertySpec.Builder.addValidationAnnotations(info: PropertyInfo) {
-        if (info.isRequired) addAnnotation(ValidationAnnotations.NON_NULL_ANNOTATION)
+        if (!info.isNullable()) addAnnotation(ValidationAnnotations.NON_NULL_ANNOTATION)
         when (info) {
             is PropertyInfo.Field -> {
                 // Regex validation pattern to validate string input
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/ControllerGeneratorUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/ControllerGeneratorUtils.kt
index ae1e59f..80492ed 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/ControllerGeneratorUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/ControllerGeneratorUtils.kt
@@ -66,7 +66,7 @@ object ControllerGeneratorUtils {
             .map {
                 RequestParameter(
                     it.name,
-                    toModelType(basePackage, KotlinTypeInfo.from(it.schema), isRequired(it)),
+                    toModelType(basePackage, KotlinTypeInfo.from(it.schema), isNullable(it)),
                     it
                 )
             }
@@ -75,7 +75,7 @@ object ControllerGeneratorUtils {
         return bodies + parameters
     }
 
-    private fun isRequired(parameter: Parameter): Boolean = parameter.isRequired || parameter.schema.default != null
+    private fun isNullable(parameter: Parameter): Boolean = !parameter.isRequired && parameter.schema.default == null
 
     fun controllerName(resourceName: String) = "$resourceName${ControllerType.SUFFIX}"
 
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
index f602fb4..cd37aff 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
@@ -5,6 +5,7 @@ import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.ClassType
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toClassName
 import com.cjbooms.fabrikt.generators.PropertyUtils.addToClass
+import com.cjbooms.fabrikt.generators.PropertyUtils.isNullable
 import com.cjbooms.fabrikt.generators.TypeFactory.createList
 import com.cjbooms.fabrikt.generators.TypeFactory.createMapOfMapsStringToStringAny
 import com.cjbooms.fabrikt.generators.TypeFactory.createMapOfStringToType
@@ -50,7 +51,7 @@ class JacksonModelGenerator(
     private val options: Set<ModelCodeGenOptionType> = emptySet()
 ) {
     companion object {
-        fun toModelType(basePackage: String, typeInfo: KotlinTypeInfo, isRequired: Boolean = true): TypeName {
+        fun toModelType(basePackage: String, typeInfo: KotlinTypeInfo, isNullable: Boolean = false): TypeName {
             val className =
                 toClassName(
                     basePackage,
@@ -93,7 +94,7 @@ class JacksonModelGenerator(
                 is KotlinTypeInfo.TypedProperties -> createMutableMapOfStringToType(className)
                 else -> className
             }
-            return if (isRequired) typeName else typeName.copy(nullable = true)
+            return if (isNullable) typeName.copy(nullable = true) else typeName
         }
 
         private fun toClassName(basePackage: String, typeInfo: KotlinTypeInfo): ClassName =
@@ -315,7 +316,7 @@ class JacksonModelGenerator(
                 toModelType(
                     packages.base,
                     it.typeInfo,
-                    it.isRequired
+                    it.isNullable()
                 ),
                 toClassName(
                     packages.base,


Test patch:
diff --git a/src/test/resources/examples/defaultValues/api.yaml b/src/test/resources/examples/defaultValues/api.yaml
index 7e50008..db6a85b 100644
--- a/src/test/resources/examples/defaultValues/api.yaml
+++ b/src/test/resources/examples/defaultValues/api.yaml
@@ -4,22 +4,23 @@ components:
     PersonWithDefaults:
       type: object
       required:
-        - name
+        - required_so_default_ignored
       properties:
-        name:
+        required_so_default_ignored:
           type: string
-        age:
+          default: "Some ignored default"
+        integer_default:
           type: integer
           default: 18
-        height_category:
+        enum_default:
           type: string
           enum:
             - tall
             - short
           default: tall
-        has_hair:
+        boolean_default:
           type: boolean
           default: true
-        catch_phrase:
+        string_phrase:
           type: string
           default: Cowabunga Dude
diff --git a/src/test/resources/examples/defaultValues/models/Models.kt b/src/test/resources/examples/defaultValues/models/Models.kt
index d2d31ac..fbe9885 100644
--- a/src/test/resources/examples/defaultValues/models/Models.kt
+++ b/src/test/resources/examples/defaultValues/models/Models.kt
@@ -8,25 +8,29 @@ import kotlin.Int
 import kotlin.String
 
 data class PersonWithDefaults(
-    @param:JsonProperty("name")
-    @get:JsonProperty("name")
+    @param:JsonProperty("required_so_default_ignored")
+    @get:JsonProperty("required_so_default_ignored")
     @get:NotNull
-    val name: String,
-    @param:JsonProperty("age")
-    @get:JsonProperty("age")
-    val age: Int? = 18,
-    @param:JsonProperty("height_category")
-    @get:JsonProperty("height_category")
-    val heightCategory: HeightCategory? = HeightCategory.TALL,
-    @param:JsonProperty("has_hair")
-    @get:JsonProperty("has_hair")
-    val hasHair: Boolean? = true,
-    @param:JsonProperty("catch_phrase")
-    @get:JsonProperty("catch_phrase")
-    val catchPhrase: String? = "Cowabunga Dude"
+    val requiredSoDefaultIgnored: String,
+    @param:JsonProperty("integer_default")
+    @get:JsonProperty("integer_default")
+    @get:NotNull
+    val integerDefault: Int = 18,
+    @param:JsonProperty("enum_default")
+    @get:JsonProperty("enum_default")
+    @get:NotNull
+    val enumDefault: EnumDefault = EnumDefault.TALL,
+    @param:JsonProperty("boolean_default")
+    @get:JsonProperty("boolean_default")
+    @get:NotNull
+    val booleanDefault: Boolean = true,
+    @param:JsonProperty("string_phrase")
+    @get:JsonProperty("string_phrase")
+    @get:NotNull
+    val stringPhrase: String = "Cowabunga Dude"
 )
 
-enum class HeightCategory(
+enum class EnumDefault(
     @JsonValue
     val value: String
 ) {