fix: support recursive types (#174)

This commit is contained in:
Ryan Brink
2022-02-04 12:34:21 -05:00
committed by GitHub
parent a2be2c9e93
commit d54b8a730f
77 changed files with 487 additions and 8 deletions

View File

@ -12,6 +12,10 @@
## Released
## [2.0.2] - February 4th, 2022
### Added
- `@Referenced` annotation enabling support for recursive models
## [2.0.1] - January 23rd, 2022
### Change

View File

@ -1,5 +1,5 @@
# Kompendium
project.version=2.0.1
project.version=2.0.2
# Kotlin
kotlin.code.style=official
# Gradle

View File

@ -0,0 +1,11 @@
package io.bkbn.kompendium.annotations
/**
* This instructs Kompendium to store the class as a referenced component.
* This is mandatory for any data models that have recursive children.
* If you do not annotate a recursive class with [Referenced], you will
* get a stack overflow error when you try to launch your API
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Referenced

View File

@ -82,6 +82,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {
"basic": {
"type": "http",

View File

@ -82,6 +82,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {
"jwt": {
"bearerFormat": "JWT",

View File

@ -82,6 +82,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {
"oauth": {
"flows": {

View File

@ -1,5 +1,16 @@
package io.bkbn.kompendium.core
import io.bkbn.kompendium.core.util.Helpers.COMPONENT_SLUG
import io.bkbn.kompendium.oas.schema.AnyOfSchema
import io.bkbn.kompendium.oas.schema.ArraySchema
import io.bkbn.kompendium.oas.schema.ComponentSchema
import io.bkbn.kompendium.oas.schema.DictionarySchema
import io.bkbn.kompendium.oas.schema.EnumSchema
import io.bkbn.kompendium.oas.schema.FormattedSchema
import io.bkbn.kompendium.oas.schema.FreeFormSchema
import io.bkbn.kompendium.oas.schema.ObjectSchema
import io.bkbn.kompendium.oas.schema.ReferencedSchema
import io.bkbn.kompendium.oas.schema.SimpleSchema
import io.ktor.application.feature
import io.ktor.routing.Route
import io.ktor.routing.application
@ -35,5 +46,32 @@ object KompendiumPreFlight {
feature.config.cache = Kontent.generateKontent(requestType, feature.config.cache)
feature.config.cache = Kontent.generateKontent(responseType, feature.config.cache)
feature.config.cache = Kontent.generateKontent(paramType, feature.config.cache)
feature.updateReferences()
}
private fun Kompendium.updateReferences() {
val references = config.cache.values
.asSequence()
.map { flattenSchema(it) }
.flatten()
.filterIsInstance<ReferencedSchema>()
.map { it.`$ref` }
.map { it.replace(COMPONENT_SLUG.plus("/"), "") }
.toList()
references.forEach { ref ->
config.spec.components.schemas[ref] = config.cache[ref] ?: error("$ref does not exist in cache 😱")
}
}
private fun flattenSchema(schema: ComponentSchema): List<ComponentSchema> = when (schema) {
is AnyOfSchema -> schema.anyOf.map { flattenSchema(it) }.flatten()
is ReferencedSchema -> listOf(schema)
is ArraySchema -> flattenSchema(schema.items)
is DictionarySchema -> flattenSchema(schema.additionalProperties)
is EnumSchema -> listOf(schema)
is FormattedSchema -> listOf(schema)
is FreeFormSchema -> listOf(schema)
is ObjectSchema -> schema.properties.values.map { flattenSchema(it) }.flatten()
is SimpleSchema -> listOf(schema)
}
}

View File

@ -2,6 +2,7 @@ package io.bkbn.kompendium.core
import io.bkbn.kompendium.annotations.Field
import io.bkbn.kompendium.annotations.FreeFormObject
import io.bkbn.kompendium.annotations.Referenced
import io.bkbn.kompendium.annotations.UndeclaredField
import io.bkbn.kompendium.annotations.constraint.Format
import io.bkbn.kompendium.annotations.constraint.MaxItems
@ -18,6 +19,7 @@ import io.bkbn.kompendium.annotations.constraint.UniqueItems
import io.bkbn.kompendium.core.metadata.SchemaMap
import io.bkbn.kompendium.core.metadata.TypeMap
import io.bkbn.kompendium.core.util.Helpers.genericNameAdapter
import io.bkbn.kompendium.core.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.core.util.Helpers.logged
import io.bkbn.kompendium.core.util.Helpers.toNumber
@ -29,6 +31,7 @@ import io.bkbn.kompendium.oas.schema.EnumSchema
import io.bkbn.kompendium.oas.schema.FormattedSchema
import io.bkbn.kompendium.oas.schema.FreeFormSchema
import io.bkbn.kompendium.oas.schema.ObjectSchema
import io.bkbn.kompendium.oas.schema.ReferencedSchema
import io.bkbn.kompendium.oas.schema.SimpleSchema
import kotlin.reflect.KClass
import kotlin.reflect.KClassifier
@ -36,6 +39,7 @@ import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.createType
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
@ -144,6 +148,10 @@ object Kontent {
false -> {
logger.debug("$slug was not found in cache, generating now")
var newCache = cache
// If referenced, add tie from simple slug to schema slug
if (clazz.hasAnnotation<Referenced>()) {
newCache = newCache.plus(type.getSimpleSlug() to ReferencedSchema(type.getReferenceSlug()))
}
// Grabs any type parameters mapped to the corresponding type argument(s)
val typeMap: TypeMap = clazz.typeParameters.zip(type.arguments).toMap()
// associates each member with a Pair of prop name to property schema
@ -274,6 +282,7 @@ object Kontent {
is FreeFormSchema -> this // todo anything here?
is ObjectSchema -> scanForConstraints(clazz, prop)
is SimpleSchema -> scanForConstraints(prop)
is ReferencedSchema -> this // todo anything here?
}
private fun ArraySchema.scanForConstraints(prop: KProperty1<*, *>): ArraySchema {

View File

@ -1,19 +1,19 @@
package io.bkbn.kompendium.core.util
import java.lang.reflect.ParameterizedType
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.full.createType
import kotlin.reflect.jvm.javaField
import org.slf4j.LoggerFactory
import java.lang.reflect.ParameterizedType
import java.util.Locale
object Helpers {
private val logger = LoggerFactory.getLogger(javaClass)
private const val COMPONENT_SLUG = "#/components/schemas"
const val COMPONENT_SLUG = "#/components/schemas"
val UNIT_TYPE by lazy { Unit::class.createType() }

View File

@ -46,6 +46,7 @@ import io.bkbn.kompendium.core.util.requiredParameter
import io.bkbn.kompendium.core.util.returnsList
import io.bkbn.kompendium.core.util.rootModule
import io.bkbn.kompendium.core.util.simpleGenericResponse
import io.bkbn.kompendium.core.util.simpleRecursive
import io.bkbn.kompendium.core.util.trailingSlash
import io.bkbn.kompendium.core.util.undeclaredType
import io.bkbn.kompendium.core.util.uniqueArray
@ -213,6 +214,9 @@ class KompendiumTest : DescribeSpec({
it("Can override field values via annotation") {
openApiTest("field_override.json") { overrideFieldInfo() }
}
it("Can serialize a recursive type using references") {
openApiTest("simple_recursive.json") { simpleRecursive() }
}
}
describe("Constraints") {
it("Can set a minimum and maximum integer value") {

View File

@ -405,6 +405,16 @@ fun Application.overrideFieldInfo() {
}
}
fun Application.simpleRecursive() {
routing {
route("/test/simple_recursive") {
notarizedGet(TestResponseInfo.simpleRecursive) {
call.respond(HttpStatusCode.OK)
}
}
}
}
fun Application.constrainedIntInfo() {
routing {
route("/test/constrained_int") {

View File

@ -119,6 +119,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -196,6 +196,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -80,6 +80,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -68,6 +68,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -90,6 +90,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -126,6 +126,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -62,6 +62,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -58,6 +58,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -68,6 +68,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -58,6 +58,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -114,6 +114,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -62,6 +62,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -62,6 +62,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -62,6 +62,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -60,6 +60,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -62,6 +62,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -59,6 +59,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -59,6 +59,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -59,6 +59,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -77,6 +77,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -62,6 +62,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -56,6 +56,7 @@
}
},
"components" : {
"schemas": {},
"securitySchemes" : { }
},
"security" : [ ],

View File

@ -77,6 +77,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -95,6 +95,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -113,6 +113,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -78,6 +78,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -42,6 +42,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -77,6 +77,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -57,6 +57,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -122,6 +122,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -61,6 +61,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -122,6 +122,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -77,6 +77,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -67,6 +67,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -77,6 +77,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -115,6 +115,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -80,6 +80,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -80,6 +80,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -77,6 +77,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -92,6 +92,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -88,6 +88,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -58,6 +58,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -67,6 +67,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -80,6 +80,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -77,6 +77,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -62,6 +62,45 @@
"b"
],
"type": "object"
},
{
"properties": {
"c": {
"anyOf": [
{
"properties": {
"a": {
"format": "int32",
"type": "integer"
}
},
"required": [
"a"
],
"type": "object"
},
{
"properties": {
"b": {
"format": "float",
"type": "number"
}
},
"required": [
"b"
],
"type": "object"
},
{
"$ref": "#/components/schemas/InsaneJamma"
}
]
}
},
"required": [
"c"
],
"type": "object"
}
]
}
@ -74,6 +113,47 @@
}
},
"components": {
"schemas": {
"InsaneJamma": {
"properties": {
"c": {
"anyOf": [
{
"properties": {
"a": {
"format": "int32",
"type": "integer"
}
},
"required": [
"a"
],
"type": "object"
},
{
"properties": {
"b": {
"format": "float",
"type": "number"
}
},
"required": [
"b"
],
"type": "object"
},
{
"$ref": "#/components/schemas/InsaneJamma"
}
]
}
},
"required": [
"c"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],

View File

@ -0,0 +1,123 @@
{
"openapi": "3.0.3",
"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": {
"/test/simple_recursive": {
"get": {
"tags": [],
"summary": "Simple recursive example",
"description": "Pretty neato!",
"parameters": [],
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"properties": {
"description": {
"type": "string"
},
"mode": {
"enum": [
"NULLABLE",
"REQUIRED",
"REPEATED"
],
"type": "string"
},
"name": {
"type": "string"
},
"subColumns": {
"items": {
"$ref": "#/components/schemas/ColumnSchema"
},
"type": "array"
},
"type": {
"type": "string"
}
},
"required": [
"name",
"type",
"description",
"mode"
],
"type": "object"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"ColumnSchema": {
"properties": {
"description": {
"type": "string"
},
"mode": {
"enum": [
"NULLABLE",
"REQUIRED",
"REPEATED"
],
"type": "string"
},
"name": {
"type": "string"
},
"subColumns": {
"items": {
"$ref": "#/components/schemas/ColumnSchema"
},
"type": "array"
},
"type": {
"type": "string"
}
},
"required": [
"name",
"type",
"description",
"mode"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -77,6 +77,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -64,6 +64,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -62,6 +62,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -4,6 +4,7 @@ import io.bkbn.kompendium.annotations.Field
import io.bkbn.kompendium.annotations.FreeFormObject
import io.bkbn.kompendium.annotations.Param
import io.bkbn.kompendium.annotations.ParamType
import io.bkbn.kompendium.annotations.Referenced
import io.bkbn.kompendium.annotations.UndeclaredField
import io.bkbn.kompendium.annotations.constraint.Format
import io.bkbn.kompendium.annotations.constraint.MaxItems
@ -198,7 +199,9 @@ sealed interface SlammaJamma
data class OneJamma(val a: Int) : SlammaJamma
data class AnothaJamma(val b: Float) : SlammaJamma
//data class InsaneJamma(val c: SlammaJamma) : SlammaJamma // 👀
@Referenced
data class InsaneJamma(val c: SlammaJamma) : SlammaJamma
sealed interface Flibbity<T>
@ -216,3 +219,18 @@ data class Mysterious(val nowYouSeeMe: String)
data class HeaderNameTest(
@Param(type = ParamType.HEADER) val `X-UserEmail`: String
)
enum class ColumnMode {
NULLABLE,
REQUIRED,
REPEATED
}
@Referenced
data class ColumnSchema(
val name: String,
val type: String,
val description: String,
val mode: ColumnMode,
val subColumns: List<ColumnSchema> = emptyList()
)

View File

@ -2,8 +2,6 @@ package io.bkbn.kompendium.core.fixtures
import io.bkbn.kompendium.core.metadata.ExceptionInfo
import io.bkbn.kompendium.core.metadata.ParameterExample
import io.bkbn.kompendium.core.metadata.method.PostInfo
import io.bkbn.kompendium.core.metadata.method.PutInfo
import io.bkbn.kompendium.core.metadata.RequestInfo
import io.bkbn.kompendium.core.metadata.ResponseInfo
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
@ -11,6 +9,8 @@ import io.bkbn.kompendium.core.metadata.method.GetInfo
import io.bkbn.kompendium.core.metadata.method.HeadInfo
import io.bkbn.kompendium.core.metadata.method.OptionsInfo
import io.bkbn.kompendium.core.metadata.method.PatchInfo
import io.bkbn.kompendium.core.metadata.method.PostInfo
import io.bkbn.kompendium.core.metadata.method.PutInfo
import io.ktor.http.HttpStatusCode
import kotlin.reflect.typeOf
@ -169,6 +169,12 @@ object TestResponseInfo {
responseInfo = simpleOkResponse()
)
val simpleRecursive = GetInfo<Unit, ColumnSchema>(
summary = "Simple recursive example",
description = "Pretty neato!",
responseInfo = simpleOkResponse()
)
val minMaxInt = GetInfo<Unit, MinMaxInt>(
summary = "Constrained int field",
description = "Cool stuff",

View File

@ -76,6 +76,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -67,6 +67,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -76,6 +76,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -67,6 +67,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -95,6 +95,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -86,6 +86,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -95,6 +95,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -86,6 +86,7 @@
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"security": [],

View File

@ -1,9 +1,11 @@
package io.bkbn.kompendium.oas.component
import io.bkbn.kompendium.oas.schema.ComponentSchema
import io.bkbn.kompendium.oas.security.SecuritySchema
import kotlinx.serialization.Serializable
@Serializable
data class Components(
val schemas: MutableMap<String, ComponentSchema> = mutableMapOf(),
val securitySchemes: MutableMap<String, SecuritySchema> = mutableMapOf()
)

View File

@ -20,6 +20,8 @@ sealed interface ComponentSchema {
is FormattedSchema -> this.copy(default = default)
is ObjectSchema -> this.copy(default = default)
is SimpleSchema -> this.copy(default = default)
is ReferencedSchema -> this.copy(default = default)
is FreeFormSchema -> this.copy(default = default)
else -> error("Compiler bug??")
}
@ -31,6 +33,8 @@ sealed interface ComponentSchema {
is FormattedSchema -> this.copy(description = description)
is ObjectSchema -> this.copy(description = description)
is SimpleSchema -> this.copy(description = description)
is ReferencedSchema -> this.copy(description = description)
is FreeFormSchema -> this.copy(description = description)
else -> error("Compiler bug??")
}
}

View File

@ -8,9 +8,10 @@ data class FreeFormSchema(
override val nullable: Boolean? = null,
// constraints
val minProperties: Int? = null,
val maxProperties: Int? = null
val maxProperties: Int? = null,
override val default: @Contextual Any? = null,
override val description: String? = null,
) : TypedSchema {
val additionalProperties: Boolean = true
override val type: String = "object"
override val default: @Contextual Any? = null
}

View File

@ -0,0 +1,11 @@
package io.bkbn.kompendium.oas.schema
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
@Serializable
data class ReferencedSchema(
val `$ref`: String,
override val default: @Contextual Any? = null,
override val description: String? = null
) : ComponentSchema

View File

@ -8,6 +8,7 @@ import io.bkbn.kompendium.oas.schema.EnumSchema
import io.bkbn.kompendium.oas.schema.FormattedSchema
import io.bkbn.kompendium.oas.schema.FreeFormSchema
import io.bkbn.kompendium.oas.schema.ObjectSchema
import io.bkbn.kompendium.oas.schema.ReferencedSchema
import io.bkbn.kompendium.oas.schema.SimpleSchema
import io.bkbn.kompendium.oas.security.ApiKeyAuth
import io.bkbn.kompendium.oas.security.BasicAuth
@ -29,6 +30,7 @@ object KompendiumSerializersModule {
subclass(DictionarySchema::class, DictionarySchema.serializer())
subclass(EnumSchema::class, EnumSchema.serializer())
subclass(FreeFormSchema::class, FreeFormSchema.serializer())
subclass(ReferencedSchema::class, ReferencedSchema.serializer())
}
polymorphic(SecuritySchema::class) {
subclass(ApiKeyAuth::class, ApiKeyAuth.serializer())

View File

@ -0,0 +1,97 @@
package io.bkbn.kompendium.playground
import io.bkbn.kompendium.annotations.Referenced
import io.bkbn.kompendium.core.Kompendium
import io.bkbn.kompendium.core.Notarized.notarizedGet
import io.bkbn.kompendium.core.metadata.ResponseInfo
import io.bkbn.kompendium.core.metadata.method.GetInfo
import io.bkbn.kompendium.core.routes.redoc
import io.bkbn.kompendium.playground.util.Util
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.ContentNegotiation
import io.ktor.http.HttpStatusCode
import io.ktor.response.respond
import io.ktor.routing.route
import io.ktor.routing.routing
import io.ktor.serialization.json
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import kotlinx.serialization.Serializable
enum class ColumnMode {
NULLABLE,
REQUIRED,
REPEATED
}
@Referenced // Indicates that Kompendium should store this class as a $ref component.
@Serializable
data class ColumnSchema(
val name: String,
val type: String,
val description: String,
val mode: ColumnMode,
val subColumns: List<ColumnSchema> = emptyList()
)
sealed interface RecursiveSlammaJamma
@Serializable
data class OneJamma(val a: Int) : RecursiveSlammaJamma
@Serializable
data class AnothaJamma(val b: Float) : RecursiveSlammaJamma
@Referenced
@Serializable
data class InsaneJamma(val c: RecursiveSlammaJamma) : RecursiveSlammaJamma
fun main() {
embeddedServer(
Netty,
port = 8081,
module = Application::mainModule
).start(wait = true)
}
private fun Application.mainModule() {
install(ContentNegotiation) {
json(json = Util.kotlinxConfig)
}
install(Kompendium) {
spec = Util.baseSpec
}
routing {
redoc(pageTitle = "Recursive API Docs")
notarizedGet(
GetInfo<Unit, ColumnSchema>(
summary = "Its recursive",
description = "This is how we do it!",
responseInfo = ResponseInfo(
status = HttpStatusCode.OK,
description = "This means everything went as expected!",
),
tags = setOf("Simple")
)
) {
call.respond(HttpStatusCode.OK, "Nice!")
}
route("cmon_and_slam") {
notarizedGet(
GetInfo<Unit, RecursiveSlammaJamma>(
summary = "Its recursive",
description = "This is how we do it!",
responseInfo = ResponseInfo(
status = HttpStatusCode.OK,
description = "This means everything went as expected!",
),
tags = setOf("Simple")
)
) {
call.respond(HttpStatusCode.OK, "Nice!")
}
}
}
}