Feature/examplebodies (#44)

This commit is contained in:
Ryan Brink
2021-05-04 14:19:29 -04:00
committed by GitHub
parent ad33832ed3
commit 6b87519a64
23 changed files with 582 additions and 523 deletions

View File

@ -1,5 +1,15 @@
# Changelog # Changelog
## [0.8.0] - May 4th, 2021
### Added
- Support for example request and response bodies. Parameter examples / defaults are a separate issue for later.
### Changed
- Converted `MethodInfo` into a sealed class with distinct method types for Get, Post, Put, and Delete
## [0.7.0] - April 29th, 2021 ## [0.7.0] - April 29th, 2021
### Added ### Added
@ -10,6 +20,7 @@
### Changed ### Changed
- Refactored `kompendium-core` to break up the `Kompendium` object into slightly more manageable chunks - Refactored `kompendium-core` to break up the `Kompendium` object into slightly more manageable chunks
- Notarization Parameters can now be inferred from method info
## [0.6.2] - April 23rd, 2021 ## [0.6.2] - April 23rd, 2021

134
README.md
View File

@ -91,73 +91,45 @@ that a notarized route needs to analyze.
## Examples ## Examples
The full source code can be found in the `kompendium-playground` module. Here we show just the adjustments The full source code can be found in the `kompendium-playground` module. Here is a simple get endpoint example
needed to a standard Ktor server to get up and running in Kompendium.
```kotlin ```kotlin
// Minimal API Example // Minimal API Example
fun main() {
embeddedServer(
Netty,
port = 8081,
module = Application::mainModule
).start(wait = true)
}
fun Application.mainModule() { fun Application.mainModule() {
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
}
}
install(StatusPages) { install(StatusPages) {
notarizedException<Exception, ExceptionResponse>(exceptionResponseInfo) { notarizedException<Exception, ExceptionResponse>(
info = ResponseInfo(
KompendiumHttpCodes.BAD_REQUEST,
"Bad Things Happened"
)
) {
call.respond(HttpStatusCode.BadRequest, ExceptionResponse("Why you do dis?")) call.respond(HttpStatusCode.BadRequest, ExceptionResponse("Why you do dis?"))
} }
} }
routing { routing {
openApi() openApi(oas)
redoc() redoc(oas)
route("/test") { swaggerUI()
route("/{id}") { route("/potato/spud") {
notarizedGet<ExampleParams, ExampleResponse>(testIdGetInfo) { notarizedGet(simpleGetInfo) {
call.respondText("get by id") call.respond(HttpStatusCode.OK)
}
}
route("/single") {
notarizedGet<ExampleParams, ExampleResponse>(testSingleGetInfo) {
call.respondText("get single")
}
notarizedPost<Unit, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) {
call.respondText("test post")
}
notarizedPut<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) {
call.respondText { "hey" }
}
notarizedDelete<Unit, Unit>(testSingleDeleteInfo) {
call.respondText { "heya" }
}
}
route("/error") {
notarizedGet<Unit, ExampleResponse>(testSingleGetInfoWithThrowable) {
error("bad things just happened")
}
} }
} }
} }
} }
val testSingleGetInfoWithThrowable = testSingleGetInfo.copy( val simpleGetInfo = GetInfo<Unit, ExampleResponse>(
summary = "Show me the error baby 🙏", summary = "Example Parameters",
canThrow = setOf(Exception::class) // Must match an exception that has been notarized in the `StatusPages` description = "A test for setting parameter examples",
responseInfo = ResponseInfo(
status = 200,
description = "nice",
examples = mapOf("test" to ExampleResponse(c = "spud"))
),
canThrow = setOf(Exception::class)
) )
``` ```
When run in the playground, this would output the following at `/openapi.json`
https://gist.github.com/rgbrizzlehizzle/b9544922f2e99a2815177f8bdbf80668
### Kompendium Auth and security schemes ### Kompendium Auth and security schemes
There is a separate library to handle security schemes: `kompendium-auth`. There is a separate library to handle security schemes: `kompendium-auth`.
@ -167,42 +139,40 @@ At the moment, the basic and jwt authentication is only supported.
A minimal example would be: A minimal example would be:
```kotlin ```kotlin
install(Authentication) { install(Authentication) {
notarizedBasic("basic") { notarizedBasic("basic") {
realm = "Ktor realm 1" realm = "Ktor realm 1"
// configure basic authentication provider.. // configure basic authentication provider..
}
notarizedJwt("jwt") {
realm = "Ktor realm 2"
// configure jwt authentication provider...
}
} }
routing { notarizedJwt("jwt") {
authenticate("basic") { realm = "Ktor realm 2"
route("/basic_auth") { // configure jwt authentication provider...
notarizedGet<TestParams, TestResponse>( }
MethodInfo( }
// securitySchemes needs to be set routing {
"Another get test", "testing more", testGetResponse, securitySchemes = setOf("basic") authenticate("basic") {
) route("/basic_auth") {
) { notarizedGet(basicAuthGetInfo) {
call.respondText { "basic auth" } call.respondText { "basic auth" }
}
}
}
authenticate("jwt") {
route("/jwt") {
notarizedGet<TestParams, TestResponse>(
MethodInfo(
// securitySchemes needs to be set
"Another get test", "testing more", testGetResponse, securitySchemes = setOf("jwt")
)
) {
call.respondText { "jwt" }
}
} }
} }
} }
authenticate("jwt") {
route("/jwt") {
notarizedGet(jwtAuthGetInfo) {
call.respondText { "jwt" }
}
}
}
}
val basicAuthGetInfo = MethodInfo<Unit, ExampleResponse>(
summary = "Another get test",
description = "testing more",
responseInfo = testGetResponse,
securitySchemes = setOf("basic")
)
val jwtAuthGetInfo = basicAuthGetInfo.copy(securitySchemes = setOf("jwt"))
``` ```
### Enabling Swagger ui ### Enabling Swagger ui

View File

@ -81,8 +81,8 @@ complexity:
threshold: 80 threshold: 80
LongParameterList: LongParameterList:
active: true active: true
functionThreshold: 6 functionThreshold: 10
constructorThreshold: 7 constructorThreshold: 10
ignoreDefaultParameters: false ignoreDefaultParameters: false
ignoreDataClasses: true ignoreDataClasses: true
ignoreAnnotated: [] ignoreAnnotated: []

View File

@ -1,7 +1,7 @@
# Kompendium # Kompendium
project.version=0.7.0 project.version=0.8.0
# Kotlin # Kotlin
kotlin.code.style=official kotlin.code.style=official
# Gradle # Gradle
#org.gradle.vfs.watch=true org.gradle.vfs.watch=true
#org.gradle.vfs.verbose=true org.gradle.vfs.verbose=true

View File

