Feature/examplebodies (#44)
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,15 @@
|
||||
# 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
|
||||
|
||||
### Added
|
||||
@ -10,6 +20,7 @@
|
||||
### Changed
|
||||
|
||||
- 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
|
||||
|
||||
|
134
README.md
134
README.md
@ -91,73 +91,45 @@ that a notarized route needs to analyze.
|
||||
|
||||
## Examples
|
||||
|
||||
The full source code can be found in the `kompendium-playground` module. Here we show just the adjustments
|
||||
needed to a standard Ktor server to get up and running in Kompendium.
|
||||
The full source code can be found in the `kompendium-playground` module. Here is a simple get endpoint example
|
||||
|
||||
```kotlin
|
||||
// Minimal API Example
|
||||
fun main() {
|
||||
embeddedServer(
|
||||
Netty,
|
||||
port = 8081,
|
||||
module = Application::mainModule
|
||||
).start(wait = true)
|
||||
}
|
||||
|
||||
fun Application.mainModule() {
|
||||
install(ContentNegotiation) {
|
||||
jackson {
|
||||
enable(SerializationFeature.INDENT_OUTPUT)
|
||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
}
|
||||
}
|
||||
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?"))
|
||||
}
|
||||
}
|
||||
routing {
|
||||
openApi()
|
||||
redoc()
|
||||
route("/test") {
|
||||
route("/{id}") {
|
||||
notarizedGet<ExampleParams, ExampleResponse>(testIdGetInfo) {
|
||||
call.respondText("get by id")
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
openApi(oas)
|
||||
redoc(oas)
|
||||
swaggerUI()
|
||||
route("/potato/spud") {
|
||||
notarizedGet(simpleGetInfo) {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val testSingleGetInfoWithThrowable = testSingleGetInfo.copy(
|
||||
summary = "Show me the error baby 🙏",
|
||||
canThrow = setOf(Exception::class) // Must match an exception that has been notarized in the `StatusPages`
|
||||
val simpleGetInfo = 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"))
|
||||
),
|
||||
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
|
||||
|
||||
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:
|
||||
```kotlin
|
||||
install(Authentication) {
|
||||
notarizedBasic("basic") {
|
||||
realm = "Ktor realm 1"
|
||||
// configure basic authentication provider..
|
||||
}
|
||||
notarizedJwt("jwt") {
|
||||
realm = "Ktor realm 2"
|
||||
// configure jwt authentication provider...
|
||||
}
|
||||
install(Authentication) {
|
||||
notarizedBasic("basic") {
|
||||
realm = "Ktor realm 1"
|
||||
// configure basic authentication provider..
|
||||
}
|
||||
routing {
|
||||
authenticate("basic") {
|
||||
route("/basic_auth") {
|
||||
notarizedGet<TestParams, TestResponse>(
|
||||
MethodInfo(
|
||||
// securitySchemes needs to be set
|
||||
"Another get test", "testing more", testGetResponse, securitySchemes = setOf("basic")
|
||||
)
|
||||
) {
|
||||
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" }
|
||||
}
|
||||
notarizedJwt("jwt") {
|
||||
realm = "Ktor realm 2"
|
||||
// configure jwt authentication provider...
|
||||
}
|
||||
}
|
||||
routing {
|
||||
authenticate("basic") {
|
||||
route("/basic_auth") {
|
||||
notarizedGet(basicAuthGetInfo) {
|
||||
call.respondText { "basic auth" }
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
@ -81,8 +81,8 @@ complexity:
|
||||
threshold: 80
|
||||
LongParameterList:
|
||||
active: true
|
||||
functionThreshold: 6
|
||||
constructorThreshold: 7
|
||||
functionThreshold: 10
|
||||
constructorThreshold: 10
|
||||
ignoreDefaultParameters: false
|
||||
ignoreDataClasses: true
|
||||
ignoreAnnotated: []
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Kompendium
|
||||
project.version=0.7.0
|
||||
project.version=0.8.0
|
||||
# Kotlin
|
||||
kotlin.code.style=official
|
||||
# Gradle
|
||||
#org.gradle.vfs.watch=true
|
||||
#org.gradle.vfs.verbose=true
|
||||
org.gradle.vfs.watch=true
|
||||
org.gradle.vfs.verbose=true
|
||||
|
@ -2,16 +2,26 @@ package org.leafygreens.kompendium.auth
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.jackson.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.server.testing.*
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
import io.ktor.auth.Authentication
|
||||
import io.ktor.auth.UserIdPrincipal
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.features.ContentNegotiation
|
||||
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.leafygreens.kompendium.Kompendium
|
||||
import org.leafygreens.kompendium.Notarized.notarizedGet
|
||||
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic
|
||||
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedJwt
|
||||
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.models.meta.MethodInfo
|
||||
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.redoc
|
||||
import org.leafygreens.kompendium.util.KompendiumHttpCodes
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.assertEquals
|
||||
import org.leafygreens.kompendium.Notarized.notarizedGet
|
||||
|
||||
internal class KompendiumAuthTest {
|
||||
|
||||
@AfterTest
|
||||
fun `reset kompendium`() {
|
||||
Kompendium.openApiSpec = OpenApiSpec(
|
||||
info = OpenApiSpecInfo(),
|
||||
servers = mutableListOf(),
|
||||
paths = mutableMapOf()
|
||||
)
|
||||
Kompendium.cache = emptyMap()
|
||||
Kompendium.resetSchema()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with basic authentication records all expected information`() {
|
||||
withTestApplication({
|
||||
@ -172,7 +171,7 @@ internal class KompendiumAuthTest {
|
||||
routing {
|
||||
authenticate(*authenticationConfigName) {
|
||||
route(TestData.getRoutePath) {
|
||||
notarizedGet<TestParams, TestResponse>(testGetInfo(*authenticationConfigName)) {
|
||||
notarizedGet(testGetInfo(*authenticationConfigName)) {
|
||||
call.respondText { "hey dude ‼️ congratz on the get request" }
|
||||
}
|
||||
}
|
||||
@ -190,8 +189,13 @@ internal class KompendiumAuthTest {
|
||||
}
|
||||
|
||||
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) =
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ import java.io.File
|
||||
|
||||
object TestData {
|
||||
object AuthConfigName {
|
||||
val Basic = "basic"
|
||||
val JWT = "jwt"
|
||||
const val Basic = "basic"
|
||||
const val JWT = "jwt"
|
||||
}
|
||||
|
||||
val getRoutePath = "/test"
|
||||
const val getRoutePath = "/test"
|
||||
|
||||
fun getFileSnapshot(fileName: String): String {
|
||||
val snapshotPath = "src/test/resources"
|
||||
|
@ -16,6 +16,7 @@ import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||
import org.leafygreens.kompendium.models.meta.RequestInfo
|
||||
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
||||
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.OpenApiSpecInfo
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
|
||||
@ -45,33 +46,37 @@ object Kompendium {
|
||||
var pathCalculator: PathCalculator = CorePathCalculator()
|
||||
|
||||
// TODO here down is a mess, needs refactor once core functionality is in place
|
||||
fun MethodInfo.parseMethodInfo(
|
||||
method: HttpMethod,
|
||||
fun parseMethodInfo(
|
||||
info: MethodInfo<*, *>,
|
||||
paramType: KType,
|
||||
requestType: KType,
|
||||
responseType: KType
|
||||
) = OpenApiSpecPathItemOperation(
|
||||
summary = this.summary,
|
||||
description = this.description,
|
||||
tags = this.tags,
|
||||
deprecated = this.deprecated,
|
||||
summary = info.summary,
|
||||
description = info.description,
|
||||
tags = info.tags,
|
||||
deprecated = info.deprecated,
|
||||
parameters = paramType.toParameterSpec(),
|
||||
responses = responseType.toResponseSpec(responseInfo)?.let { mapOf(it) }.let {
|
||||
responses = responseType.toResponseSpec(info.responseInfo)?.let { mapOf(it) }.let {
|
||||
when (it) {
|
||||
null -> {
|
||||
val throwables = parseThrowables(canThrow)
|
||||
val throwables = parseThrowables(info.canThrow)
|
||||
when (throwables.isEmpty()) {
|
||||
true -> null
|
||||
false -> throwables
|
||||
}
|
||||
}
|
||||
else -> it.plus(parseThrowables(canThrow))
|
||||
else -> it.plus(parseThrowables(info.canThrow))
|
||||
}
|
||||
},
|
||||
requestBody = if (method != HttpMethod.Get) requestType.toRequestSpec(requestInfo) else null,
|
||||
security = if (this.securitySchemes.isNotEmpty()) listOf(
|
||||
requestBody = when (info) {
|
||||
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
|
||||
this.securitySchemes.associateWith { listOf() }
|
||||
info.securitySchemes.associateWith { listOf() }
|
||||
) else null
|
||||
)
|
||||
|
||||
@ -79,7 +84,7 @@ object Kompendium {
|
||||
errorMap[it.createType()]
|
||||
}.toMap()
|
||||
|
||||
fun ResponseInfo.parseErrorInfo(
|
||||
fun <TResp> ResponseInfo<TResp>.parseErrorInfo(
|
||||
errorType: KType,
|
||||
responseType: KType
|
||||
) {
|
||||
@ -87,37 +92,44 @@ object Kompendium {
|
||||
}
|
||||
|
||||
// TODO These two lookin' real similar 👀 Combine?
|
||||
private fun KType.toRequestSpec(requestInfo: RequestInfo?): OpenApiSpecRequest? = when (requestInfo) {
|
||||
null -> null
|
||||
else -> {
|
||||
OpenApiSpecRequest(
|
||||
description = requestInfo.description,
|
||||
content = resolveContent(requestInfo.mediaTypes) ?: mapOf()
|
||||
)
|
||||
private fun <TReq> KType.toRequestSpec(requestInfo: RequestInfo<TReq>?): OpenApiSpecRequest<TReq>? =
|
||||
when (requestInfo) {
|
||||
null -> null
|
||||
else -> {
|
||||
OpenApiSpecRequest(
|
||||
description = requestInfo.description,
|
||||
content = resolveContent(requestInfo.mediaTypes, requestInfo.examples) ?: mapOf()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KType.toResponseSpec(responseInfo: ResponseInfo?): Pair<Int, OpenApiSpecResponse>? = when (responseInfo) {
|
||||
null -> null // TODO again probably revisit this
|
||||
else -> {
|
||||
val specResponse = OpenApiSpecResponse(
|
||||
description = responseInfo.description,
|
||||
content = resolveContent(responseInfo.mediaTypes)
|
||||
)
|
||||
Pair(responseInfo.status, specResponse)
|
||||
private fun <TResp> KType.toResponseSpec(responseInfo: ResponseInfo<TResp>?): Pair<Int, OpenApiSpecResponse<TResp>>? =
|
||||
when (responseInfo) {
|
||||
null -> null // TODO again probably revisit this
|
||||
else -> {
|
||||
val specResponse = OpenApiSpecResponse(
|
||||
description = responseInfo.description,
|
||||
content = resolveContent(responseInfo.mediaTypes, responseInfo.examples)
|
||||
)
|
||||
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()) {
|
||||
mediaTypes.associateWith {
|
||||
val ref = getReferenceSlug()
|
||||
OpenApiSpecMediaType.Referenced(OpenApiSpecReferenceObject(ref))
|
||||
OpenApiSpecMediaType(
|
||||
schema = OpenApiSpecReferenceObject(ref),
|
||||
examples = examples.mapValues { (_, v) -> ExampleWrapper(v) }.ifEmpty { null }
|
||||
)
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
||||
|
||||
// TODO God these annotations make this hideous... any way to improve?
|
||||
private fun KType.toParameterSpec(): List<OpenApiSpecParameter> {
|
||||
val clazz = classifier as KClass<*>
|
||||
@ -151,7 +163,7 @@ object Kompendium {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun resetSchema() {
|
||||
fun resetSchema() {
|
||||
openApiSpec = OpenApiSpec(
|
||||
info = OpenApiSpecInfo(),
|
||||
servers = mutableListOf(),
|
||||
|
@ -15,10 +15,10 @@ import org.leafygreens.kompendium.models.oas.FormatSchema
|
||||
import org.leafygreens.kompendium.models.oas.ObjectSchema
|
||||
import org.leafygreens.kompendium.models.oas.ReferencedSchema
|
||||
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.genericNameAdapter
|
||||
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
|
||||
import org.leafygreens.kompendium.util.Helpers.logged
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object Kontent {
|
||||
@ -45,7 +45,7 @@ object Kontent {
|
||||
fun generateKTypeKontent(
|
||||
type: KType,
|
||||
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")
|
||||
when (val clazz = type.classifier as KClass<*>) {
|
||||
Unit::class -> cache
|
||||
|
@ -10,7 +10,10 @@ import io.ktor.util.pipeline.PipelineInterceptor
|
||||
import org.leafygreens.kompendium.Kompendium.parseErrorInfo
|
||||
import org.leafygreens.kompendium.Kompendium.parseMethodInfo
|
||||
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.oas.OpenApiSpecPathItem
|
||||
|
||||
@ -18,55 +21,52 @@ object Notarized {
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
|
||||
info: MethodInfo,
|
||||
info: GetInfo<TParam, TResp>,
|
||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||
): Route =
|
||||
KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
|
||||
val path = Kompendium.pathCalculator.calculate(this)
|
||||
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||
Kompendium.openApiSpec.paths[path]?.get =
|
||||
info.parseMethodInfo(HttpMethod.Get, paramType, requestType, responseType)
|
||||
Kompendium.openApiSpec.paths[path]?.get = parseMethodInfo(info, paramType, requestType, responseType)
|
||||
return method(HttpMethod.Get) { handle(body) }
|
||||
}
|
||||
|
||||
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>
|
||||
): Route =
|
||||
KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
||||
val path = Kompendium.pathCalculator.calculate(this)
|
||||
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||
Kompendium.openApiSpec.paths[path]?.post =
|
||||
info.parseMethodInfo(HttpMethod.Post, paramType, requestType, responseType)
|
||||
Kompendium.openApiSpec.paths[path]?.post = parseMethodInfo(info, paramType, requestType, responseType)
|
||||
return method(HttpMethod.Post) { handle(body) }
|
||||
}
|
||||
|
||||
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>,
|
||||
): Route =
|
||||
KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
||||
val path = Kompendium.pathCalculator.calculate(this)
|
||||
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||
Kompendium.openApiSpec.paths[path]?.put =
|
||||
info.parseMethodInfo(HttpMethod.Put, paramType, requestType, responseType)
|
||||
parseMethodInfo(info, paramType, requestType, responseType)
|
||||
return method(HttpMethod.Put) { handle(body) }
|
||||
}
|
||||
|
||||
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
|
||||
info: MethodInfo,
|
||||
info: DeleteInfo<TParam, TResp>,
|
||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||
): Route =
|
||||
KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
|
||||
val path = Kompendium.pathCalculator.calculate(this)
|
||||
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||
Kompendium.openApiSpec.paths[path]?.delete =
|
||||
info.parseMethodInfo(HttpMethod.Delete, paramType, requestType, responseType)
|
||||
Kompendium.openApiSpec.paths[path]?.delete = parseMethodInfo(info, paramType, requestType, responseType)
|
||||
return method(HttpMethod.Delete) { handle(body) }
|
||||
}
|
||||
|
||||
inline fun <reified TErr : Throwable, reified TResp : Any> StatusPages.Configuration.notarizedException(
|
||||
info: ResponseInfo,
|
||||
info: ResponseInfo<TResp>,
|
||||
noinline handler: suspend PipelineContext<Unit, ApplicationCall>.(TErr) -> Unit
|
||||
) = errorNotarizationPreFlight<TErr, TResp>() { errorType, responseType ->
|
||||
info.parseErrorInfo(errorType, responseType)
|
||||
|
@ -3,4 +3,4 @@ package org.leafygreens.kompendium.models.meta
|
||||
import kotlin.reflect.KType
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
|
||||
|
||||
typealias ErrorMap = Map<KType, Pair<Int, OpenApiSpecResponse>?>
|
||||
typealias ErrorMap = Map<KType, Pair<Int, OpenApiSpecResponse<*>>?>
|
||||
|
@ -2,14 +2,94 @@ package org.leafygreens.kompendium.models.meta
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// TODO Seal and extend by method type?
|
||||
data class MethodInfo(
|
||||
val summary: String,
|
||||
val description: String? = null,
|
||||
val responseInfo: ResponseInfo? = null,
|
||||
val requestInfo: RequestInfo? = null,
|
||||
val tags: Set<String> = emptySet(),
|
||||
val deprecated: Boolean = false,
|
||||
val securitySchemes: Set<String> = emptySet(),
|
||||
val canThrow: Set<KClass<*>> = emptySet()
|
||||
)
|
||||
sealed class MethodInfo<TParam, TResp>(
|
||||
open val summary: String,
|
||||
open val description: String? = null,
|
||||
open val tags: Set<String> = emptySet(),
|
||||
open val deprecated: Boolean = false,
|
||||
open val securitySchemes: Set<String> = emptySet(),
|
||||
open val canThrow: Set<KClass<*>> = emptySet(),
|
||||
open val responseInfo: ResponseInfo<TResp>? = null,
|
||||
open val parameterExamples: Map<String, TParam> = emptyMap(),
|
||||
) {
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.leafygreens.kompendium.models.meta
|
||||
|
||||
data class RequestInfo(
|
||||
data class RequestInfo<TReq>(
|
||||
val description: String,
|
||||
val required: Boolean = true,
|
||||
val mediaTypes: List<String> = listOf("application/json")
|
||||
val mediaTypes: List<String> = listOf("application/json"),
|
||||
val examples: Map<String, TReq> = emptyMap()
|
||||
)
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.leafygreens.kompendium.models.meta
|
||||
|
||||
data class ResponseInfo(
|
||||
data class ResponseInfo<TResp>(
|
||||
val status: Int,
|
||||
val description: String,
|
||||
val mediaTypes: List<String> = listOf("application/json")
|
||||
val mediaTypes: List<String> = listOf("application/json"),
|
||||
val examples: Map<String, TResp> = emptyMap()
|
||||
)
|
||||
|
@ -1,16 +1,8 @@
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
// TODO Oof -> https://swagger.io/specification/#media-type-object
|
||||
sealed class OpenApiSpecMediaType {
|
||||
data class Explicit(
|
||||
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 OpenApiSpecMediaType<T>(
|
||||
val schema: OpenApiSpecReferenceObject,
|
||||
val examples: Map<String, ExampleWrapper<T>>? = null
|
||||
)
|
||||
|
||||
data class ExampleWrapper<T>(val value: T)
|
||||
|
@ -4,23 +4,13 @@ sealed class OpenApiSpecReferencable
|
||||
|
||||
class OpenApiSpecReferenceObject(val `$ref`: String) : OpenApiSpecReferencable()
|
||||
|
||||
data class OpenApiSpecCallback(
|
||||
val todo: String // todo fuck me -> https://swagger.io/specification/#callback-object
|
||||
) : OpenApiSpecReferencable()
|
||||
|
||||
data class OpenApiSpecResponse(
|
||||
data class OpenApiSpecResponse<T>(
|
||||
val description: String? = 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
|
||||
) : OpenApiSpecReferencable()
|
||||
|
||||
data class OpenApiSpecHeader(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val externalDocs: OpenApiSpecExternalDocumentation?
|
||||
) : OpenApiSpecReferencable()
|
||||
|
||||
data class OpenApiSpecParameter(
|
||||
val name: String,
|
||||
val `in`: String, // TODO Enum? "query", "header", "path" or "cookie"
|
||||
@ -33,8 +23,8 @@ data class OpenApiSpecParameter(
|
||||
val explode: Boolean? = null
|
||||
) : OpenApiSpecReferencable()
|
||||
|
||||
data class OpenApiSpecRequest(
|
||||
data class OpenApiSpecRequest<T>(
|
||||
val description: String?,
|
||||
val content: Map<String, OpenApiSpecMediaType>,
|
||||
val content: Map<String, OpenApiSpecMediaType<T>>,
|
||||
val required: Boolean = false
|
||||
) : OpenApiSpecReferencable()
|
||||
|
@ -26,7 +26,10 @@ import org.leafygreens.kompendium.Notarized.notarizedGet
|
||||
import org.leafygreens.kompendium.Notarized.notarizedPost
|
||||
import org.leafygreens.kompendium.Notarized.notarizedPut
|
||||
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.ResponseInfo
|
||||
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.KompendiumHttpCodes
|
||||
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.TestRequest
|
||||
import org.leafygreens.kompendium.util.TestResponse
|
||||
@ -67,7 +71,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -99,7 +103,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -131,7 +135,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -164,7 +168,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -195,7 +199,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -211,7 +215,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -243,7 +247,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -259,7 +263,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -291,7 +295,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -307,7 +311,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -323,7 +327,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -339,7 +343,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -355,7 +359,7 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@ -367,12 +371,11 @@ internal class KompendiumTest {
|
||||
docs()
|
||||
returnsList()
|
||||
}) {
|
||||
|
||||
// do
|
||||
val html = handleRequest(HttpMethod.Get, "/docs").response.content
|
||||
|
||||
// expected
|
||||
val expected = TestData.getFileSnapshot("redoc.html")
|
||||
val expected = getFileSnapshot("redoc.html")
|
||||
assertEquals(expected, html)
|
||||
}
|
||||
}
|
||||
@ -389,13 +392,11 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
fun `Generates additional responses when passed multiple throwables`() {
|
||||
withTestApplication({
|
||||
@ -408,28 +409,52 @@ internal class KompendiumTest {
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor")
|
||||
val testPostResponse = ResponseInfo(KompendiumHttpCodes.CREATED, "A Successful Endeavor")
|
||||
val testGetResponse = ResponseInfo<TestResponse>(KompendiumHttpCodes.OK, "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 =
|
||||
ResponseInfo(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor", mediaTypes = emptyList())
|
||||
val testRequest = RequestInfo("A Test request")
|
||||
val testGetInfo = MethodInfo("Another get test", "testing more", testGetResponse)
|
||||
ResponseInfo<Unit>(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor", mediaTypes = emptyList())
|
||||
val testRequest = RequestInfo<TestRequest>("A Test request")
|
||||
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(
|
||||
canThrow = setOf(Exception::class)
|
||||
)
|
||||
val testGetWithMultipleExceptions = testGetInfo.copy(
|
||||
canThrow = setOf(AccessDeniedException::class, Exception::class)
|
||||
)
|
||||
val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!", testPostResponse, testRequest)
|
||||
val testPutInfo = MethodInfo("Test put endpoint", "Put your tests here!", testPostResponse, testRequest)
|
||||
val testDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes", testDeleteResponse)
|
||||
val emptyTestGetInfo = MethodInfo("No request params and response body", "testing more")
|
||||
val testPostInfo = PostInfo<TestParams, TestRequest, TestCreatedResponse>(summary = "Test post endpoint", description = "Post your tests here!", responseInfo = testPostResponse, requestInfo = testRequest)
|
||||
val testPutInfo = PutInfo<Unit, ComplexRequest, TestCreatedResponse>(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponse, requestInfo = complexRequest)
|
||||
val testPutInfoAlso = PutInfo<TestParams, TestRequest, TestCreatedResponse>(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponse, requestInfo = testRequest)
|
||||
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() {
|
||||
@ -463,7 +488,7 @@ internal class KompendiumTest {
|
||||
private fun Application.notarizedGetWithNotarizedException() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedGet<TestParams, TestResponse>(testGetWithException) {
|
||||
notarizedGet(testGetWithException) {
|
||||
error("something terrible has happened!")
|
||||
}
|
||||
}
|
||||
@ -473,7 +498,7 @@ internal class KompendiumTest {
|
||||
private fun Application.notarizedGetWithMultipleThrowables() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedGet<TestParams, TestResponse>(testGetWithMultipleExceptions) {
|
||||
notarizedGet(testGetWithMultipleExceptions) {
|
||||
error("something terrible has happened!")
|
||||
}
|
||||
}
|
||||
@ -483,7 +508,7 @@ internal class KompendiumTest {
|
||||
private fun Application.notarizedGetModule() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedGet<TestParams, TestResponse>(testGetInfo) {
|
||||
notarizedGet(testGetInfo) {
|
||||
call.respondText { "hey dude ‼️ congratz on the get request" }
|
||||
}
|
||||
}
|
||||
@ -493,7 +518,7 @@ internal class KompendiumTest {
|
||||
private fun Application.notarizedPostModule() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedPost<TestParams, TestRequest, TestCreatedResponse>(testPostInfo) {
|
||||
notarizedPost(testPostInfo) {
|
||||
call.respondText { "hey dude ✌️ congratz on the post request" }
|
||||
}
|
||||
}
|
||||
@ -503,7 +528,7 @@ internal class KompendiumTest {
|
||||
private fun Application.notarizedDeleteModule() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedDelete<TestParams, Unit>(testDeleteInfo) {
|
||||
notarizedDelete(testDeleteInfo) {
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
}
|
||||
@ -513,7 +538,7 @@ internal class KompendiumTest {
|
||||
private fun Application.notarizedPutModule() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedPut<TestParams, TestRequest, TestCreatedResponse>(testPutInfo) {
|
||||
notarizedPut(testPutInfoAlso) {
|
||||
call.respondText { "hey pal 🌝 whatcha doin' here?" }
|
||||
}
|
||||
}
|
||||
@ -528,7 +553,7 @@ internal class KompendiumTest {
|
||||
route("/complex") {
|
||||
route("path") {
|
||||
route("with/an/{id}") {
|
||||
notarizedGet<TestParams, TestResponse>(testGetInfo) {
|
||||
notarizedGet(testGetInfo) {
|
||||
call.respondText { "Aww you followed this whole route 🥺" }
|
||||
}
|
||||
}
|
||||
@ -543,7 +568,7 @@ internal class KompendiumTest {
|
||||
private fun Application.rootModule() {
|
||||
routing {
|
||||
route("/") {
|
||||
notarizedGet<TestParams, TestResponse>(testGetInfo) {
|
||||
notarizedGet(testGetInfo) {
|
||||
call.respondText { "☎️🏠🌲" }
|
||||
}
|
||||
}
|
||||
@ -554,7 +579,7 @@ internal class KompendiumTest {
|
||||
routing {
|
||||
route("/") {
|
||||
route("/testerino") {
|
||||
notarizedGet<TestParams, TestResponse>(testGetInfo) {
|
||||
notarizedGet(testGetInfo) {
|
||||
call.respondText { "🤔🔥" }
|
||||
}
|
||||
}
|
||||
@ -566,7 +591,7 @@ internal class KompendiumTest {
|
||||
routing {
|
||||
route("/test") {
|
||||
route("/") {
|
||||
notarizedGet<TestParams, TestResponse>(testGetInfo) {
|
||||
notarizedGet(testGetInfo) {
|
||||
call.respondText { "🙀👾" }
|
||||
}
|
||||
}
|
||||
@ -577,7 +602,7 @@ internal class KompendiumTest {
|
||||
private fun Application.returnsList() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedGet<TestParams, List<TestResponse>>(testGetInfo) {
|
||||
notarizedGet(testGetInfoAgain) {
|
||||
call.respondText { "hey dude ur doing amazing work!" }
|
||||
}
|
||||
}
|
||||
@ -587,7 +612,7 @@ internal class KompendiumTest {
|
||||
private fun Application.complexType() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedPut<Unit, ComplexRequest, TestResponse>(testPutInfo) {
|
||||
notarizedPut(testPutInfo) {
|
||||
call.respondText { "heya" }
|
||||
}
|
||||
}
|
||||
@ -597,7 +622,7 @@ internal class KompendiumTest {
|
||||
private fun Application.primitives() {
|
||||
routing {
|
||||
route("/test") {
|
||||
notarizedPut<Unit, Int, Boolean>(testPutInfo) {
|
||||
notarizedPut(testPutInfoAgain) {
|
||||
call.respondText { "heya" }
|
||||
}
|
||||
}
|
||||
@ -607,7 +632,32 @@ internal class KompendiumTest {
|
||||
private fun Application.emptyGet() {
|
||||
routing {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -618,7 +668,7 @@ internal class KompendiumTest {
|
||||
private fun Application.nonRequiredParamsGet() {
|
||||
routing {
|
||||
route("/test/optional") {
|
||||
notarizedGet<OptionalParams, Unit>(emptyTestGetInfo) {
|
||||
notarizedGet(emptyTestGetInfo) {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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()
|
||||
)
|
||||
)
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@
|
||||
"description" : "Put your tests here!",
|
||||
"parameters" : [ ],
|
||||
"requestBody" : {
|
||||
"description" : "A Test request",
|
||||
"description" : "A Complex request",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
@ -46,7 +46,7 @@
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
"$ref" : "#/components/schemas/TestCreatedResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,10 +61,17 @@
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
},
|
||||
"TestCreatedResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"id" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
|
140
kompendium-core/src/test/resources/example_req_and_resp.json
Normal file
140
kompendium-core/src/test/resources/example_req_and_resp.json
Normal 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" : [ ]
|
||||
}
|
@ -47,7 +47,7 @@
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"description" : "A Successful List-y Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
|
@ -30,7 +30,10 @@ import org.leafygreens.kompendium.annotations.KompendiumField
|
||||
import org.leafygreens.kompendium.annotations.PathParam
|
||||
import org.leafygreens.kompendium.annotations.QueryParam
|
||||
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.ResponseInfo
|
||||
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.OpenApiSpecServer
|
||||
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.testSingleDeleteInfo
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo
|
||||
@ -86,8 +90,8 @@ fun main() {
|
||||
}
|
||||
|
||||
var featuresInstalled = false
|
||||
fun Application.mainModule() {
|
||||
// only install once in case of auto reload
|
||||
|
||||
fun Application.configModule() {
|
||||
if (!featuresInstalled) {
|
||||
install(ContentNegotiation) {
|
||||
jackson {
|
||||
@ -120,33 +124,42 @@ fun Application.mainModule() {
|
||||
}
|
||||
featuresInstalled = true
|
||||
}
|
||||
}
|
||||
|
||||
fun Application.mainModule() {
|
||||
configModule()
|
||||
routing {
|
||||
openApi(oas)
|
||||
redoc(oas)
|
||||
swaggerUI()
|
||||
route("/potato/spud") {
|
||||
notarizedGet(testGetWithExamples) {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
}
|
||||
route("/test") {
|
||||
route("/{id}") {
|
||||
notarizedGet<ExampleParams, ExampleResponse>(testIdGetInfo) {
|
||||
notarizedGet(testIdGetInfo) {
|
||||
call.respondText("get by id")
|
||||
}
|
||||
}
|
||||
route("/single") {
|
||||
notarizedGet<Unit, ExampleResponse>(testSingleGetInfo) {
|
||||
notarizedGet(testSingleGetInfo) {
|
||||
call.respondText("get single")
|
||||
}
|
||||
notarizedPost<Unit, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) {
|
||||
notarizedPost(testSinglePostInfo) {
|
||||
call.respondText("test post")
|
||||
}
|
||||
notarizedPut<JustQuery, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) {
|
||||
notarizedPut(testSinglePutInfo) {
|
||||
call.respondText { "hey" }
|
||||
}
|
||||
notarizedDelete<Unit, Unit>(testSingleDeleteInfo) {
|
||||
notarizedDelete(testSingleDeleteInfo) {
|
||||
call.respondText { "heya" }
|
||||
}
|
||||
}
|
||||
authenticate("basic") {
|
||||
route("/authenticated/single") {
|
||||
notarizedGet<Unit, Unit>(testAuthenticatedSingleGetInfo) {
|
||||
notarizedGet(testAuthenticatedSingleGetInfo) {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
}
|
||||
@ -186,7 +199,16 @@ data class ExceptionResponse(val message: String)
|
||||
data class ExampleCreatedResponse(val id: Int, val c: String)
|
||||
|
||||
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",
|
||||
description = "Test for the getting",
|
||||
tags = setOf("test", "sample", "get"),
|
||||
@ -195,7 +217,7 @@ object KompendiumTOC {
|
||||
description = "Returns sample info"
|
||||
)
|
||||
)
|
||||
val testSingleGetInfo = MethodInfo(
|
||||
val testSingleGetInfo = GetInfo<Unit, ExampleResponse>(
|
||||
summary = "Another get test",
|
||||
description = "testing more",
|
||||
tags = setOf("anotherTest", "sample"),
|
||||
@ -208,7 +230,7 @@ object KompendiumTOC {
|
||||
summary = "Show me the error baby 🙏",
|
||||
canThrow = setOf(Exception::class)
|
||||
)
|
||||
val testSinglePostInfo = MethodInfo(
|
||||
val testSinglePostInfo = PostInfo<Unit, ExampleRequest, ExampleCreatedResponse>(
|
||||
summary = "Test post endpoint",
|
||||
description = "Post your tests here!",
|
||||
requestInfo = RequestInfo(
|
||||
@ -219,7 +241,7 @@ object KompendiumTOC {
|
||||
description = "Worlds most complex response"
|
||||
)
|
||||
)
|
||||
val testSinglePutInfo = MethodInfo(
|
||||
val testSinglePutInfo = PutInfo<JustQuery, ExampleRequest, ExampleCreatedResponse>(
|
||||
summary = "Test put endpoint",
|
||||
description = "Put your tests here!",
|
||||
requestInfo = RequestInfo(
|
||||
@ -230,7 +252,7 @@ object KompendiumTOC {
|
||||
description = "What we give you when u do the puts"
|
||||
)
|
||||
)
|
||||
val testSingleDeleteInfo = MethodInfo(
|
||||
val testSingleDeleteInfo = DeleteInfo<Unit, Unit>(
|
||||
summary = "Test delete endpoint",
|
||||
description = "testing my deletes",
|
||||
responseInfo = ResponseInfo(
|
||||
@ -239,7 +261,7 @@ object KompendiumTOC {
|
||||
mediaTypes = emptyList()
|
||||
)
|
||||
)
|
||||
val testAuthenticatedSingleGetInfo = MethodInfo(
|
||||
val testAuthenticatedSingleGetInfo = GetInfo<Unit, Unit>(
|
||||
summary = "Another get test",
|
||||
description = "testing more",
|
||||
tags = setOf("anotherTest", "sample"),
|
||||
|
Reference in New Issue
Block a user