Repository: cjbooms/fabrikt
Pull request #109 (2022-03-03 15:47:34)

Support OpenApi3 Any type
- [x] Added failing test
- [x] Fixed some formatting issues
- [x] Improved validation logic for broken links to not rely solely on type being null
- [x] Add support for any type


cjbooms: 👍 

Patch:
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 37c454c..048ab1f 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
@@ -32,7 +32,6 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlinedTypedAdditionalP
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isOneOfPolymorphicTypes
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isPolymorphicSubType
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isPolymorphicSuperType
-import com.cjbooms.fabrikt.util.KaizenParserExtensions.isReferenceObjectDefinition
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSimpleType
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.mappingKeys
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeName
@@ -241,7 +240,7 @@ class JacksonModelGenerator(
                 try {
                     externalApiSchemas.getOrPut(URL(docUrl)) { mutableSetOf() }.add(schema.safeName())
                 } catch (ex: MalformedURLException) {
-                    // skip 
+                    // skip
                 }
             }
             if (depth < 10) schema.captureMissingExternalSchemas(apiDocUrl, depth + 1)
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
index 6ffd107..cda32ea 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
@@ -27,6 +27,7 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
     object Uuid : KotlinTypeInfo(UUID::class)
     object Boolean : KotlinTypeInfo(kotlin.Boolean::class)
     object UntypedObject : KotlinTypeInfo(Any::class)
+    object AnyType : KotlinTypeInfo(Any::class)
     data class Object(val simpleClassName: String) : KotlinTypeInfo(GeneratedType::class, simpleClassName)
     data class Array(val parameterizedType: KotlinTypeInfo) : KotlinTypeInfo(List::class)
     data class Map(val parameterizedType: KotlinTypeInfo) : KotlinTypeInfo(Map::class)
@@ -81,6 +82,7 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
                     MapTypeAdditionalProperties(
                         from(schema.additionalPropertiesSchema, "", enclosingName)
                     )