@ -2,16 +2,26 @@ package org.leafygreens.kompendium.auth
import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.SerializationFeature
import io.ktor.application.* import io.ktor.application.Application
import io.ktor.auth.* import io.ktor.application.call
import io.ktor.features.* import io.ktor.application.install
import io.ktor.http.* import io.ktor.auth.Authentication
import io.ktor.jackson.* import io.ktor.auth.UserIdPrincipal
import io.ktor.response.* import io.ktor.auth.authenticate
import io.ktor.routing.* import io.ktor.features.ContentNegotiation
import io.ktor.server.testing.* import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.jackson.jackson
import io.ktor.response.respondText
import io.ktor.routing.route
import io.ktor.routing.routing
import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.withTestApplication
import kotlin.test.AfterTest
import kotlin.test.assertEquals
import org.junit.Test import org.junit.Test
import org.leafygreens.kompendium.Kompendium import org.leafygreens.kompendium.Kompendium
import org.leafygreens.kompendium.Notarized.notarizedGet
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedJwt import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedJwt
import org.leafygreens.kompendium.auth.util.TestData import org.leafygreens.kompendium.auth.util.TestData
@ -19,28 +29,17 @@ import org.leafygreens.kompendium.auth.util.TestParams
import org.leafygreens.kompendium.auth.util.TestResponse import org.leafygreens.kompendium.auth.util.TestResponse
import org.leafygreens.kompendium.models.meta.MethodInfo import org.leafygreens.kompendium.models.meta.MethodInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.routes.openApi import org.leafygreens.kompendium.routes.openApi
import org.leafygreens.kompendium.routes.redoc import org.leafygreens.kompendium.routes.redoc
import org.leafygreens.kompendium.util.KompendiumHttpCodes import org.leafygreens.kompendium.util.KompendiumHttpCodes
import kotlin.test.AfterTest
import kotlin.test.assertEquals
import org.leafygreens.kompendium.Notarized.notarizedGet
internal class KompendiumAuthTest { internal class KompendiumAuthTest {
@AfterTest @AfterTest
fun `reset kompendium`() { fun `reset kompendium`() {
Kompendium.openApiSpec = OpenApiSpec( Kompendium.resetSchema()
info = OpenApiSpecInfo(),
servers = mutableListOf(),
paths = mutableMapOf()
)
Kompendium.cache = emptyMap()
} }
@Test @Test
fun `Notarized Get with basic authentication records all expected information`() { fun `Notarized Get with basic authentication records all expected information`() {
withTestApplication({ withTestApplication({
@ -172,7 +171,7 @@ internal class KompendiumAuthTest {
routing { routing {
authenticate(*authenticationConfigName) { authenticate(*authenticationConfigName) {
route(TestData.getRoutePath) { route(TestData.getRoutePath) {
notarizedGet<TestParams, TestResponse>(testGetInfo(*authenticationConfigName)) { notarizedGet(testGetInfo(*authenticationConfigName)) {
call.respondText { "hey dude ‼️ congratz on the get request" } call.respondText { "hey dude ‼️ congratz on the get request" }
} }
} }
@ -190,8 +189,13 @@ internal class KompendiumAuthTest {
} }
private companion object { private companion object {
val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor") val testGetResponse = ResponseInfo<TestResponse>(KompendiumHttpCodes.OK, "A Successful Endeavor")
fun testGetInfo(vararg security: String) = fun testGetInfo(vararg security: String) =
MethodInfo("Another get test", "testing more", testGetResponse, securitySchemes = security.toSet()) MethodInfo.GetInfo<TestParams, TestResponse>(
summary = "Another get test",
description = "testing more",
responseInfo = testGetResponse,
securitySchemes = security.toSet()
)
} }
} }

View File

@ -4,11 +4,11 @@ import java.io.File
object TestData { object TestData {
object AuthConfigName { object AuthConfigName {
val Basic = "basic" const val Basic = "basic"
val JWT = "jwt" const val JWT = "jwt"
} }
val getRoutePath = "/test" const val getRoutePath = "/test"
fun getFileSnapshot(fileName: String): String { fun getFileSnapshot(fileName: String): String {
val snapshotPath = "src/test/resources" val snapshotPath = "src/test/resources"

View File

@ -16,6 +16,7 @@ import org.leafygreens.kompendium.models.meta.MethodInfo
import org.leafygreens.kompendium.models.meta.RequestInfo import org.leafygreens.kompendium.models.meta.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.meta.SchemaMap import org.leafygreens.kompendium.models.meta.SchemaMap
import org.leafygreens.kompendium.models.oas.ExampleWrapper
import org.leafygreens.kompendium.models.oas.OpenApiSpec import org.leafygreens.kompendium.models.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
@ -45,33 +46,37 @@ object Kompendium {
var pathCalculator: PathCalculator = CorePathCalculator() var pathCalculator: PathCalculator = CorePathCalculator()
// TODO here down is a mess, needs refactor once core functionality is in place // TODO here down is a mess, needs refactor once core functionality is in place
fun MethodInfo.parseMethodInfo( fun parseMethodInfo(
method: HttpMethod, info: MethodInfo<*, *>,
paramType: KType, paramType: KType,
requestType: KType, requestType: KType,
responseType: KType responseType: KType
) = OpenApiSpecPathItemOperation( ) = OpenApiSpecPathItemOperation(
summary = this.summary, summary = info.summary,
description = this.description, description = info.description,
tags = this.tags, tags = info.tags,
deprecated = this.deprecated, deprecated = info.deprecated,
parameters = paramType.toParameterSpec(), parameters = paramType.toParameterSpec(),
responses = responseType.toResponseSpec(responseInfo)?.let { mapOf(it) }.let { responses = responseType.toResponseSpec(info.responseInfo)?.let { mapOf(it) }.let {
when (it) { when (it) {
null -> { null -> {
val throwables = parseThrowables(canThrow) val throwables = parseThrowables(info.canThrow)
when (throwables.isEmpty()) { when (throwables.isEmpty()) {
true -> null true -> null
false -> throwables false -> throwables
} }
} }
else -> it.plus(parseThrowables(canThrow)) else -> it.plus(parseThrowables(info.canThrow))
} }
}, },
requestBody = if (method != HttpMethod.Get) requestType.toRequestSpec(requestInfo) else null, requestBody = when (info) {
security = if (this.securitySchemes.isNotEmpty()) listOf( is MethodInfo.PutInfo<*, *, *> -> requestType.toRequestSpec(info.requestInfo)
is MethodInfo.PostInfo<*, *, *> -> requestType.toRequestSpec(info.requestInfo)
else -> null
},
security = if (info.securitySchemes.isNotEmpty()) listOf(
// TODO support scopes // TODO support scopes
this.securitySchemes.associateWith { listOf() } info.securitySchemes.associateWith { listOf() }
) else null ) else null
) )
@ -79,7 +84,7 @@ object Kompendium {
errorMap[it.createType()] errorMap[it.createType()]
}.toMap() }.toMap()
fun ResponseInfo.parseErrorInfo( fun <TResp> ResponseInfo<TResp>.parseErrorInfo(
errorType: KType, errorType: KType,
responseType: KType responseType: KType
) { ) {
@ -87,37 +92,44 @@ object Kompendium {
} }
// TODO These two lookin' real similar 👀 Combine? // TODO These two lookin' real similar 👀 Combine?
private fun KType.toRequestSpec(requestInfo: RequestInfo?): OpenApiSpecRequest? = when (requestInfo) { private fun <TReq> KType.toRequestSpec(requestInfo: RequestInfo<TReq>?): OpenApiSpecRequest<TReq>? =
null -> null when (requestInfo) {
else -> { null -> null
OpenApiSpecRequest( else -> {
description = requestInfo.description, OpenApiSpecRequest(
content = resolveContent(requestInfo.mediaTypes) ?: mapOf() description = requestInfo.description,
) content = resolveContent(requestInfo.mediaTypes, requestInfo.examples) ?: mapOf()
)
}
} }
}
private fun KType.toResponseSpec(responseInfo: ResponseInfo?): Pair<Int, OpenApiSpecResponse>? = when (responseInfo) { private fun <TResp> KType.toResponseSpec(responseInfo: ResponseInfo<TResp>?): Pair<Int, OpenApiSpecResponse<TResp>>? =
null -> null // TODO again probably revisit this when (responseInfo) {
else -> { null -> null // TODO again probably revisit this
val specResponse = OpenApiSpecResponse( else -> {
description = responseInfo.description, val specResponse = OpenApiSpecResponse(
content = resolveContent(responseInfo.mediaTypes) description = responseInfo.description,
) content = resolveContent(responseInfo.mediaTypes, responseInfo.examples)
Pair(responseInfo.status, specResponse) )
Pair(responseInfo.status, specResponse)
}
} }
}
private fun KType.resolveContent(mediaTypes: List<String>): Map<String, OpenApiSpecMediaType>? { private fun <F> KType.resolveContent(
mediaTypes: List<String>,
examples: Map<String, F>
): Map<String, OpenApiSpecMediaType<F>>? {
return if (this != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) { return if (this != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) {
mediaTypes.associateWith { mediaTypes.associateWith {
val ref = getReferenceSlug() val ref = getReferenceSlug()
OpenApiSpecMediaType.Referenced(OpenApiSpecReferenceObject(ref)) OpenApiSpecMediaType(
schema = OpenApiSpecReferenceObject(ref),
examples = examples.mapValues { (_, v) -> ExampleWrapper(v) }.ifEmpty { null }
)
} }
} else null } else null
} }
// TODO God these annotations make this hideous... any way to improve? // TODO God these annotations make this hideous... any way to improve?
private fun KType.toParameterSpec(): List<OpenApiSpecParameter> { private fun KType.toParameterSpec(): List<OpenApiSpecParameter> {
val clazz = classifier as KClass<*> val clazz = classifier as KClass<*>
@ -151,7 +163,7 @@ object Kompendium {
} }
} }
internal fun resetSchema() { fun resetSchema() {
openApiSpec = OpenApiSpec( openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(), info = OpenApiSpecInfo(),
servers = mutableListOf(), servers = mutableListOf(),

View File

@ -15,10 +15,10 @@ import org.leafygreens.kompendium.models.oas.FormatSchema
import org.leafygreens.kompendium.models.oas.ObjectSchema import org.leafygreens.kompendium.models.oas.ObjectSchema
import org.leafygreens.kompendium.models.oas.ReferencedSchema import org.leafygreens.kompendium.models.oas.ReferencedSchema
import org.leafygreens.kompendium.models.oas.SimpleSchema import org.leafygreens.kompendium.models.oas.SimpleSchema
import org.leafygreens.kompendium.util.Helpers
import org.leafygreens.kompendium.util.Helpers.COMPONENT_SLUG import org.leafygreens.kompendium.util.Helpers.COMPONENT_SLUG
import org.leafygreens.kompendium.util.Helpers.genericNameAdapter import org.leafygreens.kompendium.util.Helpers.genericNameAdapter
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
import org.leafygreens.kompendium.util.Helpers.logged
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
object Kontent { object Kontent {
@ -45,7 +45,7 @@ object Kontent {
fun generateKTypeKontent( fun generateKTypeKontent(
type: KType, type: KType,
cache: SchemaMap = emptyMap() cache: SchemaMap = emptyMap()
): SchemaMap = Helpers.logged(object {}.javaClass.enclosingMethod.name, mapOf("cache" to cache)) { ): SchemaMap = logged(object {}.javaClass.enclosingMethod.name, mapOf("cache" to cache)) {
logger.debug("Parsing Kontent of $type") logger.debug("Parsing Kontent of $type")
when (val clazz = type.classifier as KClass<*>) { when (val clazz = type.classifier as KClass<*>) {
Unit::class -> cache Unit::class -> cache

View File

@ -10,7 +10,10 @@ import io.ktor.util.pipeline.PipelineInterceptor
import org.leafygreens.kompendium.Kompendium.parseErrorInfo import org.leafygreens.kompendium.Kompendium.parseErrorInfo
import org.leafygreens.kompendium.Kompendium.parseMethodInfo import org.leafygreens.kompendium.Kompendium.parseMethodInfo
import org.leafygreens.kompendium.KompendiumPreFlight.errorNotarizationPreFlight import org.leafygreens.kompendium.KompendiumPreFlight.errorNotarizationPreFlight
import org.leafygreens.kompendium.models.meta.MethodInfo import org.leafygreens.kompendium.models.meta.MethodInfo.GetInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PutInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.DeleteInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
@ -18,55 +21,52 @@ object Notarized {
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet( inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
info: MethodInfo, info: GetInfo<TParam, TResp>,
noinline body: PipelineInterceptor<Unit, ApplicationCall> noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = ): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType -> KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this) val path = Kompendium.pathCalculator.calculate(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.get = Kompendium.openApiSpec.paths[path]?.get = parseMethodInfo(info, paramType, requestType, responseType)
info.parseMethodInfo(HttpMethod.Get, paramType, requestType, responseType)
return method(HttpMethod.Get) { handle(body) } return method(HttpMethod.Get) { handle(body) }
} }
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost( inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost(
info: MethodInfo, info: PostInfo<TParam, TReq, TResp>,
noinline body: PipelineInterceptor<Unit, ApplicationCall> noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = ): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType -> KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this) val path = Kompendium.pathCalculator.calculate(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.post = Kompendium.openApiSpec.paths[path]?.post = parseMethodInfo(info, paramType, requestType, responseType)
info.parseMethodInfo(HttpMethod.Post, paramType, requestType, responseType)
return method(HttpMethod.Post) { handle(body) } return method(HttpMethod.Post) { handle(body) }
} }
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut( inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut(
info: MethodInfo, info: PutInfo<TParam, TReq, TResp>,
noinline body: PipelineInterceptor<Unit, ApplicationCall>, noinline body: PipelineInterceptor<Unit, ApplicationCall>,
): Route = ): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType -> KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this) val path = Kompendium.pathCalculator.calculate(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.put = Kompendium.openApiSpec.paths[path]?.put =
info.parseMethodInfo(HttpMethod.Put, paramType, requestType, responseType) parseMethodInfo(info, paramType, requestType, responseType)
return method(HttpMethod.Put) { handle(body) } return method(HttpMethod.Put) { handle(body) }
} }
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete( inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
info: MethodInfo, info: DeleteInfo<TParam, TResp>,
noinline body: PipelineInterceptor<Unit, ApplicationCall> noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = ): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType -> KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this) val path = Kompendium.pathCalculator.calculate(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.delete = Kompendium.openApiSpec.paths[path]?.delete = parseMethodInfo(info, paramType, requestType, responseType)
info.parseMethodInfo(HttpMethod.Delete, paramType, requestType, responseType)
return method(HttpMethod.Delete) { handle(body) } return method(HttpMethod.Delete) { handle(body) }
} }
inline fun <reified TErr : Throwable, reified TResp : Any> StatusPages.Configuration.notarizedException( inline fun <reified TErr : Throwable, reified TResp : Any> StatusPages.Configuration.notarizedException(
info: ResponseInfo, info: ResponseInfo<TResp>,
noinline handler: suspend PipelineContext<Unit, ApplicationCall>.(TErr) -> Unit noinline handler: suspend PipelineContext<Unit, ApplicationCall>.(TErr) -> Unit
) = errorNotarizationPreFlight<TErr, TResp>() { errorType, responseType -> ) = errorNotarizationPreFlight<TErr, TResp>() { errorType, responseType ->
info.parseErrorInfo(errorType, responseType) info.parseErrorInfo(errorType, responseType)

View File

@ -3,4 +3,4 @@ package org.leafygreens.kompendium.models.meta
import kotlin.reflect.KType import kotlin.reflect.KType
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
typealias ErrorMap = Map<KType, Pair<Int, OpenApiSpecResponse>?> typealias ErrorMap = Map<KType, Pair<Int, OpenApiSpecResponse<*>>?>

View File

@ -2,14 +2,94 @@ package org.leafygreens.kompendium.models.meta
import kotlin.reflect.KClass import kotlin.reflect.KClass
// TODO Seal and extend by method type? sealed class MethodInfo<TParam, TResp>(
data class MethodInfo( open val summary: String,
val summary: String, open val description: String? = null,
val description: String? = null, open val tags: Set<String> = emptySet(),
val responseInfo: ResponseInfo? = null, open val deprecated: Boolean = false,
val requestInfo: RequestInfo? = null, open val securitySchemes: Set<String> = emptySet(),
val tags: Set<String> = emptySet(), open val canThrow: Set<KClass<*>> = emptySet(),
val deprecated: Boolean = false, open val responseInfo: ResponseInfo<TResp>? = null,
val securitySchemes: Set<String> = emptySet(), open val parameterExamples: Map<String, TParam> = emptyMap(),
val canThrow: Set<KClass<*>> = emptySet() ) {
)
data class GetInfo<TParam, TResp>(
override val responseInfo: ResponseInfo<TResp>? = null,
override val summary: String,
override val description: String? = null,
override val tags: Set<String> = emptySet(),
override val deprecated: Boolean = false,
override val securitySchemes: Set<String> = emptySet(),
override val canThrow: Set<KClass<*>> = emptySet(),
override val parameterExamples: Map<String, TParam> = emptyMap()
) : MethodInfo<TParam, TResp>(
summary = summary,
description = description,
tags = tags,
deprecated = deprecated,
securitySchemes = securitySchemes,
canThrow = canThrow,
responseInfo = responseInfo,
parameterExamples = parameterExamples
)
data class PostInfo<TParam, TReq, TResp>(
val requestInfo: RequestInfo<TReq>? = null,
override val responseInfo: ResponseInfo<TResp>? = null,
override val summary: String,
override val description: String? = null,
override val tags: Set<String> = emptySet(),
override val deprecated: Boolean = false,
override val securitySchemes: Set<String> = emptySet(),
override val canThrow: Set<KClass<*>> = emptySet(),
override val parameterExamples: Map<String, TParam> = emptyMap()
) : MethodInfo<TParam, TResp>(
summary = summary,
description = description,
tags = tags,
deprecated = deprecated,
securitySchemes = securitySchemes,
canThrow = canThrow,
responseInfo = responseInfo,
parameterExamples = parameterExamples
)
data class PutInfo<TParam, TReq, TResp>(
val requestInfo: RequestInfo<TReq>? = null,
override val responseInfo: ResponseInfo<TResp>? = null,
override val summary: String,
override val description: String? = null,
override val tags: Set<String> = emptySet(),
override val deprecated: Boolean = false,
override val securitySchemes: Set<String> = emptySet(),
override val canThrow: Set<KClass<*>> = emptySet(),
override val parameterExamples: Map<String, TParam> = emptyMap()
) : MethodInfo<TParam, TResp>(
summary = summary,
description = description,
tags = tags,
deprecated = deprecated,
securitySchemes = securitySchemes,
canThrow = canThrow,
parameterExamples = parameterExamples
)
data class DeleteInfo<TParam, TResp>(
override val responseInfo: ResponseInfo<TResp>? = null,
override val summary: String,
override val description: String? = null,
override val tags: Set<String> = emptySet(),
override val deprecated: Boolean = false,
override val securitySchemes: Set<String> = emptySet(),
override val canThrow: Set<KClass<*>> = emptySet(),
override val parameterExamples: Map<String, TParam> = emptyMap()
) : MethodInfo<TParam, TResp>(
summary = summary,
description = description,
tags = tags,
deprecated = deprecated,
securitySchemes = securitySchemes,
canThrow = canThrow,
parameterExamples = parameterExamples
)
}

View File

@ -1,7 +1,8 @@
package org.leafygreens.kompendium.models.meta package org.leafygreens.kompendium.models.meta
data class RequestInfo( data class RequestInfo<TReq>(
val description: String, val description: String,
val required: Boolean = true, val required: Boolean = true,
val mediaTypes: List<String> = listOf("application/json") val mediaTypes: List<String> = listOf("application/json"),
val examples: Map<String, TReq> = emptyMap()
) )

View File

@ -1,7 +1,8 @@
package org.leafygreens.kompendium.models.meta package org.leafygreens.kompendium.models.meta
data class ResponseInfo( data class ResponseInfo<TResp>(
val status: Int, val status: Int,
val description: String, val description: String,
val mediaTypes: List<String> = listOf("application/json") val mediaTypes: List<String> = listOf("application/json"),
val examples: Map<String, TResp> = emptyMap()
) )

View File

@ -1,16 +1,8 @@
package org.leafygreens.kompendium.models.oas package org.leafygreens.kompendium.models.oas
// TODO Oof -> https://swagger.io/specification/#media-type-object data class OpenApiSpecMediaType<T>(
sealed class OpenApiSpecMediaType { val schema: OpenApiSpecReferenceObject,
data class Explicit( val examples: Map<String, ExampleWrapper<T>>? = null
val schema: OpenApiSpecSchema, // TODO sheesh -> https://swagger.io/specification/#schema-object )
val example: String? = null, // TODO Enforce type? then serialize?
val examples: Map<String, String>? = null, // needs to be mutually exclusive with example
val encoding: Map<String, String>? = null // todo encoding object -> https://swagger.io/specification/#encoding-object
) : OpenApiSpecMediaType()
data class Referenced(
val schema: OpenApiSpecReferenceObject
) : OpenApiSpecMediaType()
}
data class ExampleWrapper<T>(val value: T)

View File

@ -4,23 +4,13 @@ sealed class OpenApiSpecReferencable
class OpenApiSpecReferenceObject(val `$ref`: String) : OpenApiSpecReferencable() class OpenApiSpecReferenceObject(val `$ref`: String) : OpenApiSpecReferencable()
data class OpenApiSpecCallback( data class OpenApiSpecResponse<T>(
val todo: String // todo fuck me -> https://swagger.io/specification/#callback-object
) : OpenApiSpecReferencable()
data class OpenApiSpecResponse(
val description: String? = null, val description: String? = null,
val headers: Map<String, OpenApiSpecReferencable>? = null, val headers: Map<String, OpenApiSpecReferencable>? = null,
val content: Map<String, OpenApiSpecMediaType>? = null, val content: Map<String, OpenApiSpecMediaType<T>>? = null,
val links: Map<String, OpenApiSpecReferencable>? = null val links: Map<String, OpenApiSpecReferencable>? = null
) : OpenApiSpecReferencable() ) : OpenApiSpecReferencable()
data class OpenApiSpecHeader(
val name: String,
val description: String?,
val externalDocs: OpenApiSpecExternalDocumentation?
) : OpenApiSpecReferencable()
data class OpenApiSpecParameter( data class OpenApiSpecParameter(
val name: String, val name: String,
val `in`: String, // TODO Enum? "query", "header", "path" or "cookie" val `in`: String, // TODO Enum? "query", "header", "path" or "cookie"
@ -33,8 +23,8 @@ data class OpenApiSpecParameter(
val explode: Boolean? = null val explode: Boolean? = null
) : OpenApiSpecReferencable() ) : OpenApiSpecReferencable()
data class OpenApiSpecRequest( data class OpenApiSpecRequest<T>(
val description: String?, val description: String?,
val content: Map<String, OpenApiSpecMediaType>, val content: Map<String, OpenApiSpecMediaType<T>>,
val required: Boolean = false val required: Boolean = false
) : OpenApiSpecReferencable() ) : OpenApiSpecReferencable()

View File

@ -26,7 +26,10 @@ import org.leafygreens.kompendium.Notarized.notarizedGet
import org.leafygreens.kompendium.Notarized.notarizedPost import org.leafygreens.kompendium.Notarized.notarizedPost
import org.leafygreens.kompendium.Notarized.notarizedPut import org.leafygreens.kompendium.Notarized.notarizedPut
import org.leafygreens.kompendium.annotations.QueryParam import org.leafygreens.kompendium.annotations.QueryParam
import org.leafygreens.kompendium.models.meta.MethodInfo import org.leafygreens.kompendium.models.meta.MethodInfo.GetInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PutInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.DeleteInfo
import org.leafygreens.kompendium.models.meta.RequestInfo import org.leafygreens.kompendium.models.meta.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
@ -39,7 +42,8 @@ import org.leafygreens.kompendium.util.ComplexRequest
import org.leafygreens.kompendium.util.ExceptionResponse import org.leafygreens.kompendium.util.ExceptionResponse
import org.leafygreens.kompendium.util.KompendiumHttpCodes import org.leafygreens.kompendium.util.KompendiumHttpCodes
import org.leafygreens.kompendium.util.TestCreatedResponse import org.leafygreens.kompendium.util.TestCreatedResponse
import org.leafygreens.kompendium.util.TestData import org.leafygreens.kompendium.util.TestHelpers.getFileSnapshot
import org.leafygreens.kompendium.util.TestNested
import org.leafygreens.kompendium.util.TestParams import org.leafygreens.kompendium.util.TestParams
import org.leafygreens.kompendium.util.TestRequest import org.leafygreens.kompendium.util.TestRequest
import org.leafygreens.kompendium.util.TestResponse import org.leafygreens.kompendium.util.TestResponse
@ -67,7 +71,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("notarized_get.json").trim() val expected = getFileSnapshot("notarized_get.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -99,7 +103,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("notarized_post.json").trim() val expected = getFileSnapshot("notarized_post.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -131,7 +135,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("notarized_put.json").trim() val expected = getFileSnapshot("notarized_put.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -164,7 +168,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("notarized_delete.json").trim() val expected = getFileSnapshot("notarized_delete.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -195,7 +199,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("path_parser.json").trim() val expected = getFileSnapshot("path_parser.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -211,7 +215,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("root_route.json").trim() val expected = getFileSnapshot("root_route.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -243,7 +247,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("nested_under_root.json").trim() val expected = getFileSnapshot("nested_under_root.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -259,7 +263,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("trailing_slash.json").trim() val expected = getFileSnapshot("trailing_slash.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -291,7 +295,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("complex_type.json").trim() val expected = getFileSnapshot("complex_type.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -307,7 +311,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("notarized_primitives.json").trim() val expected = getFileSnapshot("notarized_primitives.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -323,7 +327,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("response_list.json").trim() val expected = getFileSnapshot("response_list.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -339,7 +343,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("no_request_params_and_no_response_body.json").trim() val expected = getFileSnapshot("no_request_params_and_no_response_body.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -355,7 +359,7 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("non_required_params.json").trim() val expected = getFileSnapshot("non_required_params.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@ -367,12 +371,11 @@ internal class KompendiumTest {
docs() docs()
returnsList() returnsList()
}) { }) {
// do // do
val html = handleRequest(HttpMethod.Get, "/docs").response.content val html = handleRequest(HttpMethod.Get, "/docs").response.content
// expected // expected
val expected = TestData.getFileSnapshot("redoc.html") val expected = getFileSnapshot("redoc.html")
assertEquals(expected, html) assertEquals(expected, html)
} }
} }
@ -389,13 +392,11 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("notarized_get_with_exception_response.json").trim() val expected = getFileSnapshot("notarized_get_with_exception_response.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
@Test @Test
fun `Generates additional responses when passed multiple throwables`() { fun `Generates additional responses when passed multiple throwables`() {
withTestApplication({ withTestApplication({
@ -408,28 +409,52 @@ internal class KompendiumTest {
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect // expect
val expected = TestData.getFileSnapshot("notarized_get_with_multiple_exception_responses.json").trim() val expected = getFileSnapshot("notarized_get_with_multiple_exception_responses.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Can generate example response and request bodies`() {
withTestApplication({
configModule()
docs()
withExamples()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("example_req_and_resp.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content") assertEquals(expected, json, "The received json spec should match the expected content")
} }
} }
private companion object { private companion object {
val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor") val testGetResponse = ResponseInfo<TestResponse>(KompendiumHttpCodes.OK, "A Successful Endeavor")
val testPostResponse = ResponseInfo(KompendiumHttpCodes.CREATED, "A Successful Endeavor") val testGetListResponse = ResponseInfo<List<TestResponse>>(KompendiumHttpCodes.OK, "A Successful List-y Endeavor")
val testPostResponse = ResponseInfo<TestCreatedResponse>(KompendiumHttpCodes.CREATED, "A Successful Endeavor")
val testPostResponseAgain = ResponseInfo<Boolean>(KompendiumHttpCodes.CREATED, "A Successful Endeavor")
val testDeleteResponse = val testDeleteResponse =
ResponseInfo(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor", mediaTypes = emptyList()) ResponseInfo<Unit>(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor", mediaTypes = emptyList())
val testRequest = RequestInfo("A Test request") val testRequest = RequestInfo<TestRequest>("A Test request")
val testGetInfo = MethodInfo("Another get test", "testing more", testGetResponse) val testRequestAgain = RequestInfo<Int>("A Test request")
val complexRequest = RequestInfo<ComplexRequest>("A Complex request")
val testGetInfo = GetInfo<TestParams, TestResponse>(summary = "Another get test", description = "testing more", responseInfo = testGetResponse)
val testGetInfoAgain = GetInfo<TestParams, List<TestResponse>>(summary = "Another get test", description = "testing more", responseInfo = testGetListResponse)
val testGetWithException = testGetInfo.copy( val testGetWithException = testGetInfo.copy(
canThrow = setOf(Exception::class) canThrow = setOf(Exception::class)
) )
val testGetWithMultipleExceptions = testGetInfo.copy( val testGetWithMultipleExceptions = testGetInfo.copy(
canThrow = setOf(AccessDeniedException::class, Exception::class) canThrow = setOf(AccessDeniedException::class, Exception::class)
) )
val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!", testPostResponse, testRequest) val testPostInfo = PostInfo<TestParams, TestRequest, TestCreatedResponse>(summary = "Test post endpoint", description = "Post your tests here!", responseInfo = testPostResponse, requestInfo = testRequest)
val testPutInfo = MethodInfo("Test put endpoint", "Put your tests here!", testPostResponse, testRequest) val testPutInfo = PutInfo<Unit, ComplexRequest, TestCreatedResponse>(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponse, requestInfo = complexRequest)
val testDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes", testDeleteResponse) val testPutInfoAlso = PutInfo<TestParams, TestRequest, TestCreatedResponse>(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponse, requestInfo = testRequest)
val emptyTestGetInfo = MethodInfo("No request params and response body", "testing more") val testPutInfoAgain = PutInfo<Unit, Int, Boolean>(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponseAgain, requestInfo = testRequestAgain)
val testDeleteInfo = DeleteInfo<TestParams, Unit>(summary = "Test delete endpoint", description = "testing my deletes", responseInfo = testDeleteResponse)
val emptyTestGetInfo = GetInfo<OptionalParams, Unit>(summary = "No request params and response body", description = "testing more")
val trulyEmptyTestGetInfo = GetInfo<Unit, Unit>(summary = "No request params and response body", description = "testing more")
} }
private fun Application.configModule() { private fun Application.configModule() {
@ -463,7 +488,7 @@ internal class KompendiumTest {
private fun Application.notarizedGetWithNotarizedException() { private fun Application.notarizedGetWithNotarizedException() {
routing { routing {
route("/test") { route("/test") {
notarizedGet<TestParams, TestResponse>(testGetWithException) { notarizedGet(testGetWithException) {
error("something terrible has happened!") error("something terrible has happened!")
} }
} }
@ -473,7 +498,7 @@ internal class KompendiumTest {
private fun Application.notarizedGetWithMultipleThrowables() { private fun Application.notarizedGetWithMultipleThrowables() {
routing { routing {
route("/test") { route("/test") {
notarizedGet<TestParams, TestResponse>(testGetWithMultipleExceptions) { notarizedGet(testGetWithMultipleExceptions) {
error("something terrible has happened!") error("something terrible has happened!")
} }
} }
@ -483,7 +508,7 @@ internal class KompendiumTest {
private fun Application.notarizedGetModule() { private fun Application.notarizedGetModule() {
routing { routing {
route("/test") { route("/test") {
notarizedGet<TestParams, TestResponse>(testGetInfo) { notarizedGet(testGetInfo) {
call.respondText { "hey dude ‼️ congratz on the get request" } call.respondText { "hey dude ‼️ congratz on the get request" }
} }
} }
@ -493,7 +518,7 @@ internal class KompendiumTest {
private fun Application.notarizedPostModule() { private fun Application.notarizedPostModule() {
routing { routing {
route("/test") { route("/test") {
notarizedPost<TestParams, TestRequest, TestCreatedResponse>(testPostInfo) { notarizedPost(testPostInfo) {
call.respondText { "hey dude ✌️ congratz on the post request" } call.respondText { "hey dude ✌️ congratz on the post request" }
} }
} }
@ -503,7 +528,7 @@ internal class KompendiumTest {
private fun Application.notarizedDeleteModule() { private fun Application.notarizedDeleteModule() {
routing { routing {
route("/test") { route("/test") {
notarizedDelete<TestParams, Unit>(testDeleteInfo) { notarizedDelete(testDeleteInfo) {
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }
} }
@ -513,7 +538,7 @@ internal class KompendiumTest {
private fun Application.notarizedPutModule() { private fun Application.notarizedPutModule() {
routing { routing {
route("/test") { route("/test") {
notarizedPut<TestParams, TestRequest, TestCreatedResponse>(testPutInfo) { notarizedPut(testPutInfoAlso) {
call.respondText { "hey pal 🌝 whatcha doin' here?" } call.respondText { "hey pal 🌝 whatcha doin' here?" }
} }
} }
@ -528,7 +553,7 @@ internal class KompendiumTest {
route("/complex") { route("/complex") {
route("path") { route("path") {
route("with/an/{id}") { route("with/an/{id}") {
notarizedGet<TestParams, TestResponse>(testGetInfo) { notarizedGet(testGetInfo) {
call.respondText { "Aww you followed this whole route 🥺" } call.respondText { "Aww you followed this whole route 🥺" }
} }
} }
@ -543,7 +568,7 @@ internal class KompendiumTest {
private fun Application.rootModule() { private fun Application.rootModule() {
routing { routing {
route("/") { route("/") {
notarizedGet<TestParams, TestResponse>(testGetInfo) { notarizedGet(testGetInfo) {
call.respondText { "☎️🏠🌲" } call.respondText { "☎️🏠🌲" }
} }
} }
@ -554,7 +579,7 @@ internal class KompendiumTest {
routing { routing {
route("/") { route("/") {
route("/testerino") { route("/testerino") {
notarizedGet<TestParams, TestResponse>(testGetInfo) { notarizedGet(testGetInfo) {
call.respondText { "🤔🔥" } call.respondText { "🤔🔥" }
} }
} }
@ -566,7 +591,7 @@ internal class KompendiumTest {
routing { routing {
route("/test") { route("/test") {
route("/") { route("/") {
notarizedGet<TestParams, TestResponse>(testGetInfo) { notarizedGet(testGetInfo) {
call.respondText { "🙀👾" } call.respondText { "🙀👾" }
} }
} }
@ -577,7 +602,7 @@ internal class KompendiumTest {
private fun Application.returnsList() { private fun Application.returnsList() {
routing { routing {
route("/test") { route("/test") {
notarizedGet<TestParams, List<TestResponse>>(testGetInfo) { notarizedGet(testGetInfoAgain) {
call.respondText { "hey dude ur doing amazing work!" } call.respondText { "hey dude ur doing amazing work!" }
} }
} }
@ -587,7 +612,7 @@ internal class KompendiumTest {
private fun Application.complexType() { private fun Application.complexType() {
routing { routing {
route("/test") { route("/test") {
notarizedPut<Unit, ComplexRequest, TestResponse>(testPutInfo) { notarizedPut(testPutInfo) {
call.respondText { "heya" } call.respondText { "heya" }
} }
} }
@ -597,7 +622,7 @@ internal class KompendiumTest {
private fun Application.primitives() { private fun Application.primitives() {
routing { routing {
route("/test") { route("/test") {
notarizedPut<Unit, Int, Boolean>(testPutInfo) { notarizedPut(testPutInfoAgain) {
call.respondText { "heya" } call.respondText { "heya" }
} }
} }
@ -607,7 +632,32 @@ internal class KompendiumTest {
private fun Application.emptyGet() { private fun Application.emptyGet() {
routing { routing {
route("/test/empty") { route("/test/empty") {
notarizedGet<Unit, Unit>(emptyTestGetInfo) { notarizedGet(trulyEmptyTestGetInfo) {
call.respond(HttpStatusCode.OK)
}
}
}
}
private fun Application.withExamples() {
routing {
route("/test/examples") {
notarizedPost(info = PostInfo<Unit, TestRequest, TestResponse>(
summary = "Example Parameters",
description = "A test for setting parameter examples",
requestInfo = RequestInfo(
description = "Test",
examples = mapOf(
"one" to TestRequest(fieldName = TestNested(nesty = "hey"), b = 4.0, aaa = emptyList()),
"two" to TestRequest(fieldName = TestNested(nesty = "hello"), b = 3.8, aaa = listOf(31324234))
)
),
responseInfo = ResponseInfo(
status = 201,
description = "nice",
examples = mapOf("test" to TestResponse(c = "spud"))
),
)) {
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)
} }
} }
@ -618,7 +668,7 @@ internal class KompendiumTest {
private fun Application.nonRequiredParamsGet() { private fun Application.nonRequiredParamsGet() {
routing { routing {
route("/test/optional") { route("/test/optional") {
notarizedGet<OptionalParams, Unit>(emptyTestGetInfo) { notarizedGet(emptyTestGetInfo) {
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)
} }
} }

View File

@ -1,27 +0,0 @@
package org.leafygreens.kompendium.models
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlin.test.Test
import kotlin.test.assertEquals
import org.leafygreens.kompendium.util.TestData
internal class OpenApiSpecTest {
private val mapper = jacksonObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.writerWithDefaultPrettyPrinter()
@Test
fun `OpenApiSpec can be serialized into a valid Open API Spec`() {
// when
val spec = TestData.testSpec
// do
val json = mapper.writeValueAsString(spec)
// expect
val expected = TestData.getFileSnapshot("petstore.json").trim()
assertEquals(expected, json, "Should serialize an empty spec")
}
}

View File

@ -1,227 +0,0 @@
package org.leafygreens.kompendium.util
import java.io.File
import java.net.URI
import org.leafygreens.kompendium.models.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponents
import org.leafygreens.kompendium.models.oas.OpenApiSpecExternalDocumentation
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
import org.leafygreens.kompendium.models.oas.OpenApiSpecOAuthFlow
import org.leafygreens.kompendium.models.oas.OpenApiSpecOAuthFlows
import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaArray
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaRef
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaSecurity
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaString
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
import org.leafygreens.kompendium.models.oas.OpenApiSpecTag
object TestData {
fun getFileSnapshot(fileName: String): String {
val snapshotPath = "src/test/resources"
val file = File("$snapshotPath/$fileName")
return file.readText()
}
val testSpec = OpenApiSpec(
info = OpenApiSpecInfo(
title = "Swagger Petstore",
description = """
This is a sample server Petstore server. You can find out more about Swagger at
[http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).
For this sample, you can use the api key `special-key` to test the authorization filters.
""".trimIndent(),
termsOfService = URI("http://swagger.io/terms/"),
contact = OpenApiSpecInfoContact(
name = "Team Swag",
email = "apiteam@swagger.io"
),
license = OpenApiSpecInfoLicense(
name = "Apache 2.0",
url = URI("http://www.apache.org/licenses/LICENSE-2.0.html")
),
version = "1.0.0"
),
externalDocs = OpenApiSpecExternalDocumentation(
description = "Find out more about Swagger",
url = URI("http://swagger.io")
),
servers = mutableListOf(
OpenApiSpecServer(
url = URI("https://petstore.swagger.io/v2")
),
OpenApiSpecServer(
url = URI("http://petstore.swagger.io/v2")
)
),
tags = mutableListOf(
OpenApiSpecTag(
name = "pet",
description = "Everything about your Pets",
externalDocs = OpenApiSpecExternalDocumentation(
description = "Find out more",
url = URI("http://swagger.io")
)
),
OpenApiSpecTag(
name = "store",
description = "Access to Petstore orders"
),
OpenApiSpecTag(
name = "user",
description = "Operations about user",
externalDocs = OpenApiSpecExternalDocumentation(
description = "Find out more about our store",
url = URI("http://swagger.io")
)
)
),
paths = mutableMapOf(
"/pet" to OpenApiSpecPathItem(
put = OpenApiSpecPathItemOperation(
tags = setOf("pet"),
summary = "Update an existing pet",
operationId = "updatePet",
requestBody = OpenApiSpecRequest(
description = "Pet object that needs to be added to the store",
content = mapOf(
"application/json" to OpenApiSpecMediaType.Explicit(
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
),
"application/xml" to OpenApiSpecMediaType.Explicit(
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
)
),
required = true
),
responses = mapOf(
400 to OpenApiSpecResponse(
description = "Invalid ID supplied",
content = emptyMap()
),
404 to OpenApiSpecResponse(
description = "Pet not found",
content = emptyMap()
),
405 to OpenApiSpecResponse(
description = "Validation exception",
content = emptyMap()
)
),
security = listOf(
mapOf(
"petstore_auth" to listOf("write:pets", "read:pets")
)
),
`x-codegen-request-body-name` = "body"
),
post = OpenApiSpecPathItemOperation(
tags = setOf("pet"),
summary = "Add a new pet to the store",
operationId = "addPet",
requestBody = OpenApiSpecRequest(
description = "Pet object that needs to be added to the store",
content = mapOf(
"application/json" to OpenApiSpecMediaType.Referenced(
schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet")
),
"application/xml" to OpenApiSpecMediaType.Referenced(
schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet")
)
)
),
responses = mapOf(
405 to OpenApiSpecResponse(
description = "Invalid Input",
content = emptyMap()
)
),
security = listOf(
mapOf(
"petstore_auth" to listOf("write:pets", "read:pets")
)
),
`x-codegen-request-body-name` = "body"
)
),
"/pet/findByStatus" to OpenApiSpecPathItem(
get = OpenApiSpecPathItemOperation(
tags = setOf("pet"),
summary = "Find Pets by status",
description = "Multiple status values can be provided with comma separated strings",
operationId = "findPetsByStatus",
parameters = listOf(
OpenApiSpecParameter(
name = "status",
`in` = "query",
description = "Status values that need to be considered for filter",
required = true,
style = "form",
explode = true,
schema = OpenApiSpecSchemaArray(
items = OpenApiSpecSchemaString(
default = "available",
`enum` = setOf("available", "pending", "sold")
)
)
)
),
responses = mapOf(
200 to OpenApiSpecResponse(
description = "successful operation",
content = mapOf(
"application/xml" to OpenApiSpecMediaType.Explicit(
schema = OpenApiSpecSchemaArray(
items = OpenApiSpecSchemaRef("#/components/schemas/Pet")
)
),
"application/json" to OpenApiSpecMediaType.Explicit(
schema = OpenApiSpecSchemaArray(
items = OpenApiSpecSchemaRef("#/components/schemas/Pet")
)
)
)
),
400 to OpenApiSpecResponse(
description = "Invalid status value",
content = mapOf()
)
),
security = listOf(mapOf(
"petstore_auth" to listOf("write:pets", "read:pets")
))
)
)
),
components = OpenApiSpecComponents(
securitySchemes = mutableMapOf(
"petstore_auth" to OpenApiSpecSchemaSecurity(
type = "oauth2",
flows = OpenApiSpecOAuthFlows(
implicit = OpenApiSpecOAuthFlow(
authorizationUrl = URI("http://petstore.swagger.io/oauth/dialog"),
scopes = mapOf(
"write:pets" to "modify pets in your account",
"read:pets" to "read your pets"
)
)
)
),
"api_key" to OpenApiSpecSchemaSecurity(
type = "apiKey",
name = "api_key",
`in` = "header"
)
),
schemas = mutableMapOf()
)
)
}

View File

@ -0,0 +1,33 @@
package org.leafygreens.kompendium.util
import java.io.File
import java.net.URI
import org.leafygreens.kompendium.models.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponents
import org.leafygreens.kompendium.models.oas.OpenApiSpecExternalDocumentation
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
import org.leafygreens.kompendium.models.oas.OpenApiSpecOAuthFlow
import org.leafygreens.kompendium.models.oas.OpenApiSpecOAuthFlows
import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaArray
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaRef
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaSecurity
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaString
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
import org.leafygreens.kompendium.models.oas.OpenApiSpecTag
object TestHelpers {
fun getFileSnapshot(fileName: String): String {
val snapshotPath = "src/test/resources"
val file = File("$snapshotPath/$fileName")
return file.readText()
}
}

View File

@ -30,7 +30,7 @@
"description" : "Put your tests here!", "description" : "Put your tests here!",
"parameters" : [ ], "parameters" : [ ],
"requestBody" : { "requestBody" : {
"description" : "A Test request", "description" : "A Complex request",
"content" : { "content" : {
"application/json" : { "application/json" : {
"schema" : { "schema" : {
@ -46,7 +46,7 @@
"content" : { "content" : {
"application/json" : { "application/json" : {
"schema" : { "schema" : {
"$ref" : "#/components/schemas/TestResponse" "$ref" : "#/components/schemas/TestCreatedResponse"
} }
} }
} }
@ -61,10 +61,17 @@
"String" : { "String" : {
"type" : "string" "type" : "string"
}, },
"TestResponse" : { "Int" : {
"format" : "int32",
"type" : "integer"
},
"TestCreatedResponse" : {
"properties" : { "properties" : {
"c" : { "c" : {
"$ref" : "#/components/schemas/String" "$ref" : "#/components/schemas/String"
},
"id" : {
"$ref" : "#/components/schemas/Int"
} }
}, },
"type" : "object" "type" : "object"

View File

@ -0,0 +1,140 @@
{
"openapi" : "3.0.3",
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 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/lg-backbone/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/examples" : {
"post" : {
"tags" : [ ],
"summary" : "Example Parameters",
"description" : "A test for setting parameter examples",
"parameters" : [ ],
"requestBody" : {
"description" : "Test",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/TestRequest"
},
"examples" : {
"one" : {
"value" : {
"fieldName" : {
"nesty" : "hey"
},
"b" : 4.0,
"aaa" : [ ]
}
},
"two" : {
"value" : {
"fieldName" : {
"nesty" : "hello"
},
"b" : 3.8,
"aaa" : [ 31324234 ]
}
}
}
}
},
"required" : false
},
"responses" : {
"201" : {
"description" : "nice",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/TestResponse"
},
"examples" : {
"test" : {
"value" : {
"c" : "spud"
}
}
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"TestResponse" : {
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
},
"Long" : {
"format" : "int64",
"type" : "integer"
},
"List-Long" : {
"items" : {
"$ref" : "#/components/schemas/Long"
},
"type" : "array"
},
"Double" : {
"format" : "double",
"type" : "number"
},
"TestNested" : {
"properties" : {
"nesty" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
},
"TestRequest" : {
"properties" : {
"aaa" : {
"$ref" : "#/components/schemas/List-Long"
},
"b" : {
"$ref" : "#/components/schemas/Double"
},
"fieldName" : {
"$ref" : "#/components/schemas/TestNested"
}
},
"type" : "object"
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -47,7 +47,7 @@
} ], } ],
"responses" : { "responses" : {
"200" : { "200" : {
"description" : "A Successful Endeavor", "description" : "A Successful List-y Endeavor",
"content" : { "content" : {
"application/json" : { "application/json" : {
"schema" : { "schema" : {

View File

@ -30,7 +30,10 @@ import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.PathParam import org.leafygreens.kompendium.annotations.PathParam
import org.leafygreens.kompendium.annotations.QueryParam import org.leafygreens.kompendium.annotations.QueryParam
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic
import org.leafygreens.kompendium.models.meta.MethodInfo import org.leafygreens.kompendium.models.meta.MethodInfo.GetInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PutInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.DeleteInfo
import org.leafygreens.kompendium.models.meta.RequestInfo import org.leafygreens.kompendium.models.meta.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
@ -38,6 +41,7 @@ import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
import org.leafygreens.kompendium.playground.KompendiumTOC.testAuthenticatedSingleGetInfo import org.leafygreens.kompendium.playground.KompendiumTOC.testAuthenticatedSingleGetInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testGetWithExamples
import org.leafygreens.kompendium.playground.KompendiumTOC.testIdGetInfo import org.leafygreens.kompendium.playground.KompendiumTOC.testIdGetInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleDeleteInfo import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleDeleteInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo
@ -86,8 +90,8 @@ fun main() {
} }
var featuresInstalled = false var featuresInstalled = false
fun Application.mainModule() {
// only install once in case of auto reload fun Application.configModule() {
if (!featuresInstalled) { if (!featuresInstalled) {
install(ContentNegotiation) { install(ContentNegotiation) {
jackson { jackson {
@ -120,33 +124,42 @@ fun Application.mainModule() {
} }
featuresInstalled = true featuresInstalled = true
} }
}
fun Application.mainModule() {
configModule()
routing { routing {
openApi(oas) openApi(oas)
redoc(oas) redoc(oas)
swaggerUI() swaggerUI()
route("/potato/spud") {
notarizedGet(testGetWithExamples) {
call.respond(HttpStatusCode.OK)
}
}
route("/test") { route("/test") {
route("/{id}") { route("/{id}") {
notarizedGet<ExampleParams, ExampleResponse>(testIdGetInfo) { notarizedGet(testIdGetInfo) {
call.respondText("get by id") call.respondText("get by id")
} }
} }
route("/single") { route("/single") {
notarizedGet<Unit, ExampleResponse>(testSingleGetInfo) { notarizedGet(testSingleGetInfo) {
call.respondText("get single") call.respondText("get single")
} }
notarizedPost<Unit, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) { notarizedPost(testSinglePostInfo) {
call.respondText("test post") call.respondText("test post")
} }
notarizedPut<JustQuery, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) { notarizedPut(testSinglePutInfo) {
call.respondText { "hey" } call.respondText { "hey" }
} }
notarizedDelete<Unit, Unit>(testSingleDeleteInfo) { notarizedDelete(testSingleDeleteInfo) {
call.respondText { "heya" } call.respondText { "heya" }
} }
} }
authenticate("basic") { authenticate("basic") {
route("/authenticated/single") { route("/authenticated/single") {
notarizedGet<Unit, Unit>(testAuthenticatedSingleGetInfo) { notarizedGet(testAuthenticatedSingleGetInfo) {
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)
} }
} }
@ -186,7 +199,16 @@ data class ExceptionResponse(val message: String)
data class ExampleCreatedResponse(val id: Int, val c: String) data class ExampleCreatedResponse(val id: Int, val c: String)
object KompendiumTOC { object KompendiumTOC {
val testIdGetInfo = MethodInfo( val testGetWithExamples = GetInfo<Unit, ExampleResponse>(
summary = "Example Parameters",
description = "A test for setting parameter examples",
responseInfo = ResponseInfo(
status = 200,
description = "nice",
examples = mapOf("test" to ExampleResponse(c = "spud"))
),
)
val testIdGetInfo = GetInfo<ExampleParams, ExampleResponse>(
summary = "Get Test", summary = "Get Test",
description = "Test for the getting", description = "Test for the getting",
tags = setOf("test", "sample", "get"), tags = setOf("test", "sample", "get"),
@ -195,7 +217,7 @@ object KompendiumTOC {
description = "Returns sample info" description = "Returns sample info"
) )
) )
val testSingleGetInfo = MethodInfo( val testSingleGetInfo = GetInfo<Unit, ExampleResponse>(
summary = "Another get test", summary = "Another get test",
description = "testing more", description = "testing more",
tags = setOf("anotherTest", "sample"), tags = setOf("anotherTest", "sample"),
@ -208,7 +230,7 @@ object KompendiumTOC {
summary = "Show me the error baby 🙏", summary = "Show me the error baby 🙏",
canThrow = setOf(Exception::class) canThrow = setOf(Exception::class)
) )
val testSinglePostInfo = MethodInfo( val testSinglePostInfo = PostInfo<Unit, ExampleRequest, ExampleCreatedResponse>(
summary = "Test post endpoint", summary = "Test post endpoint",
description = "Post your tests here!", description = "Post your tests here!",
requestInfo = RequestInfo( requestInfo = RequestInfo(
@ -219,7 +241,7 @@ object KompendiumTOC {
description = "Worlds most complex response" description = "Worlds most complex response"
) )
) )
val testSinglePutInfo = MethodInfo( val testSinglePutInfo = PutInfo<JustQuery, ExampleRequest, ExampleCreatedResponse>(
summary = "Test put endpoint", summary = "Test put endpoint",
description = "Put your tests here!", description = "Put your tests here!",
requestInfo = RequestInfo( requestInfo = RequestInfo(
@ -230,7 +252,7 @@ object KompendiumTOC {
description = "What we give you when u do the puts" description = "What we give you when u do the puts"
) )
) )
val testSingleDeleteInfo = MethodInfo( val testSingleDeleteInfo = DeleteInfo<Unit, Unit>(
summary = "Test delete endpoint", summary = "Test delete endpoint",
description = "testing my deletes", description = "testing my deletes",
responseInfo = ResponseInfo( responseInfo = ResponseInfo(
@ -239,7 +261,7 @@ object KompendiumTOC {
mediaTypes = emptyList() mediaTypes = emptyList()
) )
) )
val testAuthenticatedSingleGetInfo = MethodInfo( val testAuthenticatedSingleGetInfo = GetInfo<Unit, Unit>(
summary = "Another get test", summary = "Another get test",
description = "testing more", description = "testing more",
tags = setOf("anotherTest", "sample"), tags = setOf("anotherTest", "sample"),