feat: type enrichment (#408)

This commit is contained in:
Ryan Brink
2023-01-04 21:32:31 -05:00
committed by GitHub
parent 73fb8b137f
commit 91bf93a866
43 changed files with 1372 additions and 102 deletions

View File

@ -20,6 +20,9 @@ dependencies {
// Versions
val detektVersion: String by project
// Kompendium
api(projects.kompendiumEnrichment)
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")

View File

@ -1,5 +1,6 @@
package io.bkbn.kompendium.json.schema
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
@ -9,28 +10,26 @@ import io.bkbn.kompendium.json.schema.handler.EnumHandler
import io.bkbn.kompendium.json.schema.handler.MapHandler
import io.bkbn.kompendium.json.schema.handler.SealedObjectHandler
import io.bkbn.kompendium.json.schema.handler.SimpleObjectHandler
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSlug
import java.util.UUID
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.typeOf
import java.util.UUID
object SchemaGenerator {
inline fun <reified T : Any?> fromTypeToSchema(
cache: MutableMap<String, JsonSchema> = mutableMapOf(),
schemaConfigurator: SchemaConfigurator = SchemaConfigurator.Default()
) = fromTypeToSchema(typeOf<T>(), cache, schemaConfigurator)
fun fromTypeToSchema(
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null
): JsonSchema {
cache[type.getSimpleSlug()]?.let {
val slug = type.getSlug(enrichment)
cache[slug]?.let {
return it
}
return when (val clazz = type.classifier as KClass<*>) {
Unit::class -> error(
"""
@ -48,14 +47,14 @@ object SchemaGenerator {
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
UUID::class -> checkForNull(type, TypeDefinition.UUID)
else -> when {
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)
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz, cache, enrichment)
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache, schemaConfigurator, enrichment)
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache, schemaConfigurator, enrichment)
else -> {
if (clazz.isSealed) {
SealedObjectHandler.handle(type, clazz, cache, schemaConfigurator)
SealedObjectHandler.handle(type, clazz, cache, schemaConfigurator, enrichment)
} else {
SimpleObjectHandler.handle(type, clazz, cache, schemaConfigurator)
SimpleObjectHandler.handle(type, clazz, cache, schemaConfigurator, enrichment)
}
}
}
@ -65,11 +64,12 @@ object SchemaGenerator {
fun fromTypeOrUnit(
type: KType,
cache: MutableMap<String, JsonSchema> = mutableMapOf(),
schemaConfigurator: SchemaConfigurator
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null
): JsonSchema? =
when (type.classifier as KClass<*>) {
Unit::class -> null
else -> fromTypeToSchema(type, cache, schemaConfigurator)
else -> fromTypeToSchema(type, cache, schemaConfigurator, enrichment)
}
private fun checkForNull(type: KType, schema: JsonSchema): JsonSchema = when (type.isMarkedNullable) {

View File

@ -3,4 +3,8 @@ package io.bkbn.kompendium.json.schema.definition
import kotlinx.serialization.Serializable
@Serializable
data class AnyOfDefinition(val anyOf: Set<JsonSchema>) : JsonSchema
data class AnyOfDefinition(
val anyOf: Set<JsonSchema>,
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema

View File

@ -4,7 +4,9 @@ import kotlinx.serialization.Serializable
@Serializable
data class ArrayDefinition(
val items: JsonSchema
val items: JsonSchema,
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema {
val type: String = "array"
}

View File

@ -5,5 +5,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class EnumDefinition(
val type: String,
val enum: Set<String>
val enum: Set<String>,
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema

View File

@ -11,6 +11,9 @@ import kotlinx.serialization.encoding.Encoder
@Serializable(with = JsonSchema.Serializer::class)
sealed interface JsonSchema {
val description: String?
val deprecated: Boolean?
object Serializer : KSerializer<JsonSchema> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("JsonSchema", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): JsonSchema {

View File

@ -4,7 +4,9 @@ import kotlinx.serialization.Serializable
@Serializable
data class MapDefinition(
val additionalProperties: JsonSchema
val additionalProperties: JsonSchema,
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema {
val type: String = "object"
}

View File

@ -3,4 +3,8 @@ package io.bkbn.kompendium.json.schema.definition
import kotlinx.serialization.Serializable
@Serializable
data class NullableDefinition(val type: String = "null") : JsonSchema
data class NullableDefinition(
val type: String = "null",
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema

View File

@ -3,6 +3,10 @@ package io.bkbn.kompendium.json.schema.definition
import kotlinx.serialization.Serializable
@Serializable
data class OneOfDefinition(val oneOf: Set<JsonSchema>) : JsonSchema {
data class OneOfDefinition(
val oneOf: Set<JsonSchema>,
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema {
constructor(vararg types: JsonSchema) : this(types.toSet())
}

View File

@ -3,4 +3,8 @@ package io.bkbn.kompendium.json.schema.definition
import kotlinx.serialization.Serializable
@Serializable
data class ReferenceDefinition(val `$ref`: String) : JsonSchema
data class ReferenceDefinition(
val `$ref`: String,
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema

View File

@ -7,10 +7,11 @@ import kotlinx.serialization.Serializable
data class TypeDefinition(
val type: String,
val format: String? = null,
val description: String? = null,
val properties: Map<String, JsonSchema>? = null,
val required: Set<String>? = null,
@Contextual val default: Any? = null,
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema {
fun withDefault(default: Any): TypeDefinition = this.copy(default = default)

View File

@ -1,5 +1,6 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.json.schema.SchemaConfigurator
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.ArrayDefinition
@ -9,17 +10,22 @@ import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSlug
import kotlin.reflect.KType
object CollectionHandler {
fun handle(type: KType, cache: MutableMap<String, JsonSchema>, schemaConfigurator: SchemaConfigurator): JsonSchema {
fun handle(
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null
): JsonSchema {
val collectionType = type.arguments.first().type
?: error("This indicates a bug in Kompendium, please open a GitHub issue!")
val typeSchema = SchemaGenerator.fromTypeToSchema(collectionType, cache, schemaConfigurator).let {
val typeSchema = SchemaGenerator.fromTypeToSchema(collectionType, cache, schemaConfigurator, enrichment).let {
if (it is TypeDefinition && it.type == "object") {
cache[collectionType.getSimpleSlug()] = it
ReferenceDefinition(collectionType.getReferenceSlug())
cache[collectionType.getSlug(enrichment)] = it
ReferenceDefinition(collectionType.getReferenceSlug(enrichment))
} else {
it
}

View File

@ -1,16 +1,22 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
import io.bkbn.kompendium.json.schema.definition.JsonSchema
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 io.bkbn.kompendium.json.schema.util.Helpers.getSlug
import kotlin.reflect.KClass
import kotlin.reflect.KType
object EnumHandler {
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
cache[type.getSimpleSlug()] = ReferenceDefinition(type.getReferenceSlug())
fun handle(
type: KType,
clazz: KClass<*>,
cache: MutableMap<String, JsonSchema>,
enrichment: TypeEnrichment<*>? = null
): JsonSchema {
cache[type.getSlug(enrichment)] = ReferenceDefinition(type.getReferenceSlug(enrichment))
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
return EnumDefinition(type = "string", enum = options)

View File

@ -1,5 +1,6 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.json.schema.SchemaConfigurator
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.JsonSchema
@ -9,21 +10,26 @@ import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSlug
import kotlin.reflect.KClass
import kotlin.reflect.KType
object MapHandler {
fun handle(type: KType, cache: MutableMap<String, JsonSchema>, schemaConfigurator: SchemaConfigurator): JsonSchema {
fun handle(
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null
): JsonSchema {
require(type.arguments.first().type?.classifier as KClass<*> == String::class) {
"JSON requires that map keys MUST be Strings. You provided ${type.arguments.first().type}"
}
val valueType = type.arguments[1].type ?: error("this indicates a bug in Kompendium, please open a GitHub issue")
val valueSchema = SchemaGenerator.fromTypeToSchema(valueType, cache, schemaConfigurator).let {
val valueSchema = SchemaGenerator.fromTypeToSchema(valueType, cache, schemaConfigurator, enrichment).let {
if (it is TypeDefinition && it.type == "object") {
cache[valueType.getSimpleSlug()] = it
ReferenceDefinition(valueType.getReferenceSlug())
cache[valueType.getSlug(enrichment)] = it
ReferenceDefinition(valueType.getReferenceSlug(enrichment))
} else {
it
}

View File

@ -1,5 +1,6 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.json.schema.SchemaConfigurator
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.AnyOfDefinition
@ -7,7 +8,7 @@ import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSlug
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.createType
@ -18,15 +19,17 @@ object SealedObjectHandler {
type: KType,
clazz: KClass<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null,
): JsonSchema {
val subclasses = clazz.sealedSubclasses
.map { it.createType(type.arguments) }
.map { t ->
SchemaGenerator.fromTypeToSchema(t, cache, schemaConfigurator).let { js ->
SchemaGenerator.fromTypeToSchema(t, cache, schemaConfigurator, enrichment).let { js ->
if (js is TypeDefinition && js.type == "object") {
cache[t.getSimpleSlug()] = js
ReferenceDefinition(t.getReferenceSlug())
val slug = t.getSlug(enrichment)
cache[slug] = js
ReferenceDefinition(t.getReferenceSlug(enrichment))
} else {
js
}

View File

@ -1,16 +1,21 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.PropertyEnrichment
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.json.schema.SchemaConfigurator
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.AnyOfDefinition
import io.bkbn.kompendium.json.schema.definition.ArrayDefinition
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.bkbn.kompendium.json.schema.definition.MapDefinition
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.definition.TypeDefinition
import io.bkbn.kompendium.json.schema.exception.UnknownSchemaException
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSlug
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KType
@ -26,26 +31,34 @@ object SimpleObjectHandler {
type: KType,
clazz: KClass<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>?,
): JsonSchema {
cache[type.getSimpleSlug()] = ReferenceDefinition(type.getReferenceSlug())
cache[type.getSlug(enrichment)] = ReferenceDefinition(type.getReferenceSlug(enrichment))
val typeMap = clazz.typeParameters.zip(type.arguments).toMap()
val props = schemaConfigurator.serializableMemberProperties(clazz)
.filterNot { it.javaField == null }
.associate { prop ->
val propTypeEnrichment = when (val pe = enrichment?.getEnrichmentForProperty(prop)) {
is PropertyEnrichment -> pe
else -> null
}
val schema = when (prop.needsToInjectGenerics(typeMap)) {
true -> handleNestedGenerics(typeMap, prop, cache, schemaConfigurator)
true -> handleNestedGenerics(typeMap, prop, cache, schemaConfigurator, propTypeEnrichment)
false -> when (typeMap.containsKey(prop.returnType.classifier)) {
true -> handleGenericProperty(prop, typeMap, cache, schemaConfigurator)
false -> handleProperty(prop, cache, schemaConfigurator)
true -> handleGenericProperty(prop, typeMap, cache, schemaConfigurator, propTypeEnrichment)
false -> handleProperty(prop, cache, schemaConfigurator, propTypeEnrichment?.typeEnrichment)
}
}
val nullCheckSchema = when (prop.returnType.isMarkedNullable && !schema.isNullable()) {
true -> OneOfDefinition(NullableDefinition(), schema)
false -> schema
val enrichedSchema = propTypeEnrichment?.applyToSchema(schema) ?: schema
val nullCheckSchema = when (prop.returnType.isMarkedNullable && !enrichedSchema.isNullable()) {
true -> OneOfDefinition(NullableDefinition(), enrichedSchema)
false -> enrichedSchema
}
schemaConfigurator.serializableName(prop) to nullCheckSchema
@ -90,7 +103,8 @@ object SimpleObjectHandler {
typeMap: Map<KTypeParameter, KTypeProjection>,
prop: KProperty<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator
schemaConfigurator: SchemaConfigurator,
propEnrichment: PropertyEnrichment?
): JsonSchema {
val propClass = prop.returnType.classifier as KClass<*>
val types = prop.returnType.arguments.map {
@ -98,28 +112,30 @@ object SimpleObjectHandler {
typeMap.filterKeys { k -> k.name == typeSymbol }.values.first()
}
val constructedType = propClass.createType(types)
return SchemaGenerator.fromTypeToSchema(constructedType, cache, schemaConfigurator).let {
if (it.isOrContainsObjectOrEnumDef()) {
cache[constructedType.getSimpleSlug()] = it
ReferenceDefinition(prop.returnType.getReferenceSlug())
} else {
it
return SchemaGenerator.fromTypeToSchema(constructedType, cache, schemaConfigurator, propEnrichment?.typeEnrichment)
.let {
if (it.isOrContainsObjectOrEnumDef()) {
cache[constructedType.getSlug(propEnrichment)] = it
ReferenceDefinition(prop.returnType.getReferenceSlug(propEnrichment))
} else {
it
}
}
}
}
private fun handleGenericProperty(
prop: KProperty<*>,
typeMap: Map<KTypeParameter, KTypeProjection>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator
schemaConfigurator: SchemaConfigurator,
propEnrichment: PropertyEnrichment?
): JsonSchema {
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 {
return SchemaGenerator.fromTypeToSchema(type, cache, schemaConfigurator, propEnrichment?.typeEnrichment).let {
if (it.isOrContainsObjectOrEnumDef()) {
cache[type.getSimpleSlug()] = it
ReferenceDefinition(type.getReferenceSlug())
cache[type.getSlug(propEnrichment)] = it
ReferenceDefinition(type.getReferenceSlug(propEnrichment))
} else {
it
}
@ -129,12 +145,13 @@ object SimpleObjectHandler {
private fun handleProperty(
prop: KProperty<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator
schemaConfigurator: SchemaConfigurator,
propEnrichment: TypeEnrichment<*>?
): JsonSchema =
SchemaGenerator.fromTypeToSchema(prop.returnType, cache, schemaConfigurator).let {
SchemaGenerator.fromTypeToSchema(prop.returnType, cache, schemaConfigurator, propEnrichment).let {
if (it.isOrContainsObjectOrEnumDef()) {
cache[prop.returnType.getSimpleSlug()] = it
ReferenceDefinition(prop.returnType.getReferenceSlug())
cache[prop.returnType.getSlug(propEnrichment)] = it
ReferenceDefinition(prop.returnType.getReferenceSlug(propEnrichment))
} else {
it
}
@ -149,4 +166,15 @@ object SimpleObjectHandler {
}
private fun JsonSchema.isNullable(): Boolean = this is OneOfDefinition && this.oneOf.any { it is NullableDefinition }
private fun PropertyEnrichment.applyToSchema(schema: JsonSchema): JsonSchema = when (schema) {
is AnyOfDefinition -> schema.copy(deprecated = deprecated, description = description)
is ArrayDefinition -> schema.copy(deprecated = deprecated, description = description)
is EnumDefinition -> schema.copy(deprecated = deprecated, description = description)
is MapDefinition -> schema.copy(deprecated = deprecated, description = description)
is NullableDefinition -> schema.copy(deprecated = deprecated, description = description)
is OneOfDefinition -> schema.copy(deprecated = deprecated, description = description)
is ReferenceDefinition -> schema.copy(deprecated = deprecated, description = description)
is TypeDefinition -> schema.copy(deprecated = deprecated, description = description)
}
}

View File

@ -1,5 +1,8 @@
package io.bkbn.kompendium.json.schema.util
import io.bkbn.kompendium.enrichment.Enrichment
import io.bkbn.kompendium.enrichment.PropertyEnrichment
import io.bkbn.kompendium.enrichment.TypeEnrichment
import kotlin.reflect.KClass
import kotlin.reflect.KType
@ -7,12 +10,26 @@ object Helpers {
private const val COMPONENT_SLUG = "#/components/schemas"
fun KType.getSlug(enrichment: Enrichment? = null) = when (enrichment) {
is TypeEnrichment<*> -> getEnrichedSlug(enrichment)
is PropertyEnrichment -> error("Slugs should not be generated for field enrichments")
null -> getSimpleSlug()
}
fun KType.getSimpleSlug(): String = when {
this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>)
else -> (classifier as KClass<*>).kompendiumSlug() ?: error("Could not determine simple name for $this")
}
fun KType.getReferenceSlug(): String = when {
private fun KType.getEnrichedSlug(enrichment: TypeEnrichment<*>) = getSimpleSlug() + "-${enrichment.id}"
fun KType.getReferenceSlug(enrichment: Enrichment? = null): String = when (enrichment) {
is TypeEnrichment<*> -> getSimpleReferenceSlug() + "-${enrichment.id}"
is PropertyEnrichment -> error("Reference slugs should never be generated for field enrichments")
null -> getSimpleReferenceSlug()
}
private fun KType.getSimpleReferenceSlug() = when {
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).kompendiumSlug()}"
}

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.NestedComplexItem
import io.bkbn.kompendium.core.fixtures.ObjectWithEnum
import io.bkbn.kompendium.core.fixtures.SerialNameObject
import io.bkbn.kompendium.core.fixtures.SimpleEnum
@ -11,12 +12,14 @@ 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.UnbackedObject
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.kotest.assertions.json.shouldEqualJson
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import kotlinx.serialization.json.Json
import java.util.UUID
import kotlin.reflect.typeOf
class SchemaGeneratorTest : DescribeSpec({
describe("Scalars") {
@ -88,7 +91,13 @@ class SchemaGeneratorTest : DescribeSpec({
jsonSchemaTest<Map<String, Int>>("T0012__scalar_map.json")
}
it("Throws an error when map keys are not strings") {
shouldThrow<IllegalArgumentException> { SchemaGenerator.fromTypeToSchema<Map<Int, Int>>() }
shouldThrow<IllegalArgumentException> {
SchemaGenerator.fromTypeToSchema(
typeOf<Map<Int, Int>>(),
cache = mutableMapOf(),
schemaConfigurator = KotlinXSchemaConfigurator()
)
}
}
it("Can generate the schema for a map of objects") {
jsonSchemaTest<Map<String, TestResponse>>("T0013__object_map.json")
@ -97,6 +106,36 @@ class SchemaGeneratorTest : DescribeSpec({
jsonSchemaTest<Map<String, Int>?>("T0014__nullable_map.json")
}
}
describe("Enrichment") {
it("Can attach an enrichment to a simple type") {
jsonSchemaTest<TestSimpleRequest>(
snapshotName = "T0022__enriched_simple_object.json",
enrichment = TypeEnrichment("simple") {
TestSimpleRequest::a {
description = "This is a simple description"
}
TestSimpleRequest::b {
deprecated = true
}
}
)
}
it("Can properly assign a reference to a nested enrichment") {
jsonSchemaTest<ComplexRequest>(
snapshotName = "T0023__enriched_nested_reference.json",
enrichment = TypeEnrichment("example") {
ComplexRequest::tables {
description = "Collection of important items"
typeEnrichment = TypeEnrichment("table") {
NestedComplexItem::name {
description = "The name of the table"
}
}
}
}
)
}
}
}) {
companion object {
private val json = Json {
@ -107,11 +146,14 @@ class SchemaGeneratorTest : DescribeSpec({
private fun JsonSchema.serialize() = json.encodeToString(JsonSchema.serializer(), this)
private inline fun <reified T> jsonSchemaTest(snapshotName: String) {
private inline fun <reified T> jsonSchemaTest(snapshotName: String, enrichment: TypeEnrichment<*>? = null) {
// act
val schema = SchemaGenerator.fromTypeToSchema<T>(schemaConfigurator = KotlinXSchemaConfigurator())
// todo add cache assertions!!!
val schema = SchemaGenerator.fromTypeToSchema(
type = typeOf<T>(),
cache = mutableMapOf(),
schemaConfigurator = KotlinXSchemaConfigurator(),
enrichment = enrichment,
)
// assert
schema.serialize() shouldEqualJson getFileSnapshot(snapshotName)

View File

@ -0,0 +1,18 @@
{
"type": "object",
"properties": {
"a": {
"type": "string",
"description": "This is a simple description"
},
"b": {
"type": "number",
"format": "int32",
"deprecated": true
}
},
"required": [
"a",
"b"
]
}

View File

@ -0,0 +1,23 @@
{
"type": "object",
"properties": {
"amazingField": {
"type": "string"
},
"org": {
"type": "string"
},
"tables": {
"items": {
"$ref": "#/components/schemas/NestedComplexItem-table"
},
"description": "Collection of important items",
"type": "array"
}
},
"required": [
"amazingField",
"org",
"tables"
]
}