Repository: cjbooms/fabrikt (kotlin)

Issue:
Failing Validation on valid OneOf openapi schema
Hey team,

I've been trying to see if this generator will work for us! It seems that the openapi specification files generated by Spring Boot 3's SpringDoc library for our hierarchy classes in kotlin are failing validation via the fabrikt v9 generator. 

Here's an example OpenApi spec file, that passes validation on the [online validator](https://apitools.dev/swagger-parser/online/#), and generates correctly for other clients like the java generator, but fails validation here. 

Thanks for any attention or a fix! It'd be awesome if we could use this.

Command and error message:

`> java -jar fabrikt-9.0.1.jar --output-directory kotlin-fabrikt-client --base-package com.mammalsareawesome --api-file openapi.json --targets CLIENT --targets HTTP_MODELS --validation-library JAKARTA_VALIDATION`

Error:
```
Jul 13, 2023 12:13:34 PM com.cjbooms.fabrikt.cli.CodeGen generate
INFO: Generating code and dumping to kotlin-fabrikt-client/
Exception in thread "main" com.beust.jcommander.ParameterException: Invalid models or api file:
ValidationError(reason='CreateDog' schema contains an invalid combination of properties and `oneOf | anyOf | allOf`. Do not use properties and a combiner at the same level.)
	ValidationError(reason='CreateCow' schema contains an invalid combination of properties and `oneOf | anyOf | allOf`. Do not use properties and a combiner at the same level.)
	at com.cjbooms.fabrikt.model.SourceApi.<init>(SourceApi.kt:40)
	at com.cjbooms.fabrikt.model.SourceApi$Companion.create(SourceApi.kt:31)
	at com.cjbooms.fabrikt.cli.CodeGen.generate(CodeGen.kt:62)
	at com.cjbooms.fabrikt.cli.CodeGen.main(CodeGen.kt:22)
```

OpenApi Spec File:

```
{
    "openapi": "3.0.1",
    "info": {
        "title": "Mammal Microservice",
        "version": "0.0.1"
    },
    "externalDocs": {
        "description": "github",
        "url": "https://github.com/fakenamespace/mammal-service"
    },
    "servers": [
        {
            "url": "http://localhost:8080",
            "description": "Generated server url"
        }
    ],
    "security": [
        {
            "api-key": []
        }
    ],
    "paths": {
        "/api/v1/mammals": {
            "post": {
                "tags": [
                    "mammal-controller"
                ],
                "summary": "Create Mammal APIs.",
                "operationId": "createMammal",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/CreateMammalRequest"
                            }
                        }
                    },
                    "required": true
                },
                "responses": {
                    "201": {
                        "description": "Created",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/MammalCreated"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Bad Request",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/MammalCreateFailure"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "MammalType": {
                "type": "string",
                "enum": [
                    "DOG",
                    "COW",
                ]
            },
            "CreateDog": {
                "required": [
                    "mammalType",
                    "doesBark",
                ],
                "type": "object",
                "allOf": [
                    {
                        "$ref": "#/components/schemas/CreateMammal"
                    },
                    {
                        "type": "object",
                        "properties": {
                            "doesBark": {
                                "type": "boolean"
                            }
                        }
                    }
                ]
            },
            "CreateCow": {
                "required": [
                    "mammalType",
                    "doesTip",
                ],
                "type": "object",
                "allOf": [
                    {
                        "$ref": "#/components/schemas/CreateMammal"
                    },
                    {
                        "type": "object",
                        "properties": {
                            "doesTip": {
                                "type": "boolean",
                                "description": "If you can push it at night and it tips over."
                            }
                        }
                    }
                ]
            },
            "CreateMammal": {
                "required": [
                    "mammalType",
                ],
                "type": "object",
                "properties": {
                    "mammalType": {
                        "$ref": "#/components/schemas/MammalType"
                    }
                },
                "description": "Mammal Creation Parent Object.",
                "discriminator": {
                    "propertyName": "mammalType"
                }
            },
            "CreateMammalRequest": {
                "required": [
                    "createMammal",
                    "requestId",
                    "skipLockValidation"
                ],
                "type": "object",
                "properties": {
                    "createMammal": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/CreateDog"
                            },
                            {
                                "$ref": "#/components/schemas/CreateCow"
                            },
                        ]
                    },
                    "requestId": {
                        "type": "string",
                        "format": "uuid"
                    }
                }
            },
            "MammalCreateFailure": {
                "required": [
                    "error",
                ],
                "type": "object",
                "properties": {
                    "error": {
                        "type": "string"
                    }
                }
            },
            "MammalCreated": {
                "required": [
                    "message",
                ],
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string"
                    }
                }
            }
        },
        "securitySchemes": {
            "api-key": {
                "type": "apiKey",
                "name": "api-key",
                "in": "header"
            }
        }
    }
}
```

Pull request:
Relax validation such that schemas that make use of allOf oneOf or anyOf may be declared as type object.
Closes #223

Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
index 21603c5d..a949e473 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
@@ -49,7 +49,7 @@ data class SourceApi(
         val schemaErrors = api.schemas.entries.fold(emptyList<ValidationError>()) { errors, entry ->
             val name = entry.key
             val schema = entry.value
-            if (schema.type == OasType.Object.type && (
+            if (schema.type == OasType.Object.type && schema.properties?.isNotEmpty() == true && (
                     schema.oneOfSchemas?.isNotEmpty() == true ||
                         schema.allOfSchemas?.isNotEmpty() == true ||
                         schema.anyOfSchemas?.isNotEmpty() == true
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
index a15248b6..798ad2e6 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
@@ -61,7 +61,11 @@ object KaizenParserExtensions {
 
     fun Schema.toMapValueClassName() = safeName().toMapValueClassName()
 
-    fun Schema.isSchemaLess() = isObjectType() && properties?.isEmpty() == true
+    fun Schema.isSchemaLess() = isObjectType() && properties?.isEmpty() == true && (
+        oneOfSchemas?.isNotEmpty() != true &&
+            allOfSchemas?.isNotEmpty() != true &&
+            anyOfSchemas?.isNotEmpty() != true
+        )
 
     fun Schema.isSimpleMapDefinition() = hasAdditionalProperties() && properties?.isEmpty() == true
 


Test patch:
diff --git a/src/test/resources/examples/anyOfOneOfAllOf/api.yaml b/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
index 01941ead..fb73a759 100644
--- a/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
+++ b/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
@@ -21,6 +21,9 @@ components:
           type: integer
 
     ComplexParent:
+      required:
+        - top_level_prop
+      type: object
       allOf:
         - $ref: '#/components/schemas/ContainsNestedOneOf'
         - $ref: '#/components/schemas/ContainsNestedAnyOf'