Compare commits

..

4 Commits

27 changed files with 369 additions and 95 deletions

View File

@ -37,3 +37,28 @@ jobs:
run: ./gradlew assemble
- name: Run Unit Tests
run: ./gradlew test
- name: Cache Coverage Results
uses: actions/cache@v2
with:
path: ./**/build/reports/jacoco
key: ${{ runner.os }}-unit-${{ env.GITHUB_SHA }}
upload-code-coverage:
runs-on: ubuntu-latest
needs:
- unit
steps:
- uses: actions/checkout@v2
- name: Cache Coverage Results
uses: actions/cache@v2
with:
path: ./**/build/reports/jacoco
key: ${{ runner.os }}-unit-${{ env.GITHUB_SHA }}
- name: Cache Coverage Results
uses: actions/cache@v2
with:
path: ./**/build/reports/jacoco
key: ${{ runner.os }}-integration-${{ env.GITHUB_SHA }}
- name: Publish code coverage to Codacy
uses: codacy/codacy-coverage-reporter-action@v1
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}

View File

@ -24,3 +24,23 @@ jobs:
run: ./gradlew publishAllPublicationsToGithubPackagesRepository
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
code-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Cache Gradle packages
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
- name: Run Unit Tests
run: ./gradlew test
- name: Publish code coverage to Codacy
uses: codacy/codacy-coverage-reporter-action@v1
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}

View File

@ -1,6 +1,26 @@
# Changelog
## [1.6.0] - August 12, 2021
## [1.8.1] - October 4th, 2021
### Added
- Codacy integration
## [1.8.0] - October 4th, 2021
### Changed
- Path calculation revamped to allow for simpler selector injection
- Kotlin version bumped to 1.5.31
- Ktor version bumped to 1.6.4
## [1.7.0] - August 14th, 2021
### Added
- Added ability to inject an emergency `UndeclaredField` in the event of certain polymorphic serializers and such
## [1.6.0] - August 12th, 2021
### Added

View File

