fix: recursion (#293)

This commit is contained in:
Ryan Brink
2022-08-13 14:54:16 -07:00
committed by GitHub
parent 62080f3248
commit 7cd0e6154b
4 changed files with 146 additions and 32 deletions

View File

@ -40,6 +40,7 @@ import io.bkbn.kompendium.core.util.TestModules.returnsList
import io.bkbn.kompendium.core.util.TestModules.rootRoute import io.bkbn.kompendium.core.util.TestModules.rootRoute
import io.bkbn.kompendium.core.util.TestModules.simpleGenericResponse import io.bkbn.kompendium.core.util.TestModules.simpleGenericResponse
import io.bkbn.kompendium.core.util.TestModules.simplePathParsing import io.bkbn.kompendium.core.util.TestModules.simplePathParsing
import io.bkbn.kompendium.core.util.TestModules.simpleRecursive
import io.bkbn.kompendium.core.util.TestModules.trailingSlash import io.bkbn.kompendium.core.util.TestModules.trailingSlash
import io.bkbn.kompendium.core.util.TestModules.withOperationId import io.bkbn.kompendium.core.util.TestModules.withOperationId
import io.bkbn.kompendium.json.schema.definition.TypeDefinition import io.bkbn.kompendium.json.schema.definition.TypeDefinition
@ -183,8 +184,8 @@ class KompendiumTest : DescribeSpec({
xit("Can override field name") { xit("Can override field name") {
// TODO Assess strategies here // TODO Assess strategies here
} }
xit("Can serialize a recursive type") { it("Can serialize a recursive type") {
// TODO openApiTestAllSerializers("simple_recursive.json") { simpleRecursive() } openApiTestAllSerializers("T0042__simple_recursive.json") { simpleRecursive() }
} }
it("Nullable fields do not lead to doom") { it("Nullable fields do not lead to doom") {
openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() } openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() }

View File

@ -62,30 +62,7 @@
"type": "null" "type": "null"
}, },
{ {
"type": "object", "$ref": "#/components/schemas/ProfileMetadataUpdateRequest"
"properties": {
"isPrivate": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
}
]
},
"otherThing": {
"oneOf": [
{
"type": "null"
},
{
"type": "string"
}
]
}
},
"required": []
} }
] ]
}, },
@ -112,6 +89,39 @@
} }
}, },
"required": [] "required": []
},
"ProfileMetadataUpdateRequest": {
"oneOf": [
{
"type": "null"
},
{
"type": "object",
"properties": {
"isPrivate": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
}
]
},
"otherThing": {
"oneOf": [
{
"type": "null"
},
{
"type": "string"
}
]
}
},
"required": []
}
]
} }
}, },
"securitySchemes": {} "securitySchemes": {}

View File

