breaking change: V3 alpha (#256)
This commit is contained in:
32
json-schema/build.gradle.kts
Normal file
32
json-schema/build.gradle.kts
Normal file
@ -0,0 +1,32 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.serialization")
|
||||
id("io.bkbn.sourdough.library.jvm")
|
||||
id("io.gitlab.arturbosch.detekt")
|
||||
id("com.adarshr.test-logger")
|
||||
id("org.jetbrains.dokka")
|
||||
id("maven-publish")
|
||||
id("java-library")
|
||||
id("signing")
|
||||
}
|
||||
|
||||
sourdoughLibrary {
|
||||
libraryName.set("Kompendium JSON Schema")
|
||||
libraryDescription.set("Json Schema Generator")
|
||||
compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn"))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.10")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")
|
||||
|
||||
testImplementation(testFixtures(projects.kompendiumCore))
|
||||
}
|
||||
|
||||
testing {
|
||||
suites {
|
||||
named("test", JvmTestSuite::class) {
|
||||
useJUnitJupiter()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package io.bkbn.kompendium.json.schema
|
||||
|
||||
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.TypeDefinition
|
||||
import io.bkbn.kompendium.json.schema.handler.CollectionHandler
|
||||
import io.bkbn.kompendium.json.schema.handler.EnumHandler
|
||||
import io.bkbn.kompendium.json.schema.handler.MapHandler
|
||||
import io.bkbn.kompendium.json.schema.handler.SimpleObjectHandler
|
||||
import io.bkbn.kompendium.json.schema.handler.SealedObjectHandler
|
||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
object SchemaGenerator {
|
||||
inline fun <reified T : Any?> fromTypeToSchema(cache: MutableMap<String, JsonSchema> = mutableMapOf()) =
|
||||
fromTypeToSchema(typeOf<T>(), cache)
|
||||
|
||||
fun fromTypeToSchema(type: KType, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
||||
cache[type.getSimpleSlug()]?.let {
|
||||
return it
|
||||
}
|
||||
return when (val clazz = type.classifier as KClass<*>) {
|
||||
Unit::class -> error(
|
||||
"""
|
||||
Unit cannot be converted to JsonSchema.
|
||||
If you are looking for a method will return null when called with Unit,
|
||||
please call SchemaGenerator.fromTypeOrUnit()
|
||||
""".trimIndent()
|
||||
)
|
||||
Int::class -> checkForNull(type, TypeDefinition.INT)
|
||||
Long::class -> checkForNull(type, TypeDefinition.LONG)
|
||||
Double::class -> checkForNull(type, TypeDefinition.DOUBLE)
|
||||
Float::class -> checkForNull(type, TypeDefinition.FLOAT)
|
||||
String::class -> checkForNull(type, TypeDefinition.STRING)
|
||||
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
|
||||
else -> when {
|
||||
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz)
|
||||
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache)
|
||||
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache)
|
||||
else -> {
|
||||
if (clazz.isSealed) {
|
||||
SealedObjectHandler.handle(type, clazz, cache)
|
||||
} else {
|
||||
SimpleObjectHandler.handle(type, clazz, cache)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fromTypeOrUnit(type: KType, cache: MutableMap<String, JsonSchema> = mutableMapOf()): JsonSchema? =
|
||||
when (type.classifier as KClass<*>) {
|
||||
Unit::class -> null
|
||||
else -> fromTypeToSchema(type, cache)
|
||||
}
|
||||
|
||||
private fun checkForNull(type: KType, schema: JsonSchema): JsonSchema = when (type.isMarkedNullable) {
|
||||
true -> OneOfDefinition(NullableDefinition(), schema)
|
||||
false -> schema
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AnyOfDefinition(val anyOf: Set<JsonSchema>) : JsonSchema
|
@ -0,0 +1,10 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ArrayDefinition(
|
||||
val items: JsonSchema
|
||||
) : JsonSchema {
|
||||
val type: String = "array"
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class EnumDefinition(
|
||||
val enum: Set<String>
|
||||
) : JsonSchema
|
@ -0,0 +1,33 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
@Serializable(with = JsonSchema.Serializer::class)
|
||||
sealed interface JsonSchema {
|
||||
|
||||
object Serializer : KSerializer<JsonSchema> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("JsonSchema", PrimitiveKind.STRING)
|
||||
override fun deserialize(decoder: Decoder): JsonSchema {
|
||||
error("Abandon all hope ye who enter 💀")
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: JsonSchema) {
|
||||
when (value) {
|
||||
is ReferenceDefinition -> ReferenceDefinition.serializer().serialize(encoder, value)
|
||||
is TypeDefinition -> TypeDefinition.serializer().serialize(encoder, value)
|
||||
is EnumDefinition -> EnumDefinition.serializer().serialize(encoder, value)
|
||||
is ArrayDefinition -> ArrayDefinition.serializer().serialize(encoder, value)
|
||||
is MapDefinition -> MapDefinition.serializer().serialize(encoder, value)
|
||||
is NullableDefinition -> NullableDefinition.serializer().serialize(encoder, value)
|
||||
is OneOfDefinition -> OneOfDefinition.serializer().serialize(encoder, value)
|
||||
is AnyOfDefinition -> AnyOfDefinition.serializer().serialize(encoder, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MapDefinition(
|
||||
val additionalProperties: JsonSchema
|
||||
) : JsonSchema {
|
||||
val type: String = "object"
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class NullableDefinition(val type: String = "null") : JsonSchema
|
@ -0,0 +1,8 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OneOfDefinition(val oneOf: Set<JsonSchema>) : JsonSchema {
|
||||
constructor(vararg types: JsonSchema) : this(types.toSet())
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ReferenceDefinition(val `$ref`: String) : JsonSchema
|
@ -0,0 +1,47 @@
|
||||
package io.bkbn.kompendium.json.schema.definition
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@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,
|
||||
) : JsonSchema {
|
||||
|
||||
fun withDefault(default: Any): TypeDefinition = this.copy(default = default)
|
||||
|
||||
companion object {
|
||||
val INT = TypeDefinition(
|
||||
type = "number",
|
||||
format = "int32"
|
||||
)
|
||||
|
||||
val LONG = TypeDefinition(
|
||||
type = "number",
|
||||
format = "int64"
|
||||
)
|
||||
|
||||
val DOUBLE = TypeDefinition(
|
||||
type = "number",
|
||||
format = "double"
|
||||
)
|
||||
|
||||
val FLOAT = TypeDefinition(
|
||||
type = "number",
|
||||
format = "float"
|
||||
)
|
||||
|
||||
val STRING = TypeDefinition(
|
||||
type = "string"
|
||||
)
|
||||
|
||||
val BOOLEAN = TypeDefinition(
|
||||
type = "boolean"
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package io.bkbn.kompendium.json.schema.handler
|
||||
|
||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||
import io.bkbn.kompendium.json.schema.definition.ArrayDefinition
|
||||
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.definition.TypeDefinition
|
||||
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
|
||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||
import kotlin.reflect.KType
|
||||
|
||||
object CollectionHandler {
|
||||
|
||||
fun handle(type: KType, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
||||
val collectionType = type.arguments.first().type!!
|
||||
val typeSchema = SchemaGenerator.fromTypeToSchema(collectionType, cache).let {
|
||||
if (it is TypeDefinition && it.type == "object") {
|
||||
cache[collectionType.getSimpleSlug()] = it
|
||||
ReferenceDefinition(collectionType.getReferenceSlug())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
val definition = ArrayDefinition(typeSchema)
|
||||
return when (type.isMarkedNullable) {
|
||||
true -> OneOfDefinition(NullableDefinition(), definition)
|
||||
false -> definition
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
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 kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
object EnumHandler {
|
||||
|
||||
fun handle(type: KType, clazz: KClass<*>): JsonSchema {
|
||||
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
|
||||
val definition = EnumDefinition(enum = options)
|
||||
return when (type.isMarkedNullable) {
|
||||
true -> OneOfDefinition(NullableDefinition(), definition)
|
||||
false -> definition
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package io.bkbn.kompendium.json.schema.handler
|
||||
|
||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||
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.util.Helpers.getReferenceSlug
|
||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
object MapHandler {
|
||||
|
||||
fun handle(type: KType, cache: MutableMap<String, JsonSchema>): 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!!
|
||||
val valueSchema = SchemaGenerator.fromTypeToSchema(valueType, cache).let {
|
||||
if (it is TypeDefinition && it.type == "object") {
|
||||
cache[valueType.getSimpleSlug()] = it
|
||||
ReferenceDefinition(valueType.getReferenceSlug())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
val definition = MapDefinition(valueSchema)
|
||||
return when (type.isMarkedNullable) {
|
||||
true -> OneOfDefinition(NullableDefinition(), definition)
|
||||
false -> definition
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package io.bkbn.kompendium.json.schema.handler
|
||||
|
||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||
import io.bkbn.kompendium.json.schema.definition.AnyOfDefinition
|
||||
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 kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.createType
|
||||
|
||||
object SealedObjectHandler {
|
||||
|
||||
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
||||
val subclasses = clazz.sealedSubclasses
|
||||
.map { it.createType(type.arguments) }
|
||||
.map { t ->
|
||||
SchemaGenerator.fromTypeToSchema(t, cache).let { js ->
|
||||
if (js is TypeDefinition && js.type == "object") {
|
||||
cache[t.getSimpleSlug()] = js
|
||||
ReferenceDefinition(t.getReferenceSlug())
|
||||
} else {
|
||||
js
|
||||
}
|
||||
}
|
||||
}
|
||||
.toSet()
|
||||
return AnyOfDefinition(subclasses)
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package io.bkbn.kompendium.json.schema.handler
|
||||
|
||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||
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.definition.TypeDefinition
|
||||
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.KProperty
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.KTypeParameter
|
||||
import kotlin.reflect.KTypeProjection
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
|
||||
object SimpleObjectHandler {
|
||||
|
||||
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
||||
// cache[type.getSimpleSlug()] = ReferenceDefinition("RECURSION_PLACEHOLDER")
|
||||
val typeMap = clazz.typeParameters.zip(type.arguments).toMap()
|
||||
val props = clazz.memberProperties.associate { prop ->
|
||||
val schema = when (typeMap.containsKey(prop.returnType.classifier)) {
|
||||
true -> handleGenericProperty(prop, typeMap, cache)
|
||||
false -> handleProperty(prop, cache)
|
||||
}
|
||||
|
||||
prop.name to schema
|
||||
}
|
||||
|
||||
val required = clazz.memberProperties.filterNot { prop -> prop.returnType.isMarkedNullable }
|
||||
.filterNot { prop -> clazz.primaryConstructor!!.parameters.find { it.name == prop.name }!!.isOptional }
|
||||
.map { it.name }
|
||||
.toSet()
|
||||
|
||||
|
||||
val definition = TypeDefinition(
|
||||
type = "object",
|
||||
properties = props,
|
||||
required = required
|
||||
)
|
||||
|
||||
return when (type.isMarkedNullable) {
|
||||
true -> OneOfDefinition(NullableDefinition(), definition)
|
||||
false -> definition
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGenericProperty(
|
||||
prop: KProperty<*>,
|
||||
typeMap: Map<KTypeParameter, KTypeProjection>,
|
||||
cache: MutableMap<String, JsonSchema>
|
||||
): JsonSchema {
|
||||
val type = typeMap[prop.returnType.classifier]?.type!!
|
||||
return SchemaGenerator.fromTypeToSchema(type, cache).let {
|
||||
if (it is TypeDefinition && it.type == "object") {
|
||||
cache[type.getSimpleSlug()] = it
|
||||
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleProperty(prop: KProperty<*>, cache: MutableMap<String, JsonSchema>): JsonSchema =
|
||||
SchemaGenerator.fromTypeToSchema(prop.returnType, cache).let {
|
||||
if (it is TypeDefinition && it.type == "object") {
|
||||
cache[prop.returnType.getSimpleSlug()] = it
|
||||
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package io.bkbn.kompendium.json.schema.util
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
object Helpers {
|
||||
|
||||
private const val COMPONENT_SLUG = "#/components/schemas"
|
||||
|
||||
fun KType.getSimpleSlug(): String = when {
|
||||
this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>)
|
||||
else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this")
|
||||
}
|
||||
|
||||
fun KType.getReferenceSlug(): String = when {
|
||||
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
|
||||
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}"
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts a class with type parameters into a reference friendly string
|
||||
*/
|
||||
private fun genericNameAdapter(type: KType, clazz: KClass<*>): String {
|
||||
val classNames = type.arguments
|
||||
.map { it.type?.classifier as KClass<*> }
|
||||
.map { it.simpleName }
|
||||
return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-")
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package io.bkbn.kompendium.json.schema.util
|
||||
|
||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||
import kotlin.reflect.KType
|
||||
|
||||
data class ReferenceCache(
|
||||
val referenceRootPath: String = "#/",
|
||||
val cache: MutableMap<KType, JsonSchema> = mutableMapOf()
|
||||
)
|
@ -0,0 +1,98 @@
|
||||
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.SimpleEnum
|
||||
import io.bkbn.kompendium.core.fixtures.SlammaJamma
|
||||
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.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
|
||||
|
||||
class SchemaGeneratorTest : DescribeSpec({
|
||||
describe("Scalars") {
|
||||
it("Can generate the schema for an Int") {
|
||||
jsonSchemaTest<Int>("T0001__scalar_int.json")
|
||||
}
|
||||
it("Can generate the schema for a Boolean") {
|
||||
jsonSchemaTest<Boolean>("T0002__scalar_bool.json")
|
||||
}
|
||||
it("Can generate the schema for a String") {
|
||||
jsonSchemaTest<String>("T0003__scalar_string.json")
|
||||
}
|
||||
}
|
||||
describe("Objects") {
|
||||
it("Can generate the schema for a simple object") {
|
||||
jsonSchemaTest<TestSimpleRequest>("T0004__simple_object.json")
|
||||
}
|
||||
it("Can generate the schema for a complex object") {
|
||||
jsonSchemaTest<ComplexRequest>("T0005__complex_object.json")
|
||||
}
|
||||
it("Can generate the schema for a nullable object") {
|
||||
jsonSchemaTest<TestSimpleRequest?>("T0006__nullable_object.json")
|
||||
}
|
||||
it("Can generate the schema for a polymorphic object") {
|
||||
jsonSchemaTest<FlibbityGibbit>("T0015__polymorphic_object.json")
|
||||
}
|
||||
xit("Can generate the schema for a recursive type") {
|
||||
// TODO jsonSchemaTest<SlammaJamma>("T0016__recursive_object.json")
|
||||
}
|
||||
}
|
||||
describe("Enums") {
|
||||
it("Can generate the schema for a simple enum") {
|
||||
jsonSchemaTest<SimpleEnum>("T0007__simple_enum.json")
|
||||
}
|
||||
it("Can generate the schema for a nullable enum") {
|
||||
jsonSchemaTest<SimpleEnum?>("T0008__nullable_enum.json")
|
||||
}
|
||||
}
|
||||
describe("Arrays") {
|
||||
it("Can generate the schema for an array of scalars") {
|
||||
jsonSchemaTest<List<Int>>("T0009__scalar_array.json")
|
||||
}
|
||||
it("Can generate the schema for an array of objects") {
|
||||
jsonSchemaTest<List<TestResponse>>("T0010__object_array.json")
|
||||
}
|
||||
it("Can generate the schema for a nullable array") {
|
||||
jsonSchemaTest<List<Int>?>("T0011__nullable_array.json")
|
||||
}
|
||||
}
|
||||
describe("Maps") {
|
||||
it("Can generate the schema for a map of scalars") {
|
||||
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>>() }
|
||||
}
|
||||
it("Can generate the schema for a map of objects") {
|
||||
jsonSchemaTest<Map<String, TestResponse>>("T0013__object_map.json")
|
||||
}
|
||||
it("Can generate the schema for a nullable map") {
|
||||
jsonSchemaTest<Map<String, Int>?>("T0014__nullable_map.json")
|
||||
}
|
||||
}
|
||||
}) {
|
||||
companion object {
|
||||
private val json = Json {
|
||||
encodeDefaults = true
|
||||
explicitNulls = false
|
||||
prettyPrint = true
|
||||
}
|
||||
|
||||
private fun JsonSchema.serialize() = json.encodeToString(JsonSchema.serializer(), this)
|
||||
|
||||
private inline fun <reified T> jsonSchemaTest(snapshotName: String) {
|
||||
// act
|
||||
val schema = SchemaGenerator.fromTypeToSchema<T>()
|
||||
|
||||
// todo add cache assertions!!!
|
||||
|
||||
// assert
|
||||
schema.serialize() shouldEqualJson getFileSnapshot(snapshotName)
|
||||
}
|
||||
}
|
||||
}
|
4
json-schema/src/test/resources/T0001__scalar_int.json
Normal file
4
json-schema/src/test/resources/T0001__scalar_int.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "number",
|
||||
"format": "int32"
|
||||
}
|
3
json-schema/src/test/resources/T0002__scalar_bool.json
Normal file
3
json-schema/src/test/resources/T0002__scalar_bool.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
3
json-schema/src/test/resources/T0003__scalar_string.json
Normal file
3
json-schema/src/test/resources/T0003__scalar_string.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "string"
|
||||
}
|
16
json-schema/src/test/resources/T0004__simple_object.json
Normal file
16
json-schema/src/test/resources/T0004__simple_object.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
},
|
||||
"b": {
|
||||
"type": "number",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
}
|
22
json-schema/src/test/resources/T0005__complex_object.json
Normal file
22
json-schema/src/test/resources/T0005__complex_object.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amazingField": {
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"type": "string"
|
||||
},
|
||||
"tables": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/NestedComplexItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"amazingField",
|
||||
"org",
|
||||
"tables"
|
||||
]
|
||||
}
|
23
json-schema/src/test/resources/T0006__nullable_object.json
Normal file
23
json-schema/src/test/resources/T0006__nullable_object.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
},
|
||||
"b": {
|
||||
"type": "number",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
3
json-schema/src/test/resources/T0007__simple_enum.json
Normal file
3
json-schema/src/test/resources/T0007__simple_enum.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"enum": [ "ONE", "TWO" ]
|
||||
}
|
13
json-schema/src/test/resources/T0008__nullable_enum.json
Normal file
13
json-schema/src/test/resources/T0008__nullable_enum.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"ONE",
|
||||
"TWO"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
7
json-schema/src/test/resources/T0009__scalar_array.json
Normal file
7
json-schema/src/test/resources/T0009__scalar_array.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"items": {
|
||||
"type": "number",
|
||||
"format": "int32"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
6
json-schema/src/test/resources/T0010__object_array.json
Normal file
6
json-schema/src/test/resources/T0010__object_array.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TestResponse"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
14
json-schema/src/test/resources/T0011__nullable_array.json
Normal file
14
json-schema/src/test/resources/T0011__nullable_array.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "number",
|
||||
"format": "int32"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
}
|
7
json-schema/src/test/resources/T0012__scalar_map.json
Normal file
7
json-schema/src/test/resources/T0012__scalar_map.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "int32"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
6
json-schema/src/test/resources/T0013__object_map.json
Normal file
6
json-schema/src/test/resources/T0013__object_map.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/TestResponse"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
14
json-schema/src/test/resources/T0014__nullable_map.json
Normal file
14
json-schema/src/test/resources/T0014__nullable_map.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "int32"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ComplexGibbit"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SimpleGibbit"
|
||||
}
|
||||
]
|
||||
}
|
13
json-schema/src/test/resources/T0016__recursive_object.json
Normal file
13
json-schema/src/test/resources/T0016__recursive_object.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/OneJamma"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AnothaJamma"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/InsaneJamma"
|
||||
}
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user