Repository: cjbooms/fabrikt
Pull request #138 (2022-07-22 11:09:53)

Fix handling of default values.
- URI format string types did correctly handle default values. It produced compile-time errors because of: `uriType: URI = "http://some-uri"`.
- Default handling of parameters in generated clients was also incorrect, this has code path now uses the same default handling logic which has been moved to a dedicated class.


Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/OasDefault.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/OasDefault.kt
new file mode 100644
index 0000000..626179e
--- /dev/null
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/OasDefault.kt
@@ -0,0 +1,62 @@
+package com.cjbooms.fabrikt.generators
+
+import com.cjbooms.fabrikt.model.KotlinTypeInfo
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.TypeName
+import java.net.URI
+
+sealed class OasDefault {
+
+    abstract fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder
+
+    data class StringValue(val strValue: String) : OasDefault() {
+        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
+            return paramSpecBuilder.defaultValue("%S", strValue)
+        }
+    }
+
+    data class NumericValue(val numericValue: Number) : OasDefault() {
+        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
+            return paramSpecBuilder.defaultValue("%L", numericValue)
+        }
+    }
+
+    data class UriValue(val strValue: String) : OasDefault() {
+        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
+            return paramSpecBuilder.defaultValue("%T(\"$strValue\")", URI::class)
+        }
+    }
+
+    data class BooleanValue(val boolValue: Boolean) : OasDefault() {
+        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
+            return paramSpecBuilder.defaultValue("%L", boolValue)
+        }
+    }
+
+    data class EnumValue(val type: TypeName, val enumValue: String) : OasDefault() {
+        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
+            return paramSpecBuilder.defaultValue("%T.${enumValue.toUpperCase()}", type)
+        }
+    }
+
+    companion object {
+        fun from(typeInfo: KotlinTypeInfo, type: TypeName?, default: Any): OasDefault? {
+            return when (default) {
+                is String -> {
+                    when (typeInfo) {
+                        is KotlinTypeInfo.Enum -> {
+                            if (type != null && typeInfo.entries.contains(default)) OasDefault.EnumValue(type, default)
+                            else null
+                        }
+                        is KotlinTypeInfo.Text -> OasDefault.StringValue(default)
+                        is KotlinTypeInfo.Uri -> OasDefault.UriValue(default)
+                        else -> null
+                    }
+                }
+                is Number -> OasDefault.NumericValue(default)
+                is Boolean -> OasDefault.BooleanValue(default)
+                else -> null
+            }
+        }
+    }
+}
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
index 83f02c8..a4633b7 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
@@ -20,35 +20,6 @@ enum class ClassType {
     SUB_MODEL
 }
 