@ -1,5 +1,7 @@
# Kompendium
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/a9bfd6c77d22497b907b3221849a3ba9)](https://www.codacy.com/gh/bkbnio/kompendium/dashboard?utm_source=github.com&utm_medium=referral&utm_content=bkbnio/kompendium&utm_campaign=Badge_Grade)
[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/a9bfd6c77d22497b907b3221849a3ba9)](https://www.codacy.com/gh/bkbnio/kompendium/dashboard?utm_source=github.com&utm_medium=referral&utm_content=bkbnio/kompendium&utm_campaign=Badge_Coverage)
[![version](https://img.shields.io/maven-central/v/io.bkbn/kompendium-core?style=flat-square)](https://search.maven.org/search?q=io.bkbn%20kompendium)
## What is Kompendium
@ -96,9 +98,21 @@ The intended purpose of `KompendiumField` is to offer field level overrides such
The purpose of `KompendiumParam` is to provide supplemental information needed to properly assign the type of parameter
(cookie, header, query, path) as well as other parameter-level metadata.
### Undeclared Field
There is also a final `UndeclaredField` annotation. This should be used only in an absolutely emergency. This annotation
will allow you to inject a _single_ undeclared field that will be included as part of the schema.
Due to limitations in using repeated annotations, this can only be used once per class
This is a complete hack, and is included for odd scenarios like kotlinx serialization polymorphic adapters that expect a
`type` field in order to perform their analysis.
Use this _only_ when **all** else fails
### Polymorphism
Out of the box, Kompendium has support for sealed classes. At runtime, it will build a mapping of all available sub-classes
Speaking of polymorphism... out of the box, Kompendium has support for sealed classes and interfaces. At runtime, it will build a mapping of all available sub-classes
and build a spec that takes `anyOf` the implementations. This is currently a weak point of the entire library, and
suggestions on better implementations are welcome 🤠
@ -107,6 +121,33 @@ suggestions on better implementations are welcome 🤠
Under the hood, Kompendium uses Jackson to serialize the final api spec. However, this implementation detail
does not leak to the actual API, meaning that users are free to choose the serialization library of their choice.
### Route Handling
> ⚠️ Warning: Custom route handling is almost definitely an indication that a new Kompendium module needs to be added. If you have encountered a route type that is not handled, please consider opening an [issue](https://github.com/bkbnio/kompendium/issues/new)
Kompendium does its best to handle all Ktor routes out of the gate. However, in keeping with the modular approach of
Ktor and Kompendium, this is not always possible.
Should you need to, custom route handlers can be registered via the
`Kompendium.addCustomRouteHandler` function.
The method declaration is a bit gross, so lets dig in to what is happening.
```kotlin
fun <T : RouteSelector> addCustomRouteHandler(
selector: KClass<T>,
handler: PathCalculator.(Route, String) -> String
)
```
This function takes a selector, which _must_ be an implementation of the Ktor `RouteSelector`. The handler is a function
that extends the Kompendium `PathCalculator`. This is necessary because it gives you access to `PathCalculator.calculate`,
which you are going to want in order to calculate the rest of the route :)
Its parameters are the `Route` itself, along with the "tail" of the Path (the path that has been calculated thus far).
Working examples `init` blocks of the `PathCalculator` and `KompendiumAuth` object.
## Examples
The full source code can be found in the `kompendium-playground` module. Here is a simple get endpoint example

View File

@ -4,7 +4,7 @@ import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.jetbrains.kotlin.jvm") version "1.5.0" apply false
id("org.jetbrains.kotlin.jvm") version "1.5.31" apply false
id("io.gitlab.arturbosch.detekt") version "1.17.0-RC3" apply false
id("com.adarshr.test-logger") version "3.0.0" apply false
id("io.github.gradle-nexus.publish-plugin") version "1.1.0" apply true
@ -30,6 +30,7 @@ allprojects {
apply(plugin = "io.gitlab.arturbosch.detekt")
apply(plugin = "com.adarshr.test-logger")
apply(plugin = "idea")
apply(plugin = "jacoco")
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
@ -37,6 +38,21 @@ allprojects {
}
}
tasks.withType<Test>() {
finalizedBy(tasks.withType(JacocoReport::class))
}
tasks.withType<JacocoReport>() {
reports {
html.required.set(true)
xml.required.set(true)
}
}
configure<JacocoPluginExtension> {
toolVersion = "0.8.7"
}
configure<TestLoggerExtension> {
theme = ThemeType.MOCHA
setLogLevel("lifecycle")

View File

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

View File

@ -1,6 +1,6 @@
[versions]
kotlin = "1.4.32"
ktor = "1.5.3"
ktor = "1.6.4"
kotlinx-serialization = "1.2.1"
jackson-kotlin = "2.12.0"
slf4j = "1.7.30"

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,19 +0,0 @@
package io.bkbn.kompendium.auth
import io.ktor.auth.AuthenticationRouteSelector
import io.ktor.routing.Route
import io.bkbn.kompendium.path.CorePathCalculator
import org.slf4j.LoggerFactory
class AuthPathCalculator : CorePathCalculator() {
private val logger = LoggerFactory.getLogger(javaClass)
override fun handleCustomSelectors(route: Route, tail: String): String = when (route.selector) {
is AuthenticationRouteSelector -> {
logger.debug("Found authentication route selector ${route.selector}")
super.calculate(route.parent, tail)
}
else -> super.handleCustomSelectors(route, tail)
}
}

View File

@ -7,11 +7,14 @@ import io.ktor.auth.jwt.jwt
import io.ktor.auth.jwt.JWTAuthenticationProvider
import io.bkbn.kompendium.Kompendium
import io.bkbn.kompendium.models.oas.OpenApiSpecSchemaSecurity
import io.ktor.auth.AuthenticationRouteSelector
object KompendiumAuth {
init {
Kompendium.pathCalculator = AuthPathCalculator()
Kompendium.addCustomRouteHandler(AuthenticationRouteSelector::class) { route, tail ->
calculate(route.parent, tail)
}
}
fun Authentication.Configuration.notarizedBasic(

View File

@ -5,8 +5,10 @@ import io.bkbn.kompendium.models.meta.SchemaMap
import io.bkbn.kompendium.models.oas.OpenApiSpec
import io.bkbn.kompendium.models.oas.OpenApiSpecInfo
import io.bkbn.kompendium.models.oas.TypedSchema
import io.bkbn.kompendium.path.CorePathCalculator
import io.bkbn.kompendium.path.IPathCalculator
import io.bkbn.kompendium.path.PathCalculator
import io.ktor.routing.Route
import io.ktor.routing.RouteSelector
import kotlin.reflect.KClass
/**
@ -23,7 +25,7 @@ object Kompendium {
paths = mutableMapOf()
)
var pathCalculator: PathCalculator = CorePathCalculator()
fun calculatePath(route: Route) = PathCalculator.calculate(route)
fun resetSchema() {
openApiSpec = OpenApiSpec(
@ -37,4 +39,12 @@ object Kompendium {
fun addCustomTypeSchema(clazz: KClass<*>, schema: TypedSchema) {
cache = cache.plus(clazz.simpleName!! to schema)
}
fun <T : RouteSelector> addCustomRouteHandler(
selector: KClass<T>,
handler: IPathCalculator.(Route, String) -> String
) {
PathCalculator.addCustomRouteHandler(selector, handler)
}
}

View File

@ -1,5 +1,6 @@
package io.bkbn.kompendium
import io.bkbn.kompendium.annotations.UndeclaredField
import io.bkbn.kompendium.models.meta.SchemaMap
import io.bkbn.kompendium.models.oas.AnyOfReferencedSchema
import io.bkbn.kompendium.models.oas.ArraySchema
@ -7,6 +8,7 @@ import io.bkbn.kompendium.models.oas.DictionarySchema
import io.bkbn.kompendium.models.oas.EnumSchema
import io.bkbn.kompendium.models.oas.FormatSchema
import io.bkbn.kompendium.models.oas.ObjectSchema
import io.bkbn.kompendium.models.oas.OpenApiSpecComponentSchema
import io.bkbn.kompendium.models.oas.ReferencedSchema
import io.bkbn.kompendium.models.oas.SimpleSchema
import io.bkbn.kompendium.util.Helpers.COMPONENT_SLUG
@ -204,8 +206,14 @@ object Kontent {
}
Pair(prop.name, propSchema)
}
logger.debug("Looking for undeclared fields")
val undeclaredFieldMap = clazz.annotations.filterIsInstance<UndeclaredField>().associate {
val undeclaredType = it.clazz.createType()
newCache = generateKontent(undeclaredType, newCache)
it.field to ReferencedSchema(undeclaredType.getReferenceSlug())
}
logger.debug("$slug contains $fieldMap")
val schema = ObjectSchema(fieldMap)
val schema = ObjectSchema(fieldMap.plus(undeclaredFieldMap))
logger.debug("$slug schema: $schema")
newCache.plus(slug to schema)
}

View File

@ -36,7 +36,7 @@ object Notarized {
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this)
val path = Kompendium.calculatePath(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.get = parseMethodInfo(info, paramType, requestType, responseType)
return method(HttpMethod.Get) { handle(body) }
@ -55,7 +55,7 @@ object Notarized {
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this)
val path = Kompendium.calculatePath(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.post = parseMethodInfo(info, paramType, requestType, responseType)
return method(HttpMethod.Post) { handle(body) }
@ -74,7 +74,7 @@ object Notarized {
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this)
val path = Kompendium.calculatePath(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.put =
parseMethodInfo(info, paramType, requestType, responseType)
@ -93,7 +93,7 @@ object Notarized {
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this)
val path = Kompendium.calculatePath(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.delete = parseMethodInfo(info, paramType, requestType, responseType)
return method(HttpMethod.Delete) { handle(body) }

View File

@ -0,0 +1,8 @@
package io.bkbn.kompendium.annotations
import kotlin.reflect.KClass
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@Repeatable
annotation class UndeclaredField(val field: String, val clazz: KClass<*>)

View File

@ -1,51 +0,0 @@
package io.bkbn.kompendium.path
import io.ktor.routing.PathSegmentConstantRouteSelector
import io.ktor.routing.PathSegmentParameterRouteSelector
import io.ktor.routing.RootRouteSelector
import io.ktor.routing.Route
import io.ktor.util.InternalAPI
import org.slf4j.LoggerFactory
/**
* Default [PathCalculator] meant to be overridden as necessary
*/
open class CorePathCalculator : PathCalculator {
private val logger = LoggerFactory.getLogger(javaClass)
@OptIn(InternalAPI::class)
override fun calculate(
route: Route?,
tail: String
): String = when (route) {
null -> tail
else -> when (route.selector) {
is RootRouteSelector -> {
logger.debug("Root route detected, returning path: $tail")
tail
}
is PathSegmentParameterRouteSelector -> {
logger.debug("Found segment parameter ${route.selector}, continuing to parent")
val newTail = "/${route.selector}$tail"
calculate(route.parent, newTail)
}
is PathSegmentConstantRouteSelector -> {
logger.debug("Found segment constant ${route.selector}, continuing to parent")
val newTail = "/${route.selector}$tail"
calculate(route.parent, newTail)
}
else -> when (route.selector.javaClass.simpleName) {
"TrailingSlashRouteSelector" -> {
logger.debug("Found trailing slash route selector")
val newTail = tail.ifBlank { "/" }
calculate(route.parent, newTail)
}
else -> handleCustomSelectors(route, tail)
}
}
}
override fun handleCustomSelectors(route: Route, tail: String): String = error("Unknown selector ${route.selector}")
}

View File

@ -0,0 +1,13 @@
package io.bkbn.kompendium.path
import io.ktor.routing.Route
import io.ktor.routing.RouteSelector
import kotlin.reflect.KClass
interface IPathCalculator {
fun calculate(route: Route?, tail: String = ""): String
fun <T : RouteSelector> addCustomRouteHandler(
selector: KClass<T>,
handler: IPathCalculator.(Route, String) -> String
)
}

View File

@ -1,20 +1,54 @@
package io.bkbn.kompendium.path
import io.ktor.routing.PathSegmentConstantRouteSelector
import io.ktor.routing.PathSegmentParameterRouteSelector
import io.ktor.routing.RootRouteSelector
import io.ktor.routing.Route
import io.ktor.routing.RouteSelector
import io.ktor.routing.TrailingSlashRouteSelector
import io.ktor.util.InternalAPI
import kotlin.reflect.KClass
/**
* Extensible interface for calculating Ktor paths
* Responsible for calculating a url path from a provided [Route]
*/
interface PathCalculator {
@OptIn(InternalAPI::class)
internal object PathCalculator: IPathCalculator {
/**
* Core route calculation method
*/
fun calculate(route: Route?, tail: String = ""): String
/**
* Used to handle any custom selectors that may be missed by the base route calculation
*/
fun handleCustomSelectors(route: Route, tail: String): String
private val pathHandler: RouteHandlerMap = mutableMapOf()
init {
addCustomRouteHandler(RootRouteSelector::class) { _, tail -> tail }
addCustomRouteHandler(PathSegmentParameterRouteSelector::class) { route, tail ->
val newTail = "/${route.selector}$tail"
calculate(route.parent, newTail)
}
addCustomRouteHandler(PathSegmentConstantRouteSelector::class) { route, tail ->
val newTail = "/${route.selector}$tail"
calculate(route.parent, newTail)
}
addCustomRouteHandler(TrailingSlashRouteSelector::class) { route, tail ->
val newTail = tail.ifBlank { "/" }
calculate(route.parent, newTail)
}
}
@OptIn(InternalAPI::class)
override fun calculate(
route: Route?,
tail: String
): String = when (route) {
null -> tail
else -> when (pathHandler.containsKey(route.selector::class)) {
true -> pathHandler[route.selector::class]!!.invoke(this, route, tail)
else -> error("No handler has been registered for ${route.selector}")
}
}
override fun <T : RouteSelector> addCustomRouteHandler(
selector: KClass<T>,
handler: IPathCalculator.(Route, String) -> String
) {
pathHandler[selector] = handler
}
}

View File

@ -0,0 +1,7 @@
package io.bkbn.kompendium.path
import io.ktor.routing.Route
import io.ktor.routing.RouteSelector
import kotlin.reflect.KClass
typealias RouteHandlerMap = MutableMap<KClass<out RouteSelector>, IPathCalculator.(Route, String) -> String>

View File

@ -43,6 +43,7 @@ import io.bkbn.kompendium.util.simpleGenericResponse
import io.bkbn.kompendium.util.statusPageModule
import io.bkbn.kompendium.util.statusPageMultiExceptions
import io.bkbn.kompendium.util.trailingSlash
import io.bkbn.kompendium.util.undeclaredType
import io.bkbn.kompendium.util.withDefaultParameter
import io.bkbn.kompendium.util.withExamples
@ -556,6 +557,22 @@ internal class KompendiumTest {
}
}
@Test
fun `Can add an undeclared field`() {
withTestApplication({
kotlinxConfigModule()
docs()
undeclaredType()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("undeclared_field.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
private val oas = Kompendium.openApiSpec.copy(
info = OpenApiSpecInfo(
title = "Test API",

View File

@ -4,6 +4,7 @@ import java.util.UUID
import io.bkbn.kompendium.annotations.KompendiumField
import io.bkbn.kompendium.annotations.KompendiumParam
import io.bkbn.kompendium.annotations.ParamType
import io.bkbn.kompendium.annotations.UndeclaredField
import java.math.BigDecimal
import java.math.BigInteger
@ -94,3 +95,11 @@ sealed interface Flibbity<T>
data class Gibbity<T>(val a: T): Flibbity<T>
data class Bibbity<T>(val b: String, val f: T) : Flibbity<T>
enum class Hehe {
HAHA,
HOHO
}
@UndeclaredField("nowYouDont", Hehe::class)
data class Mysterious(val nowYouSeeMe: String)

View File

@ -340,6 +340,16 @@ fun Application.genericPolymorphicResponseMultipleImpls() {
}
}
fun Application.undeclaredType() {
routing {
route("/test/polymorphic") {
notarizedGet(TestResponseInfo.undeclaredResponseType) {
call.respond(HttpStatusCode.OK, Mysterious("hi"))
}
}
}
}
fun Application.simpleGenericResponse() {
routing {
route("/test/polymorphic") {

View File

@ -103,6 +103,11 @@ object TestResponseInfo {
description = "Polymorphic with generics but like... crazier",
responseInfo = simpleOkResponse()
)
val undeclaredResponseType = GetInfo<Unit, Mysterious>(
summary = "spooky class",
description = "break this glass in scenario of emergency",
responseInfo = simpleOkResponse()
)
val genericResponse = GetInfo<Unit, TestGeneric<Int>>(
summary = "Single Generic",
description = "Simple generic data class",

View File

@ -0,0 +1,73 @@
{
"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/polymorphic" : {
"get" : {
"tags" : [ ],
"summary" : "spooky class",
"description" : "break this glass in scenario of emergency",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/Mysterious"
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"Hehe" : {
"type" : "string",
"enum" : [ "HAHA", "HOHO" ]
},
"Mysterious" : {
"type" : "object",
"properties" : {
"nowYouSeeMe" : {
"$ref" : "#/components/schemas/String"
},
"nowYouDont" : {
"$ref" : "#/components/schemas/Hehe"
}
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -19,6 +19,7 @@ import io.bkbn.kompendium.playground.PlaygroundToC.testSingleGetInfo
import io.bkbn.kompendium.playground.PlaygroundToC.testSingleGetInfoWithThrowable
import io.bkbn.kompendium.playground.PlaygroundToC.testSinglePostInfo
import io.bkbn.kompendium.playground.PlaygroundToC.testSinglePutInfo
import io.bkbn.kompendium.playground.PlaygroundToC.testUndeclaredFields
import io.bkbn.kompendium.routes.openApi
import io.bkbn.kompendium.routes.redoc
import io.bkbn.kompendium.swagger.swaggerUI
@ -138,5 +139,10 @@ fun Application.mainModule() {
error("bad things just happened")
}
}
route("/undeclared") {
notarizedGet(testUndeclaredFields) {
call.respondText { "hi" }
}
}
}
}

View File

@ -3,6 +3,7 @@ package io.bkbn.kompendium.playground
import io.bkbn.kompendium.annotations.KompendiumField
import io.bkbn.kompendium.annotations.KompendiumParam
import io.bkbn.kompendium.annotations.ParamType
import io.bkbn.kompendium.annotations.UndeclaredField
import org.joda.time.DateTime
data class ExampleParams(
@ -33,3 +34,11 @@ data class ExceptionResponse(val message: String)
data class ExampleCreatedResponse(val id: Int, val c: String)
data class DateTimeWrapper(val dt: DateTime)
enum class Testerino {
First,
Second
}
@UndeclaredField("type", Testerino::class)
data class SimpleYetMysterious(val exists: Boolean)

View File

@ -109,4 +109,13 @@ object PlaygroundToC {
),
securitySchemes = setOf("basic")
)
val testUndeclaredFields = MethodInfo.GetInfo<Unit, SimpleYetMysterious>(
summary = "Tests adding undeclared fields",
description = "vvv mysterious",
tags = setOf("mysterious"),
responseInfo = ResponseInfo(
status = HttpStatusCode.OK,
description = "good tings"
)
)
}

View File

@ -1,4 +1,5 @@
rootProject.name = "kompendium"
include("kompendium-core")
include("kompendium-auth")
include("kompendium-swagger-ui")