feat: enriched enrichments (#566)

This commit is contained in:
Ryan Brink
2024-01-21 14:15:21 -05:00
committed by GitHub
parent c7545b1072
commit ac464ea9ca
49 changed files with 1371 additions and 724 deletions

View File

@ -26,7 +26,7 @@ class KotlinXSchemaConfigurator : SchemaConfigurator {
.filterIsInstance<SerialName>()
.firstOrNull()?.value ?: property.name
override fun sealedTypeEnrichment(
override fun sealedObjectEnrichment(
implementationType: KType,
implementationSchema: JsonSchema,
): JsonSchema {

View File

@ -9,7 +9,7 @@ interface SchemaConfigurator {
fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>>
fun serializableName(property: KProperty1<out Any, *>): String
fun sealedTypeEnrichment(
fun sealedObjectEnrichment(
implementationType: KType,
implementationSchema: JsonSchema
): JsonSchema

View File

@ -1,5 +1,9 @@
package io.bkbn.kompendium.json.schema
import io.bkbn.kompendium.enrichment.CollectionEnrichment
import io.bkbn.kompendium.enrichment.Enrichment
import io.bkbn.kompendium.enrichment.MapEnrichment
import io.bkbn.kompendium.enrichment.ObjectEnrichment
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
@ -23,7 +27,7 @@ object SchemaGenerator {
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null
enrichment: Enrichment? = null
): JsonSchema {
val slug = type.getSlug(enrichment)
@ -47,18 +51,7 @@ object SchemaGenerator {
String::class -> checkForNull(type, TypeDefinition.STRING)
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
UUID::class -> checkForNull(type, TypeDefinition.UUID)
else -> when {
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, enrichment)
} else {
SimpleObjectHandler.handle(type, clazz, cache, schemaConfigurator, enrichment)
}
}
}
else -> complexTypeToSchema(clazz, type, cache, schemaConfigurator, enrichment as? TypeEnrichment<*>?)
}
}
@ -77,4 +70,59 @@ object SchemaGenerator {
true -> OneOfDefinition(NullableDefinition(), schema)
false -> schema
}
private fun complexTypeToSchema(
clazz: KClass<*>,
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: Enrichment? = null
): JsonSchema = when {
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz, cache)
clazz.isSubclassOf(Collection::class) -> handleCollection(type, cache, schemaConfigurator, enrichment)
clazz.isSubclassOf(Map::class) -> handleMap(type, cache, schemaConfigurator, enrichment)
else -> handleObject(type, clazz, cache, schemaConfigurator, enrichment)
}
private fun handleCollection(
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: Enrichment?
) = when (enrichment) {
is CollectionEnrichment<*> -> CollectionHandler.handle(type, cache, schemaConfigurator, enrichment)
null -> CollectionHandler.handle(type, cache, schemaConfigurator, null)
else -> error("Incorrect enrichment type for enrichment id: ${enrichment.id}")
}
private fun handleMap(
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: Enrichment?
) = when (enrichment) {
is MapEnrichment<*> -> MapHandler.handle(type, cache, schemaConfigurator, enrichment)
null -> MapHandler.handle(type, cache, schemaConfigurator, null)
else -> error("Incorrect enrichment type for enrichment id: ${enrichment.id}")
}
private fun handleObject(
type: KType,
clazz: KClass<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: Enrichment?
) = when (clazz.isSealed) {
true -> when (enrichment) {
is ObjectEnrichment<*> -> SealedObjectHandler.handle(type, clazz, cache, schemaConfigurator, enrichment)
null -> SealedObjectHandler.handle(type, clazz, cache, schemaConfigurator, null)
else -> error("Incorrect enrichment type for enrichment id: ${enrichment.id}")
}
false -> when (enrichment) {
is ObjectEnrichment<*> -> SimpleObjectHandler.handle(type, clazz, cache, schemaConfigurator, enrichment)
null -> SimpleObjectHandler.handle(type, clazz, cache, schemaConfigurator, null)
else -> error("Incorrect enrichment type for enrichment id: ${enrichment.id}")
}
}
}

View File

@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable
@Serializable
data class MapDefinition(
val additionalProperties: JsonSchema,
val maxProperties: Int? = null,
val minProperties: Int? = null,
override val deprecated: Boolean? = null,
override val description: String? = null,
) : JsonSchema {

View File

@ -1,6 +1,6 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.enrichment.CollectionEnrichment
import io.bkbn.kompendium.json.schema.SchemaConfigurator
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.ArrayDefinition
@ -19,18 +19,23 @@ object CollectionHandler {
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null
enrichment: CollectionEnrichment<*>? = null
): JsonSchema {
require(enrichment is CollectionEnrichment<*> || enrichment == null) {
"Enrichment for collection must be either null or a CollectionEnrichment"
}
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, enrichment).let {
if ((it is TypeDefinition && it.type == "object") || it is EnumDefinition) {
cache[collectionType.getSlug(enrichment)] = it
ReferenceDefinition(collectionType.getReferenceSlug(enrichment))
} else {
it
val typeSchema =
SchemaGenerator.fromTypeToSchema(collectionType, cache, schemaConfigurator, enrichment?.itemEnrichment).let {
if ((it is TypeDefinition && it.type == "object") || it is EnumDefinition) {
cache[collectionType.getSlug(enrichment)] = it
ReferenceDefinition(collectionType.getReferenceSlug(enrichment))
} else {
it
}
}
}
val definition = ArrayDefinition(typeSchema)
return when (type.isMarkedNullable) {
true -> OneOfDefinition(NullableDefinition(), definition)

View File

@ -0,0 +1,94 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.CollectionEnrichment
import io.bkbn.kompendium.enrichment.Enrichment
import io.bkbn.kompendium.enrichment.MapEnrichment
import io.bkbn.kompendium.enrichment.NumberEnrichment
import io.bkbn.kompendium.enrichment.ObjectEnrichment
import io.bkbn.kompendium.enrichment.StringEnrichment
import io.bkbn.kompendium.json.schema.definition.ArrayDefinition
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.bkbn.kompendium.json.schema.definition.MapDefinition
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
object EnrichmentHandler {
fun Enrichment.applyToSchema(schema: JsonSchema): JsonSchema = when (this) {
is NumberEnrichment -> applyToSchema(schema)
is StringEnrichment -> applyToSchema(schema)
is CollectionEnrichment<*> -> applyToSchema(schema)
is MapEnrichment<*> -> applyToSchema(schema)
is ObjectEnrichment<*> -> applyToSchema(schema)
else -> error("Incorrect enrichment type for enrichment id: ${this.id}")
}
private fun ObjectEnrichment<*>.applyToSchema(schema: JsonSchema): JsonSchema = when (schema) {
is TypeDefinition -> schema.copy(deprecated = deprecated, description = description)
is ReferenceDefinition -> schema.copy(deprecated = deprecated, description = description)
else -> error("Incorrect enrichment type for enrichment id: ${this.id}")
}
private fun MapEnrichment<*>.applyToSchema(schema: JsonSchema): JsonSchema = when (schema) {
is MapDefinition -> schema.copyMapEnrichment(this)
else -> error("Incorrect enrichment type for enrichment id: ${this.id}")
}
private fun CollectionEnrichment<*>.applyToSchema(schema: JsonSchema): JsonSchema = when (schema) {
is ArrayDefinition -> schema.copyArrayEnrichment(this)
else -> error("Incorrect enrichment type for enrichment id: ${this.id}")
}
private fun NumberEnrichment.applyToSchema(schema: JsonSchema): JsonSchema = when (schema) {
is TypeDefinition -> schema.copyNumberEnrichment(this)
else -> error("Incorrect enrichment type for enrichment id: ${this.id}")
}
private fun StringEnrichment.applyToSchema(schema: JsonSchema): JsonSchema = when (schema) {
is TypeDefinition -> schema.copyStringEnrichment(this)
else -> error("Incorrect enrichment type for enrichment id: ${this.id}")
}
private fun TypeDefinition.copyNumberEnrichment(
enrichment: NumberEnrichment
): TypeDefinition = copy(
deprecated = enrichment.deprecated,
description = enrichment.description,
multipleOf = enrichment.multipleOf,
maximum = enrichment.maximum,
exclusiveMaximum = enrichment.exclusiveMaximum,
minimum = enrichment.minimum,
exclusiveMinimum = enrichment.exclusiveMinimum,
)
private fun TypeDefinition.copyStringEnrichment(
enrichment: StringEnrichment
): TypeDefinition = copy(
deprecated = enrichment.deprecated,
description = enrichment.description,
maxLength = enrichment.maxLength,
minLength = enrichment.minLength,
pattern = enrichment.pattern,
contentEncoding = enrichment.contentEncoding,
contentMediaType = enrichment.contentMediaType,
)
private fun ArrayDefinition.copyArrayEnrichment(
enrichment: CollectionEnrichment<*>
): ArrayDefinition = copy(
deprecated = enrichment.deprecated,
description = enrichment.description,
minItems = enrichment.minItems,
maxItems = enrichment.maxItems,
uniqueItems = enrichment.uniqueItems,
)
private fun MapDefinition.copyMapEnrichment(
enrichment: MapEnrichment<*>
): MapDefinition = copy(
deprecated = enrichment.deprecated,
description = enrichment.description,
minProperties = enrichment.minProperties,
maxProperties = enrichment.maxProperties,
)
}

View File

@ -1,6 +1,5 @@
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
@ -14,9 +13,8 @@ object EnumHandler {
type: KType,
clazz: KClass<*>,
cache: MutableMap<String, JsonSchema>,
enrichment: TypeEnrichment<*>? = null
): JsonSchema {
cache[type.getSlug(enrichment)] = ReferenceDefinition(type.getReferenceSlug(enrichment))
cache[type.getSlug()] = ReferenceDefinition(type.getReferenceSlug())
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
return EnumDefinition(enum = options)

View File

@ -1,6 +1,6 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.enrichment.MapEnrichment
import io.bkbn.kompendium.json.schema.SchemaConfigurator
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.JsonSchema
@ -20,20 +20,24 @@ object MapHandler {
type: KType,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null
enrichment: MapEnrichment<*>? = null
): JsonSchema {
require(enrichment is MapEnrichment<*> || enrichment == null) {
"Enrichment for map must be either null or a MapEnrichment"
}
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, enrichment).let {
if (it is TypeDefinition && it.type == "object") {
cache[valueType.getSlug(enrichment)] = it
ReferenceDefinition(valueType.getReferenceSlug(enrichment))
} else {
it
val valueSchema =
SchemaGenerator.fromTypeToSchema(valueType, cache, schemaConfigurator, enrichment?.valueEnrichment).let {
if (it is TypeDefinition && it.type == "object") {
cache[valueType.getSlug(enrichment)] = it
ReferenceDefinition(valueType.getReferenceSlug(enrichment))
} else {
it
}
}
}
val definition = MapDefinition(valueSchema)
return when (type.isMarkedNullable) {
true -> OneOfDefinition(NullableDefinition(), definition)

View File

@ -1,6 +1,6 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.enrichment.ObjectEnrichment
import io.bkbn.kompendium.json.schema.SchemaConfigurator
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.AnyOfDefinition
@ -20,14 +20,14 @@ object SealedObjectHandler {
clazz: KClass<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>? = null,
enrichment: ObjectEnrichment<*>? = null,
): JsonSchema {
val subclasses = clazz.sealedSubclasses
.map { it.createType(type.arguments) }
.map { t ->
SchemaGenerator.fromTypeToSchema(t, cache, schemaConfigurator, enrichment)
.let {
schemaConfigurator.sealedTypeEnrichment(t, it)
schemaConfigurator.sealedObjectEnrichment(t, it)
}.let { js ->
if (js is TypeDefinition && js.type == "object") {
val slug = t.getSlug(enrichment)

View File

@ -1,19 +1,17 @@
package io.bkbn.kompendium.json.schema.handler
import io.bkbn.kompendium.enrichment.PropertyEnrichment
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.enrichment.Enrichment
import io.bkbn.kompendium.enrichment.ObjectEnrichment
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.handler.EnrichmentHandler.applyToSchema
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSlug
import kotlin.reflect.KClass
@ -32,28 +30,31 @@ object SimpleObjectHandler {
clazz: KClass<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
enrichment: TypeEnrichment<*>?,
enrichment: ObjectEnrichment<*>?,
): JsonSchema {
cache[type.getSlug(enrichment)] = ReferenceDefinition(type.getReferenceSlug(enrichment))
require(enrichment is ObjectEnrichment<*> || enrichment == null) {
"Enrichment for object must either be of type ObjectEnrichment or null"
}
val slug = type.getSlug(enrichment)
val referenceSlug = type.getReferenceSlug(enrichment)
cache[slug] = ReferenceDefinition(referenceSlug)
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 propEnrichment = enrichment?.propertyEnrichment?.get(prop)
val schema = when (prop.needsToInjectGenerics(typeMap)) {
true -> handleNestedGenerics(typeMap, prop, cache, schemaConfigurator, propTypeEnrichment)
true -> handleNestedGenerics(typeMap, prop, cache, schemaConfigurator, propEnrichment)
false -> when (typeMap.containsKey(prop.returnType.classifier)) {
true -> handleGenericProperty(prop, typeMap, cache, schemaConfigurator, propTypeEnrichment)
false -> handleProperty(prop, cache, schemaConfigurator, propTypeEnrichment?.typeEnrichment)
true -> handleGenericProperty(prop, typeMap, cache, schemaConfigurator, propEnrichment)
false -> handleProperty(prop, cache, schemaConfigurator, propEnrichment)
}
}
val enrichedSchema = propTypeEnrichment?.applyToSchema(schema) ?: schema
val enrichedSchema = propEnrichment?.applyToSchema(schema) ?: schema
val nullCheckSchema = when (prop.returnType.isMarkedNullable && !enrichedSchema.isNullable()) {
true -> OneOfDefinition(NullableDefinition(), enrichedSchema)
@ -84,11 +85,14 @@ object SimpleObjectHandler {
.map { schemaConfigurator.serializableName(it) }
.toSet()
return TypeDefinition(
val definition = TypeDefinition(
type = "object",
properties = props,
required = required
)
cache[slug] = definition
return definition
}
private fun KProperty<*>.needsToInjectGenerics(
@ -103,7 +107,7 @@ object SimpleObjectHandler {
prop: KProperty<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
propEnrichment: PropertyEnrichment?
propEnrichment: Enrichment?
): JsonSchema {
val propClass = prop.returnType.classifier as KClass<*>
val types = prop.returnType.arguments.map {
@ -111,7 +115,7 @@ object SimpleObjectHandler {
typeMap.filterKeys { k -> k.name == typeSymbol }.values.first()
}
val constructedType = propClass.createType(types)
return SchemaGenerator.fromTypeToSchema(constructedType, cache, schemaConfigurator, propEnrichment?.typeEnrichment)
return SchemaGenerator.fromTypeToSchema(constructedType, cache, schemaConfigurator, propEnrichment)
.let {
if (it.isOrContainsObjectOrEnumDef()) {
cache[constructedType.getSlug(propEnrichment)] = it
@ -127,14 +131,14 @@ object SimpleObjectHandler {
typeMap: Map<KTypeParameter, KTypeProjection>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
propEnrichment: PropertyEnrichment?
propEnrichment: Enrichment?
): 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, propEnrichment?.typeEnrichment).let {
return SchemaGenerator.fromTypeToSchema(type, cache, schemaConfigurator, propEnrichment).let {
if (it.isOrContainsObjectOrEnumDef()) {
cache[type.getSlug(propEnrichment?.typeEnrichment)] = it
ReferenceDefinition(type.getReferenceSlug(propEnrichment?.typeEnrichment))
cache[type.getSlug(propEnrichment)] = it
ReferenceDefinition(type.getReferenceSlug(propEnrichment))
} else {
it
}
@ -145,7 +149,7 @@ object SimpleObjectHandler {
prop: KProperty<*>,
cache: MutableMap<String, JsonSchema>,
schemaConfigurator: SchemaConfigurator,
propEnrichment: TypeEnrichment<*>?
propEnrichment: Enrichment?
): JsonSchema =
SchemaGenerator.fromTypeToSchema(prop.returnType, cache, schemaConfigurator, propEnrichment).let {
if (it.isOrContainsObjectOrEnumDef()) {
@ -165,37 +169,4 @@ 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,
minItems = minItems,
maxItems = maxItems,
uniqueItems = uniqueItems,
)
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,
multipleOf = multipleOf,
maximum = maximum,
exclusiveMaximum = exclusiveMaximum,
minimum = minimum,
exclusiveMinimum = exclusiveMinimum,
maxLength = maxLength,
minLength = minLength,
pattern = pattern,
contentEncoding = contentEncoding,
contentMediaType = contentMediaType,
maxProperties = maxProperties,
minProperties = minProperties,
)
}
}

View File

@ -1,8 +1,6 @@
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
@ -11,9 +9,8 @@ object Helpers {
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")
else -> getSimpleSlug()
null -> getSimpleSlug()
else -> getEnrichedSlug(enrichment)
}
fun KType.getSimpleSlug(): String = when {
@ -21,12 +18,11 @@ object Helpers {
else -> (classifier as KClass<*>).kompendiumSlug() ?: error("Could not determine simple name for $this")
}
private fun KType.getEnrichedSlug(enrichment: TypeEnrichment<*>) = getSimpleSlug() + "-${enrichment.id}"
private fun KType.getEnrichedSlug(enrichment: Enrichment) = 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")
else -> getSimpleReferenceSlug()
null -> getSimpleReferenceSlug()
else -> getSimpleReferenceSlug() + "-${enrichment.id}"
}
private fun KType.getSimpleReferenceSlug() = when {

View File

@ -13,7 +13,10 @@ import io.bkbn.kompendium.core.fixtures.TransientObject
import io.bkbn.kompendium.core.fixtures.UnbackedObject
import io.bkbn.kompendium.core.fixtures.GenericObject
import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot
import io.bkbn.kompendium.enrichment.TypeEnrichment
import io.bkbn.kompendium.enrichment.CollectionEnrichment
import io.bkbn.kompendium.enrichment.NumberEnrichment
import io.bkbn.kompendium.enrichment.ObjectEnrichment
import io.bkbn.kompendium.enrichment.StringEnrichment
import io.bkbn.kompendium.json.schema.definition.JsonSchema
import io.kotest.assertions.json.shouldEqualJson
import io.kotest.assertions.throwables.shouldThrow
@ -114,12 +117,16 @@ class SchemaGeneratorTest : DescribeSpec({
it("Can attach an enrichment to a simple type") {
jsonSchemaTest<TestSimpleRequest>(
snapshotName = "T0022__enriched_simple_object.json",
enrichment = TypeEnrichment("simple") {
enrichment = ObjectEnrichment("simple") {
TestSimpleRequest::a {
description = "This is a simple description"
StringEnrichment("blah") {
description = "This is a simple description"
}
}
TestSimpleRequest::b {
deprecated = true
NumberEnrichment("bla") {
deprecated = true
}
}
}
)
@ -127,12 +134,16 @@ class SchemaGeneratorTest : DescribeSpec({
it("Can properly assign a reference to a nested enrichment") {
jsonSchemaTest<ComplexRequest>(
snapshotName = "T0023__enriched_nested_reference.json",
enrichment = TypeEnrichment("example") {
enrichment = ObjectEnrichment("example") {
ComplexRequest::tables {
description = "Collection of important items"
typeEnrichment = TypeEnrichment("table") {
NestedComplexItem::name {
description = "The name of the table"
CollectionEnrichment<List<NestedComplexItem>>("tables") {
description = "Collection of important items"
itemEnrichment = ObjectEnrichment("table") {
NestedComplexItem::name {
StringEnrichment("name") {
description = "The name of the table"
}
}
}
}
}
@ -142,15 +153,19 @@ class SchemaGeneratorTest : DescribeSpec({
it("Can properly assign a reference to a generic object") {
jsonSchemaTest<GenericObject<TestSimpleRequest>>(
snapshotName = "T0025__enrichment_generic_object.json",
enrichment = TypeEnrichment("generic") {
enrichment = ObjectEnrichment("generic") {
GenericObject<TestSimpleRequest>::data {
description = "This is a generic param"
typeEnrichment = TypeEnrichment("simple") {
ObjectEnrichment<TestSimpleRequest>("blob") {
description = "This is a generic object"
TestSimpleRequest::a {
description = "This is a simple description"
StringEnrichment("blah") {
description = "This is a simple description"
}
}
TestSimpleRequest::b {
deprecated = true
NumberEnrichment("bla") {
deprecated = true
}
}
}
}
@ -168,7 +183,7 @@ class SchemaGeneratorTest : DescribeSpec({
private fun JsonSchema.serialize() = json.encodeToString(JsonSchema.serializer(), this)
private inline fun <reified T> jsonSchemaTest(snapshotName: String, enrichment: TypeEnrichment<*>? = null) {
private inline fun <reified T> jsonSchemaTest(snapshotName: String, enrichment: ObjectEnrichment<*>? = null) {
// act
val schema = SchemaGenerator.fromTypeToSchema(
type = typeOf<T>(),

View File

@ -9,7 +9,7 @@
},
"tables": {
"items": {
"$ref": "#/components/schemas/NestedComplexItem-table"
"$ref": "#/components/schemas/NestedComplexItem-tables"
},
"description": "Collection of important items",
"type": "array"

View File

@ -2,8 +2,8 @@
"type": "object",
"properties": {
"data": {
"description": "This is a generic param",
"$ref": "#/components/schemas/TestSimpleRequest-simple"
"$ref": "#/components/schemas/TestSimpleRequest-blob",
"description": "This is a generic object"
}
},
"required": [