-private sealed class OasDefault {
-
-    abstract fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder
-
-    data class StringValue(val strValue: String) : OasDefault() {
-        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
-            return paramSpecBuilder.defaultValue("%S", strValue)
-        }
-    }
-
-    data class NumericValue(val numericValue: Number) : OasDefault() {
-        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
-            return paramSpecBuilder.defaultValue("%L", numericValue)
-        }
-    }
-
-    data class BooleanValue(val boolValue: Boolean) : OasDefault() {
-        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
-            return paramSpecBuilder.defaultValue("%L", boolValue)
-        }
-    }
-
-    data class EnumValue(val type: ClassName, val enumValue: String) : OasDefault() {
-        override fun setDefault(paramSpecBuilder: ParameterSpec.Builder): ParameterSpec.Builder {
-            return paramSpecBuilder.defaultValue("%T.${enumValue.toUpperCase()}", type)
-        }
-    }
-}
-
 object PropertyUtils {
 
     fun PropertyInfo.addToClass(
@@ -64,9 +35,9 @@ object PropertyUtils {
             property.initializer("mutableMapOf()")
             property.addAnnotation(JacksonMetadata.ignore)
             val value =
-                if (typeInfo is KotlinTypeInfo.MapTypeAdditionalProperties)
+                if (typeInfo is KotlinTypeInfo.MapTypeAdditionalProperties) {
                     Map::class.asTypeName().parameterizedBy(String::class.asTypeName(), parameterizedType)
-                else parameterizedType
+                } else parameterizedType
             classBuilder.addFunction(
                 FunSpec.builder("get")
                     .returns(Map::class.asTypeName().parameterizedBy(String::class.asTypeName(), value))
@@ -139,7 +110,7 @@ object PropertyUtils {
         return when (propTypeInfo) {
             is PropertyInfo.Field -> propTypeInfo.schema.default?.let {
                 val className = parameterizedType as? ClassName
-                getDefaultValue(propTypeInfo, className, it)
+                OasDefault.from(propTypeInfo.typeInfo, className, it)
             }
             else -> null
         }
@@ -223,21 +194,4 @@ object PropertyUtils {
             }
         }
     }
-
-    private fun getDefaultValue(field: PropertyInfo.Field, type: ClassName?, default: Any): OasDefault? {
-        return when (default) {
-            is String -> {
-                when (val typeInfo = field.typeInfo) {
-                    is KotlinTypeInfo.Enum -> {
-                        if (type != null && typeInfo.entries.contains(default)) OasDefault.EnumValue(type, default)
-                        else null
-                    }
-                    else -> OasDefault.StringValue(default)
-                }
-            }
-            is Number -> OasDefault.NumericValue(default)
-            is Boolean -> OasDefault.BooleanValue(default)
-            else -> null
-        }
-    }
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
index 0259cdd..68aafbc 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
@@ -8,6 +8,7 @@ import com.cjbooms.fabrikt.generators.GeneratorUtils.hasMultipleContentMediaType
 import com.cjbooms.fabrikt.generators.GeneratorUtils.hasMultipleResponseSchemas
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toClassName
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toIncomingParameters
+import com.cjbooms.fabrikt.generators.OasDefault
 import com.cjbooms.fabrikt.generators.model.JacksonModelGenerator.Companion.toModelType
 import com.cjbooms.fabrikt.model.ClientType
 import com.cjbooms.fabrikt.model.HeaderParam
@@ -86,7 +87,7 @@ object ClientGeneratorUtils {
         val specs = parameters.map {
             val builder = it.toParameterSpecBuilder()
             if (it is RequestParameter && it.defaultValue != null) {
-                builder.defaultValue("%S", it.defaultValue)
+                OasDefault.from(it.typeInfo, it.type, it.defaultValue)?.setDefault(builder)
             }
             builder.build()
         }


Test patch:
diff --git a/src/test/resources/examples/defaultValues/api.yaml b/src/test/resources/examples/defaultValues/api.yaml
index db6a85b..addb98e 100644
--- a/src/test/resources/examples/defaultValues/api.yaml
+++ b/src/test/resources/examples/defaultValues/api.yaml
@@ -24,3 +24,12 @@ components:
         string_phrase:
           type: string
           default: Cowabunga Dude
+        uri_type:
+          type: string
+          format: uri
+          description: |
+            An absolute URI that identifies the problem type.  When dereferenced,
+            it SHOULD provide human-readable documentation for the problem type
+            (e.g., using HTML).
+          default: 'about:blank'
+          example: 'https://zalando.github.io/problem/constraint-violation'
diff --git a/src/test/resources/examples/defaultValues/models/Models.kt b/src/test/resources/examples/defaultValues/models/Models.kt
index 4c94ffa..69e4b81 100644
--- a/src/test/resources/examples/defaultValues/models/Models.kt
+++ b/src/test/resources/examples/defaultValues/models/Models.kt
@@ -2,6 +2,7 @@ package examples.defaultValues.models
 
 import com.fasterxml.jackson.annotation.JsonProperty
 import com.fasterxml.jackson.annotation.JsonValue
+import java.net.URI
 import javax.validation.constraints.NotNull
 import kotlin.Boolean
 import kotlin.Int
@@ -28,7 +29,11 @@ data class PersonWithDefaults(
     @param:JsonProperty("string_phrase")
     @get:JsonProperty("string_phrase")
     @get:NotNull
-    val stringPhrase: String = "Cowabunga Dude"
+    val stringPhrase: String = "Cowabunga Dude",
+    @param:JsonProperty("uri_type")
+    @get:JsonProperty("uri_type")
+    @get:NotNull
+    val uriType: URI = URI("about:blank")
 )
 
 enum class PersonWithDefaultsEnumDefault(
diff --git a/src/test/resources/examples/okHttpClient/api.yaml b/src/test/resources/examples/okHttpClient/api.yaml
index 61d2671..cb3bff0 100644
--- a/src/test/resources/examples/okHttpClient/api.yaml
+++ b/src/test/resources/examples/okHttpClient/api.yaml
@@ -40,6 +40,7 @@ paths:
         - $ref: "#/components/parameters/PathParam"
         - $ref: "#/components/parameters/QueryParam2"
         - $ref: "#/components/parameters/IfNoneMatch"
+        - $ref: "#/components/parameters/PaginationLimitParam"
       responses:
         200:
           description: "successful operation"
@@ -144,6 +145,16 @@ components:
           type: "string"
       required: false
       allowEmptyValue: false
+      
+    PaginationLimitParam:
+      name: limit
+      in: query
+      schema:
+        type: integer
+        minimum: 1
+        maximum: 1000
+        default: 500
+      required: false
 
     QueryParam2:
       name: "query_param2"
diff --git a/src/test/resources/examples/okHttpClient/client/ApiClient.kt b/src/test/resources/examples/okHttpClient/client/ApiClient.kt
index b66af8c..04e72b6 100644
--- a/src/test/resources/examples/okHttpClient/client/ApiClient.kt
+++ b/src/test/resources/examples/okHttpClient/client/ApiClient.kt
@@ -101,12 +101,14 @@ class ExamplePath2Client(
      * GET example path 2
      *
      * @param pathParam The resource id
+     * @param limit
      * @param queryParam2
      * @param ifNoneMatch The RFC7232 If-None-Match header field
      */
     @Throws(ApiException::class)
     fun getExamplePath2PathParam(
         pathParam: String,
+        limit: Int = 500,
         queryParam2: Int?,
         ifNoneMatch: String?,
         additionalHeaders: Map<String, String> = emptyMap()
@@ -115,6 +117,7 @@ class ExamplePath2Client(
             .pathParam("{path_param}" to pathParam)
             .toHttpUrl()
             .newBuilder()
+            .queryParam("limit", limit)
             .queryParam("query_param2", queryParam2)
             .build()
 
diff --git a/src/test/resources/examples/okHttpClient/client/ApiService.kt b/src/test/resources/examples/okHttpClient/client/ApiService.kt
index cedf7df..ac78aec 100644
--- a/src/test/resources/examples/okHttpClient/client/ApiService.kt
+++ b/src/test/resources/examples/okHttpClient/client/ApiService.kt
@@ -77,12 +77,13 @@ class ExamplePath2Service(
     @Throws(ApiException::class)
     fun getExamplePath2PathParam(
         pathParam: String,
+        limit: Int = 500,
         queryParam2: Int?,
         ifNoneMatch: String?,
         additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Content> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.getExamplePath2PathParam(pathParam, queryParam2, ifNoneMatch, additionalHeaders)
+            apiClient.getExamplePath2PathParam(pathParam, limit, queryParam2, ifNoneMatch, additionalHeaders)
         }
 
     @Throws(ApiException::class)