@ -0,0 +1,94 @@
{
"openapi": "3.1.0",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "Test API",
"version": "1.33.7",
"description": "An amazing, fully-ish 😉 generated API spec",
"termsOfService": "https://example.com",
"contact": {
"name": "Homer Simpson",
"url": "https://gph.is/1NPUDiM",
"email": "chunkylover53@aol.com"
},
"license": {
"name": "MIT",
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers": [
{
"url": "https://myawesomeapi.com",
"description": "Production instance of my API"
},
{
"url": "https://staging.myawesomeapi.com",
"description": "Where the fun stuff happens"
}
],
"paths": {
"/": {
"get": {
"tags": [],
"summary": "Great Summary!",
"description": "testing more",
"parameters": [],
"responses": {
"200": {
"description": "A Successful Endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ColumnSchema"
}
}
}
}
},
"deprecated": false
},
"parameters": []
}
},
"webhooks": {},
"components": {
"schemas": {
"ColumnSchema": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"mode": {
"enum": [
"NULLABLE",
"REQUIRED",
"REPEATED"
]
},
"name": {
"type": "string"
},
"subColumns": {
"items": {
"$ref": "#/components/schemas/ColumnSchema"
},
"type": "array"
},
"type": {
"type": "string"
}
},
"required": [
"description",
"mode",
"name",
"type"
]
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -20,7 +20,9 @@ import kotlin.reflect.full.primaryConstructor
object SimpleObjectHandler { object SimpleObjectHandler {
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema { fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
// cache[type.getSimpleSlug()] = ReferenceDefinition("RECURSION_PLACEHOLDER")
cache[type.getSimpleSlug()] = ReferenceDefinition(type.getReferenceSlug())
val typeMap = clazz.typeParameters.zip(type.arguments).toMap() val typeMap = clazz.typeParameters.zip(type.arguments).toMap()
val props = clazz.memberProperties.associate { prop -> val props = clazz.memberProperties.associate { prop ->
val schema = when (prop.needsToInjectGenerics(typeMap)) { val schema = when (prop.needsToInjectGenerics(typeMap)) {
@ -31,8 +33,7 @@ object SimpleObjectHandler {
} }
} }
// TODO This is kinda hacky 👀 And might break in certain edge cases? val nullCheckSchema = when (prop.returnType.isMarkedNullable && !schema.isNullable()) {
val nullCheckSchema = when (prop.returnType.isMarkedNullable && schema !is OneOfDefinition) {
true -> OneOfDefinition(NullableDefinition(), schema) true -> OneOfDefinition(NullableDefinition(), schema)
false -> schema false -> schema
} }
@ -77,7 +78,7 @@ object SimpleObjectHandler {
} }
val constructedType = propClass.createType(types) val constructedType = propClass.createType(types)
return SchemaGenerator.fromTypeToSchema(constructedType, cache).let { return SchemaGenerator.fromTypeToSchema(constructedType, cache).let {
if (it is TypeDefinition && it.type == "object") { if (it.isOrContainsObjectDef()) {
cache[constructedType.getSimpleSlug()] = it cache[constructedType.getSimpleSlug()] = it
ReferenceDefinition(prop.returnType.getReferenceSlug()) ReferenceDefinition(prop.returnType.getReferenceSlug())
} else { } else {
@ -93,7 +94,7 @@ object SimpleObjectHandler {
): JsonSchema { ): JsonSchema {
val type = typeMap[prop.returnType.classifier]?.type!! val type = typeMap[prop.returnType.classifier]?.type!!
return SchemaGenerator.fromTypeToSchema(type, cache).let { return SchemaGenerator.fromTypeToSchema(type, cache).let {
if (it is TypeDefinition && it.type == "object") { if (it.isOrContainsObjectDef()) {
cache[type.getSimpleSlug()] = it cache[type.getSimpleSlug()] = it
ReferenceDefinition(prop.returnType.getReferenceSlug()) ReferenceDefinition(prop.returnType.getReferenceSlug())
} else { } else {
@ -104,11 +105,19 @@ object SimpleObjectHandler {
private fun handleProperty(prop: KProperty<*>, cache: MutableMap<String, JsonSchema>): JsonSchema = private fun handleProperty(prop: KProperty<*>, cache: MutableMap<String, JsonSchema>): JsonSchema =
SchemaGenerator.fromTypeToSchema(prop.returnType, cache).let { SchemaGenerator.fromTypeToSchema(prop.returnType, cache).let {
if (it is TypeDefinition && it.type == "object") { if (it.isOrContainsObjectDef()) {
cache[prop.returnType.getSimpleSlug()] = it cache[prop.returnType.getSimpleSlug()] = it
ReferenceDefinition(prop.returnType.getReferenceSlug()) ReferenceDefinition(prop.returnType.getReferenceSlug())
} else { } else {
it it
} }
} }
private fun JsonSchema.isOrContainsObjectDef(): Boolean {
val isTypeDef = this is TypeDefinition && type == "object"
val isTypeDefOneOf = this is OneOfDefinition && this.oneOf.any { js -> js is TypeDefinition && js.type == "object" }
return isTypeDef || isTypeDefOneOf
}
private fun JsonSchema.isNullable(): Boolean = this is OneOfDefinition && this.oneOf.any{ it is NullableDefinition }
} }