fix: support recursive types (#174)
This commit is contained in:
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Kompendium
|
||||
project.version=2.0.1
|
||||
project.version=2.0.2
|
||||
# Kotlin
|
||||
kotlin.code.style=official
|
||||
# Gradle
|
||||
|
@ -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
|
@ -82,6 +82,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {
|
||||
"basic": {
|
||||
"type": "http",
|
||||
|
@ -82,6 +82,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {
|
||||
"jwt": {
|
||||
"bearerFormat": "JWT",
|
||||
|
@ -82,6 +82,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {
|
||||
"oauth": {
|
||||
"flows": {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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() }
|
||||
|
||||
|
@ -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") {
|
||||
|
@ -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") {
|
||||
|
@ -119,6 +119,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -196,6 +196,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -80,6 +80,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -68,6 +68,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -90,6 +90,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -126,6 +126,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -62,6 +62,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -58,6 +58,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -68,6 +68,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -58,6 +58,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -114,6 +114,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -62,6 +62,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -62,6 +62,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -62,6 +62,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -60,6 +60,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -62,6 +62,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -59,6 +59,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -59,6 +59,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -59,6 +59,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -77,6 +77,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -62,6 +62,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -56,6 +56,7 @@
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas": {},
|
||||
"securitySchemes" : { }
|
||||
},
|
||||
"security" : [ ],
|
||||
|
@ -77,6 +77,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -95,6 +95,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -113,6 +113,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -78,6 +78,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -42,6 +42,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -77,6 +77,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -57,6 +57,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -122,6 +122,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -61,6 +61,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -122,6 +122,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -77,6 +77,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -67,6 +67,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -77,6 +77,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -115,6 +115,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -80,6 +80,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -80,6 +80,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -77,6 +77,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -92,6 +92,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -88,6 +88,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -58,6 +58,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -67,6 +67,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -80,6 +80,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -77,6 +77,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -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": [],
|
||||
|
123
kompendium-core/src/test/resources/simple_recursive.json
Normal file
123
kompendium-core/src/test/resources/simple_recursive.json
Normal 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": []
|
||||
}
|
@ -77,6 +77,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -64,6 +64,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -62,6 +62,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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",
|
||||
|
@ -76,6 +76,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -67,6 +67,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -76,6 +76,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -67,6 +67,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -95,6 +95,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -86,6 +86,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -95,6 +95,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -86,6 +86,7 @@
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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??")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
@ -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())
|
||||
|
@ -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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user