feat: create schema reference for enum types (#368)

This commit is contained in:
Geir Sagberg
2022-11-05 21:09:06 +01:00
committed by GitHub
parent 8ebab04a83
commit a7b52ec114
21 changed files with 218 additions and 104 deletions

View File

@ -48,7 +48,7 @@ object SchemaGenerator {
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
UUID::class -> checkForNull(type, TypeDefinition.UUID)
else -> when {
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz)
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz, cache)
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache, schemaConfigurator)
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache, schemaConfigurator)
else -> {

View File

@ -4,5 +4,6 @@ import kotlinx.serialization.Serializable
@Serializable
data class EnumDefinition(
val type: String,
val enum: Set<String>
) : JsonSchema

View File

@ -2,18 +2,17 @@ package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
import kotlin.reflect.KClass
import kotlin.reflect.KType
object EnumHandler {
fun handle(type: KType, clazz: KClass<*>): JsonSchema {
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
cache[type.getSimpleSlug()] = ReferenceDefinition(type.getReferenceSlug())
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
val definition = EnumDefinition(enum = options)
return when (type.isMarkedNullable) {
true -> OneOfDefinition(NullableDefinition(), definition)
false -> definition
}
return EnumDefinition(type = "string", enum = options)
}
}

View File

@ -2,6 +2,7 @@ package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.json.schema.SchemaConfigurator
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
@ -71,16 +72,11 @@ object SimpleObjectHandler {
.map { schemaConfigurator.serializableName(it) }
.toSet()
val definition = TypeDefinition(
return TypeDefinition(
type = "object",
properties = props,
required = required
)
return when (type.isMarkedNullable) {
true -> OneOfDefinition(NullableDefinition(), definition)
false -> definition
}
}
private fun KProperty<*>.needsToInjectGenerics(
@ -103,7 +99,7 @@ object SimpleObjectHandler {
}
val constructedType = propClass.createType(types)
return SchemaGenerator.fromTypeToSchema(constructedType, cache, schemaConfigurator).let {
if (it.isOrContainsObjectDef()) {
if (it.isOrContainsObjectOrEnumDef()) {
cache[constructedType.getSimpleSlug()] = it
ReferenceDefinition(prop.returnType.getReferenceSlug())
} else {
@ -121,7 +117,7 @@ object SimpleObjectHandler {
val type = typeMap[prop.returnType.classifier]?.type
?: error("This indicates a bug in Kompendium, please open a GitHub issue")
return SchemaGenerator.fromTypeToSchema(type, cache, schemaConfigurator).let {
if (it.isOrContainsObjectDef()) {
if (it.isOrContainsObjectOrEnumDef()) {
cache[type.getSimpleSlug()] = it
ReferenceDefinition(type.getReferenceSlug())
} else {
@ -136,7 +132,7 @@ object SimpleObjectHandler {
schemaConfigurator: SchemaConfigurator
): JsonSchema =
SchemaGenerator.fromTypeToSchema(prop.returnType, cache, schemaConfigurator).let {
if (it.isOrContainsObjectDef()) {
if (it.isOrContainsObjectOrEnumDef()) {
cache[prop.returnType.getSimpleSlug()] = it
ReferenceDefinition(prop.returnType.getReferenceSlug())
} else {
@ -144,10 +140,12 @@ object SimpleObjectHandler {
}
}
private fun JsonSchema.isOrContainsObjectDef(): Boolean {
private fun JsonSchema.isOrContainsObjectOrEnumDef(): 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
val isEnumDef = this is EnumDefinition
val isEnumDefOneOf = this is OneOfDefinition && this.oneOf.any { js -> js is EnumDefinition }
return isTypeDef || isTypeDefOneOf || isEnumDef || isEnumDefOneOf
}
private fun JsonSchema.isNullable(): Boolean = this is OneOfDefinition && this.oneOf.any { it is NullableDefinition }

View File

@ -2,6 +2,7 @@ package io.bkbn.kompendium.json.schema
import io.bkbn.kompendium.core.fixtures.ComplexRequest
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
import io.bkbn.kompendium.core.fixtures.ObjectWithEnum
import io.bkbn.kompendium.core.fixtures.SerialNameObject
import io.bkbn.kompendium.core.fixtures.SimpleEnum
import io.bkbn.kompendium.core.fixtures.SlammaJamma
@ -9,7 +10,7 @@ import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot
import io.bkbn.kompendium.core.fixtures.TestResponse
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
import io.bkbn.kompendium.core.fixtures.TransientObject
import io.bkbn.kompendium.core.fixtures.UnbakcedObject
import io.bkbn.kompendium.core.fixtures.UnbackedObject
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.kotest.assertions.json.shouldEqualJson
import io.kotest.assertions.throwables.shouldThrow
@ -40,6 +41,7 @@ class SchemaGeneratorTest : DescribeSpec({
jsonSchemaTest<ComplexRequest>("T0005__complex_object.json")
}
it("Can generate the schema for a nullable object") {
// Same schema as a non-nullable type, since the nullability will be handled on the property
jsonSchemaTest<TestSimpleRequest?>("T0006__nullable_object.json")
}
it("Can generate the schema for a polymorphic object") {
@ -52,7 +54,7 @@ class SchemaGeneratorTest : DescribeSpec({
jsonSchemaTest<TransientObject>("T0018__transient_object.json")
}
it("Can generate the schema for object with unbacked property") {
jsonSchemaTest<UnbakcedObject>("T0019__unbacked_object.json")
jsonSchemaTest<UnbackedObject>("T0019__unbacked_object.json")
}
it("Can generate the schema for object with SerialName annotation") {
jsonSchemaTest<SerialNameObject>("T0020__serial_name_object.json")
@ -63,8 +65,12 @@ class SchemaGeneratorTest : DescribeSpec({
jsonSchemaTest<SimpleEnum>("T0007__simple_enum.json")
}
it("Can generate the schema for a nullable enum") {
// Same schema as a non-nullable enum, since the nullability will be handled on the property
jsonSchemaTest<SimpleEnum?>("T0008__nullable_enum.json")
}
it("Can generate the schema for an object with an enum property") {
jsonSchemaTest<ObjectWithEnum>("T0021__object_with_enum.json")
}
}
describe("Arrays") {
it("Can generate the schema for an array of scalars") {

View File

@ -1,23 +1,16 @@
{
"oneOf": [
{
"type": "null"
"type": "object",
"properties": {
"a": {
"type": "string"
},
{
"type": "object",
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "number",
"format": "int32"
}
},
"required": [
"a",
"b"
]
"b": {
"type": "number",
"format": "int32"
}
},
"required": [
"a",
"b"
]
}

View File

@ -1,3 +1,4 @@
{
"enum": [ "ONE", "TWO" ]
"enum": [ "ONE", "TWO" ],
"type": "string"
}

View File

@ -1,13 +1,4 @@
{
"oneOf": [
{
"type": "null"
},
{
"enum": [
"ONE",
"TWO"
]
}
]
"enum": [ "ONE", "TWO" ],
"type": "string"
}

View File

@ -0,0 +1,11 @@
{
"type": "object",
"properties": {
"color": {
"$ref": "#/components/schemas/Color"
}
},
"required": [
"color"
]
}