+                OasType.Any -> AnyType
             }
     }
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt
index 91938e2..0f747ca 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt
@@ -18,6 +18,7 @@ sealed class OasType(
     val format: String? = null,
     val specialization: Specialization = Specialization.NONE
 ) {
+    object Any : OasType(null)
     object Boolean : OasType("boolean")
     object Date : OasType("string", "date")
     object DateTime : OasType("string", "date-time")
@@ -34,10 +35,18 @@ sealed class OasType(
     object Enum : OasType("string", specialization = Specialization.ENUM)
     object Uuid : OasType("string", specialization = Specialization.UUID)
     object Map : OasType("object", specialization = Specialization.MAP)
-    object UnknownAdditionalProperties : OasType("object", specialization = Specialization.UNKNOWN_ADDITIONAL_PROPERTIES)
-    object UntypedObjectAdditionalProperties : OasType("object", specialization = Specialization.UNTYPED_OBJECT_ADDITIONAL_PROPERTIES)
-    object TypedObjectAdditionalProperties : OasType("object", specialization = Specialization.TYPED_OBJECT_ADDITIONAL_PROPERTIES)
-    object TypedMapAdditionalProperties : OasType("object", specialization = Specialization.TYPED_MAP_ADDITIONAL_PROPERTIES)
+    object UnknownAdditionalProperties :
+        OasType("object", specialization = Specialization.UNKNOWN_ADDITIONAL_PROPERTIES)
+
+    object UntypedObjectAdditionalProperties :
+        OasType("object", specialization = Specialization.UNTYPED_OBJECT_ADDITIONAL_PROPERTIES)
+
+    object TypedObjectAdditionalProperties :
+        OasType("object", specialization = Specialization.TYPED_OBJECT_ADDITIONAL_PROPERTIES)
+
+    object TypedMapAdditionalProperties :
+        OasType("object", specialization = Specialization.TYPED_MAP_ADDITIONAL_PROPERTIES)
+
     companion object {
         fun Schema.toOasType(oasKey: String): OasType =
             values(OasType::class)
@@ -47,7 +56,9 @@ sealed class OasType(
                 .let { candidates ->
                     if (candidates.size > 1) candidates.find { it.format == format }
                     else candidates.firstOrNull()
-                } ?: throw IllegalStateException("Unknown OAS type: ${safeType()} and format: $format")
+                } ?: throw IllegalStateException(
+                "Unknown OAS type: ${safeType()} and format: $format and specialization: ${getSpecialization(oasKey)}"
+            )
 
         private fun values(clazz: KClass<OasType>) =
             clazz.nestedClasses.filter { it.isFinal && it.isSubclassOf(clazz) }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
index abc9257..131be31 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
@@ -4,8 +4,6 @@ import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
 import com.cjbooms.fabrikt.generators.MutableSettings
 import com.cjbooms.fabrikt.model.OasType
 import com.cjbooms.fabrikt.model.PropertyInfo
-import com.cjbooms.fabrikt.util.KaizenParserExtensions.getSchemaNameInParent
-import com.cjbooms.fabrikt.util.KaizenParserExtensions.mappingKeys
 import com.cjbooms.fabrikt.util.NormalisedString.toMapValueClassName
 import com.cjbooms.fabrikt.util.NormalisedString.toModelClassName
 import com.reprezen.jsonoverlay.Overlay
@@ -52,9 +50,6 @@ object KaizenParserExtensions {
     fun Schema.isInlinedArrayDefinition() =
         isArrayType() && !isSchemaLess() && this.itemsSchema.isInlinedObjectDefinition()
 
-    fun Schema.isReferenceObjectDefinition() =
-        isObjectType() && !isSchemaLess() && !Overlay.of(this).pathFromRoot.contains("properties")
-
     fun Schema.toModelClassName(enclosingClassName: String = "") = enclosingClassName + safeName().toModelClassName()
 
     fun Schema.toMapValueClassName() = safeName().toMapValueClassName()
@@ -77,16 +72,17 @@ object KaizenParserExtensions {
     fun Schema.getEnumValues(): List<String> = when {
         this.hasEnums() -> this.enums.map { it.toString() }.filterNot { it.isBlank() }
         !MutableSettings.modelOptions().contains(ModelCodeGenOptionType.X_EXTENSIBLE_ENUMS) -> emptyList()
-        else -> extensions[EXTENSIBLE_ENUM_KEY]?.let { it as List<String?> }?.filterNotNull()?.filterNot { it.isBlank() } ?: emptyList()
+        else -> extensions[EXTENSIBLE_ENUM_KEY]?.let { it as List<String?> }?.filterNotNull()
+            ?.filterNot { it.isBlank() } ?: emptyList()
     }
 
     fun Schema.hasAdditionalProperties(): Boolean = isObjectType() && Overlay.of(additionalPropertiesSchema).isPresent
 
     fun Schema.isUnknownAdditionalProperties(oasKey: String) = type == null &&
-        getSchemaNameInParent() ?: oasKey == "additionalProperties" && properties?.isEmpty() == true
+        (getSchemaNameInParent() ?: oasKey) == "additionalProperties" && properties?.isEmpty() == true
 
     fun Schema.isUntypedAdditionalProperties(oasKey: String) = type == OasType.Object.type &&
-        getSchemaNameInParent() ?: oasKey == "additionalProperties" && properties?.isEmpty() == true
+        (getSchemaNameInParent() ?: oasKey) == "additionalProperties" && properties?.isEmpty() == true
 
     fun Schema.isTypedAdditionalProperties(oasKey: String) = type == OasType.Object.type &&
         (getSchemaNameInParent() == "additionalProperties" || oasKey == "additionalProperties") && properties?.isEmpty() != true
@@ -95,8 +91,8 @@ object KaizenParserExtensions {
         (oasKey == "additionalProperties") && properties?.isEmpty() == true &&
         hasAdditionalProperties()
 
-    fun Schema.isComplexTypedAdditionalProperties(oasKey: String) =
-        getSchemaNameInParent() ?: oasKey == "additionalProperties" && properties?.isEmpty() != true && !isSimpleType()
+    fun Schema.isComplexTypedAdditionalProperties(oasKey: String) = (getSchemaNameInParent() ?: oasKey) ==
+        "additionalProperties" && properties?.isEmpty() != true && !isSimpleType()
 
     fun Schema.isSimpleType(): Boolean =
         (simpleTypes.contains(type) && !isEnumDefinition()) || isInlineableMapDefinition()
@@ -105,7 +101,8 @@ object KaizenParserExtensions {
 
     private fun Schema.isArrayType() = OasType.Array.type == type
 
-    fun Schema.isNotDefined() = type == null && !(hasAllOfSchemas() || hasOneOfSchemas() || hasAnyOfSchemas())
+    fun Schema.isNotDefined() = !Overlay.of(this).isPresent &&
+        type == null && !(hasAllOfSchemas() || hasOneOfSchemas() || hasAnyOfSchemas())
 
     private fun Schema.getSchemaNameInParent(): String? = Overlay.of(this).pathInParent
 
@@ -175,7 +172,9 @@ object KaizenParserExtensions {
             allOfSchemas?.firstOrNull { it.type != null } != null -> allOfSchemas.first { it.type != null }.type
             oneOfSchemas?.firstOrNull { it.type != null } != null -> oneOfSchemas.first { it.type != null }.type
             anyOfSchemas?.firstOrNull { it.type != null } != null -> anyOfSchemas.first { it.type != null }.type
-            else -> "object"
+            isOneOfPolymorphicTypes() -> "object"
+            isUnknownAdditionalProperties("") -> "object"
+            else -> null
         }
 
     fun Schema.isOneOfPolymorphicTypes() =


Test patch:
diff --git a/src/test/resources/examples/anyOfOneOfAllOf/api.yaml b/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
index c2dbe49..a69ac8d 100644
--- a/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
+++ b/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
@@ -60,4 +60,9 @@ components:
         second_property:
           type: string
         third_property:
-          type: string          
\ No newline at end of file
+          type: string   
+        forth_property:
+          $ref: '#/components/schemas/AnyValue'
+            
+    AnyValue:
+      description: Tests support for https://swagger.io/docs/specification/data-models/data-types/#any
\ No newline at end of file
diff --git a/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt b/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
index ae7ade2..bafccb3 100644
--- a/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
+++ b/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
@@ -2,6 +2,7 @@ package examples.anyOfOneOfAllOf.models
 
 import com.fasterxml.jackson.annotation.JsonProperty
 import java.math.BigDecimal
+import kotlin.Any
 import kotlin.String
 
 data class ComplexParent(
@@ -82,5 +83,8 @@ data class SecondOneB(
     val secondProperty: String? = null,
     @param:JsonProperty("third_property")
     @get:JsonProperty("third_property")
-    val thirdProperty: String? = null
+    val thirdProperty: String? = null,
+    @param:JsonProperty("forth_property")
+    @get:JsonProperty("forth_property")
+    val forthProperty: Any? = null
 )