fix: recursion (#293)
This commit is contained in:
@ -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() }
|
||||||
|
@ -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": {}
|
||||||
|
94
core/src/test/resources/T0042__simple_recursive.json
Normal file
94
core/src/test/resources/T0042__simple_recursive.json
Normal 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": []
|
||||||
|
}
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user