document top level collectiom (#24)

This commit is contained in:
Ryan Brink
2021-04-17 08:32:43 -04:00
committed by GitHub
parent 810f290f0d
commit 81e24f96dc
20 changed files with 308 additions and 158 deletions

View File

@ -1,5 +1,12 @@
# Changelog
## [0.3.0] - April 17th, 2021
### Changed
- Removed response and request annotations in favor of MethodInfo extension.
- Modified notarization to add the correct reference slug regardless of type
## [0.2.0] - April 16th, 2021
### Changed

View File

@ -68,26 +68,39 @@ meaning that swapping in a default Ktor route and a Kompendium `notarized` route
### Supplemental Annotations
In general, Kompendium tries to limit the number of annotations that developers need to use in order to get an app
integrated. However, there are a couple areas that it made sense, at least for an MVP.
integrated.
Currently, there are three Kompendium annotations
Currently, there is only a single Kompendium annotation
- `KompendiumRequest`
- `KompendiumResponse`
- `KompendiumField`
These are aimed at offering modifications at the request, response, and field level respectively, and offer things such
as response status codes, field name overrides, and OpenApi metadata such as `description`.
The intended purpose is to offer field level overrides such as naming conventions (ie snake instead of camel).
## 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.
```kotlin
// Minimal API Example
fun main() {
embeddedServer(
Netty,
port = 8081,
module = Application::mainModule
).start(wait = true)
}
fun Application.mainModule() {
install(ContentNegotiation) {
jackson()
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
}
}
routing {
openApi()
redoc()
route("/test") {
route("/{id}") {
notarizedGet<ExampleParams, ExampleResponse>(testIdGetInfo) {
@ -109,52 +122,13 @@ fun Application.mainModule() {
}
}
}
route("/openapi.json") {
get {
call.respond(openApiSpec.copy(
info = OpenApiSpecInfo(
title = "Test API",
version = "1.3.3.7",
description = "An amazing, fully-ish 😉 generated API spec"
)
))
}
}
}
}
// Ancillary Data
data class ExampleParams(val a: String, val aa: Int)
data class ExampleNested(val nesty: String)
@KompendiumResponse(status = KompendiumHttpCodes.NO_CONTENT, "Entity was deleted successfully")
object DeleteResponse
@KompendiumRequest("Example Request")
data class ExampleRequest(
@KompendiumField(name = "field_name")
val fieldName: ExampleNested,
val b: Double,
val aaa: List<Long>
)
@KompendiumResponse(KompendiumHttpCodes.OK, "A Successful Endeavor")
data class ExampleResponse(val c: String)
@KompendiumResponse(KompendiumHttpCodes.CREATED, "Created Successfully")
data class ExampleCreatedResponse(val id: Int, val c: String)
object KompendiumTOC {
val testIdGetInfo = MethodInfo("Get Test", "Test for getting", tags = setOf("test", "example", "get"))
val testSingleGetInfo = MethodInfo("Another get test", "testing more")
val testSinglePostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
val testSinglePutInfo = MethodInfo("Test put endpoint", "Put your tests here!")
val testSingleDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes")
}
```
This example would output the following json spec https://gist.github.com/rgbrizzlehizzle/b9544922f2e99a2815177f8bdbf80668
When run in the playground, this would output the following at `/openapi.json`
https://gist.github.com/rgbrizzlehizzle/b9544922f2e99a2815177f8bdbf80668
## Limitations

View File

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

View File

@ -5,11 +5,12 @@ import io.ktor.http.HttpMethod
import io.ktor.routing.Route
import io.ktor.routing.method
import io.ktor.util.pipeline.PipelineInterceptor
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.KType
import kotlin.reflect.typeOf
import org.leafygreens.kompendium.Kontent.generateKontent
import org.leafygreens.kompendium.annotations.KompendiumRequest
import org.leafygreens.kompendium.annotations.KompendiumResponse
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.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
@ -18,8 +19,8 @@ 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.util.Helpers.COMPONENT_SLUG
import org.leafygreens.kompendium.util.Helpers.calculatePath
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
object Kompendium {
@ -29,94 +30,102 @@ object Kompendium {
paths = mutableMapOf()
)
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = generateComponentSchemas<Unit, TResp>() {
): Route = notarizationPreFlight<Unit, TResp>() { requestType, responseType ->
val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.get = info.parseMethodInfo<Unit, TResp>()
openApiSpec.paths[path]?.get = info.parseMethodInfo(HttpMethod.Get, requestType, responseType)
return method(HttpMethod.Get) { handle(body) }
}
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost(
info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = generateComponentSchemas<TReq, TResp>() {
): Route = notarizationPreFlight<TReq, TResp>() { requestType, responseType ->
val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.post = info.parseMethodInfo<TReq, TResp>()
openApiSpec.paths[path]?.post = info.parseMethodInfo(HttpMethod.Post, requestType, responseType)
return method(HttpMethod.Post) { handle(body) }
}
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut(
info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
): Route = generateComponentSchemas<TReq, TResp>() {
): Route = notarizationPreFlight<TReq, TResp>() { requestType, responseType ->
val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.put = info.parseMethodInfo<TReq, TResp>()
openApiSpec.paths[path]?.put = info.parseMethodInfo(HttpMethod.Put, requestType, responseType)
return method(HttpMethod.Put) { handle(body) }
}
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = generateComponentSchemas<Unit, TResp> {
): Route = notarizationPreFlight<Unit, TResp> { requestType, responseType ->
val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.delete = info.parseMethodInfo<Unit, TResp>()
openApiSpec.paths[path]?.delete = info.parseMethodInfo(HttpMethod.Delete, requestType, responseType)
return method(HttpMethod.Delete) { handle(body) }
}
inline fun <reified TReq, reified TResp> MethodInfo.parseMethodInfo() = OpenApiSpecPathItemOperation(
// TODO here down is a mess, needs refactor once core functionality is in place
fun MethodInfo.parseMethodInfo(
method: HttpMethod,
requestType: KType,
responseType: KType
) = OpenApiSpecPathItemOperation(
summary = this.summary,
description = this.description,
tags = this.tags,
deprecated = this.deprecated,
responses = parseResponseAnnotation<TResp>()?.let { mapOf(it) },
requestBody = parseRequestAnnotation<TReq>()
responses = responseType.toSpec(responseInfo)?.let { mapOf(it) },
requestBody = if (method != HttpMethod.Get) requestType.toSpec(requestInfo) else null
)
inline fun <reified TReq : Any, reified TResp : Any> generateComponentSchemas(
block: () -> Route
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified TReq : Any, reified TResp : Any> notarizationPreFlight(
block: (KType, KType) -> Route
): Route {
val responseKontent = generateKontent<TResp>()
val requestKontent = generateKontent<TReq>()
openApiSpec.components.schemas.putAll(responseKontent)
openApiSpec.components.schemas.putAll(requestKontent)
return block.invoke()
val requestType = typeOf<TReq>()
val responseType = typeOf<TResp>()
return block.invoke(requestType, responseType)
}
inline fun <reified TReq> parseRequestAnnotation(): OpenApiSpecRequest? = when (TReq::class) {
// TODO These two lookin' real similar 👀 Combine?
private fun KType.toSpec(requestInfo: RequestInfo?): OpenApiSpecRequest? = when (this) {
Unit::class -> null
else -> when (val anny = TReq::class.findAnnotation<KompendiumRequest>()) {
else -> when (requestInfo) {
null -> null
else -> OpenApiSpecRequest(
description = anny.description,
content = anny.mediaTypes.associate {
val ref = OpenApiSpecReferenceObject("$COMPONENT_SLUG/${TReq::class.simpleName}")
val mediaType = OpenApiSpecMediaType.Referenced(ref)
Pair(it, mediaType)
description = requestInfo.description,
content = requestInfo.mediaTypes.associateWith {
val ref = getReferenceSlug()
OpenApiSpecMediaType.Referenced(OpenApiSpecReferenceObject(ref))
}
)
}
}
inline fun <reified TResp> parseResponseAnnotation(): Pair<Int, OpenApiSpecResponse>? = when (TResp::class) {
Unit::class -> null
else -> when (val anny = TResp::class.findAnnotation<KompendiumResponse>()) {
null -> null
private fun KType.toSpec(responseInfo: ResponseInfo?): Pair<Int, OpenApiSpecResponse>? = when (this) {
Unit::class -> null // TODO Maybe not though? could be unit but 200 🤔
else -> when (responseInfo) {
null -> null // TODO again probably revisit this
else -> {
val specResponse = OpenApiSpecResponse(
description = anny.description,
content = anny.mediaTypes.associate {
val ref = OpenApiSpecReferenceObject("$COMPONENT_SLUG/${TResp::class.simpleName}")
val mediaType = OpenApiSpecMediaType.Referenced(ref)
Pair(it, mediaType)
description = responseInfo.description,
content = responseInfo.mediaTypes.associateWith {
val ref = getReferenceSlug()
OpenApiSpecMediaType.Referenced(OpenApiSpecReferenceObject(ref))
}
)
Pair(anny.status, specResponse)
Pair(responseInfo.status, specResponse)
}
}
}

View File

@ -99,7 +99,7 @@ object Kontent {
val referenceName = genericNameAdapter(type, clazz)
val valueReference = ReferencedSchema("$COMPONENT_SLUG/$valClassName")
val schema = DictionarySchema(additionalProperties = valueReference)
val updatedCache = generateKTypeKontent(valType!!, cache)
val updatedCache = generateKTypeKontent(valType, cache)
return updatedCache.plus(referenceName to schema)
}

View File

@ -1,7 +0,0 @@
package org.leafygreens.kompendium.annotations
annotation class KompendiumRequest(
val description: String,
val required: Boolean = true,
val mediaTypes: Array<String> = ["application/json"]
)

View File

@ -1,9 +0,0 @@
package org.leafygreens.kompendium.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class KompendiumResponse(
val status: Int,
val description: String,
val mediaTypes: Array<String> = ["application/json"]
)

View File

@ -1,8 +1,11 @@
package org.leafygreens.kompendium.models.meta
// 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
)

View File

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

View File

@ -0,0 +1,7 @@
package org.leafygreens.kompendium.models.meta
data class ResponseInfo(
val status: Int, // TODO How to handle error codes?
val description: String,
val mediaTypes: List<String> = listOf("application/json")
)

View File

@ -92,6 +92,11 @@ object Helpers {
return result
}
fun KType.getReferenceSlug(): String = when {
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}"
}
/**
* Will build a reference slug that is useful for schema caching and references, particularly
* in the case of a class with type parameters

View File

@ -25,11 +25,14 @@ import org.leafygreens.kompendium.Kompendium.notarizedGet
import org.leafygreens.kompendium.Kompendium.notarizedPost
import org.leafygreens.kompendium.Kompendium.notarizedPut
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.oas.OpenApiSpecInfo
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.util.ComplexRequest
import org.leafygreens.kompendium.util.KompendiumHttpCodes
import org.leafygreens.kompendium.util.TestCreatedResponse
import org.leafygreens.kompendium.util.TestData
import org.leafygreens.kompendium.util.TestDeleteResponse
@ -289,11 +292,31 @@ internal class KompendiumTest {
}
}
@Test
fun `Can notarize a top level list response`() {
withTestApplication({
configModule()
openApiModule()
returnsList()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = TestData.getFileSnapshot("response_list.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
private companion object {
val testGetInfo = MethodInfo("Another get test", "testing more")
val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
val testPutInfo = MethodInfo("Test put endpoint", "Put your tests here!")
val testDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes")
val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor")
val testPostResponse = ResponseInfo(KompendiumHttpCodes.CREATED, "A Successful Endeavor")
val testDeleteResponse = ResponseInfo(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor")
val testRequest = RequestInfo("A Test request")
val testGetInfo = MethodInfo("Another get test", "testing more", testGetResponse)
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)
}
private fun Application.configModule() {
@ -387,6 +410,16 @@ internal class KompendiumTest {
}
}
private fun Application.returnsList() {
routing {
route("/test") {
notarizedGet<TestParams, List<TestResponse>>(testGetInfo) {
call.respondText { "hey dude ur doing amazing work!" }
}
}
}
}
private fun Application.complexType() {
routing {
route("/test") {

View File

@ -2,8 +2,6 @@ package org.leafygreens.kompendium.util
import java.util.UUID
import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.KompendiumRequest
import org.leafygreens.kompendium.annotations.KompendiumResponse
data class TestSimpleModel(val a: String, val b: Int)
@ -25,7 +23,6 @@ data class TestNested(val nesty: String)
data class TestWithUUID(val id: UUID)
@KompendiumRequest("Example Request")
data class TestRequest(
@KompendiumField(name = "field_name")
val fieldName: TestNested,
@ -33,16 +30,12 @@ data class TestRequest(
val aaa: List<Long>
)
@KompendiumResponse(KompendiumHttpCodes.OK, "A Successful Endeavor")
data class TestResponse(val c: String)
@KompendiumResponse(KompendiumHttpCodes.CREATED, "Created Successfully")
data class TestCreatedResponse(val id: Int, val c: String)
@KompendiumResponse(KompendiumHttpCodes.NO_CONTENT, "Entity was deleted successfully")
object TestDeleteResponse
@KompendiumRequest("Request object to create a backbone project")
data class ComplexRequest(
val org: String,
@KompendiumField("amazing_field")

View File

@ -29,7 +29,7 @@
"summary" : "Test put endpoint",
"description" : "Put your tests here!",
"requestBody" : {
"description" : "Request object to create a backbone project",
"description" : "A Test request",
"content" : {
"application/json" : {
"schema" : {
@ -40,7 +40,7 @@
"required" : false
},
"responses" : {
"200" : {
"201" : {
"description" : "A Successful Endeavor",
"content" : {
"application/json" : {

View File

@ -30,7 +30,7 @@
"description" : "testing my deletes",
"responses" : {
"204" : {
"description" : "Entity was deleted successfully",
"description" : "A Successful Endeavor",
"content" : {
"application/json" : {
"schema" : {

View File

@ -29,7 +29,7 @@
"summary" : "Test post endpoint",
"description" : "Post your tests here!",
"requestBody" : {
"description" : "Example Request",
"description" : "A Test request",
"content" : {
"application/json" : {
"schema" : {
@ -41,7 +41,7 @@
},
"responses" : {
"201" : {
"description" : "Created Successfully",
"description" : "A Successful Endeavor",
"content" : {
"application/json" : {
"schema" : {

View File

@ -28,6 +28,29 @@
"tags" : [ ],
"summary" : "Test put endpoint",
"description" : "Put your tests here!",
"requestBody" : {
"description" : "A Test request",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/Int"
}
}
},
"required" : false
},
"responses" : {
"201" : {
"description" : "A Successful Endeavor",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/Boolean"
}
}
}
}
},
"deprecated" : false
}
}

View File

@ -29,7 +29,7 @@
"summary" : "Test put endpoint",
"description" : "Put your tests here!",
"requestBody" : {
"description" : "Example Request",
"description" : "A Test request",
"content" : {
"application/json" : {
"schema" : {
@ -41,7 +41,7 @@
},
"responses" : {
"201" : {
"description" : "Created Successfully",
"description" : "A Successful Endeavor",
"content" : {
"application/json" : {
"schema" : {

View File

@ -0,0 +1,71 @@
{
"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" : {
"get" : {
"tags" : [ ],
"summary" : "Another get test",
"description" : "testing more",
"responses" : {
"200" : {
"description" : "A Successful Endeavor",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/List-TestResponse"
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"TestResponse" : {
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
},
"List-TestResponse" : {
"items" : {
"$ref" : "#/components/schemas/TestResponse"
},
"type" : "array"
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -7,7 +7,6 @@ import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.ContentNegotiation
import io.ktor.html.respondHtml
import io.ktor.http.HttpStatusCode
import io.ktor.jackson.jackson
import io.ktor.response.respond
import io.ktor.response.respondText
@ -32,9 +31,9 @@ import org.leafygreens.kompendium.Kompendium.notarizedPost
import org.leafygreens.kompendium.Kompendium.notarizedPut
import org.leafygreens.kompendium.Kompendium.openApiSpec
import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.KompendiumRequest
import org.leafygreens.kompendium.annotations.KompendiumResponse
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.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
@ -54,38 +53,6 @@ fun main() {
).start(wait = true)
}
data class ExampleParams(val a: String, val aa: Int)
data class ExampleNested(val nesty: String)
@KompendiumResponse(KompendiumHttpCodes.NO_CONTENT, "Entity was deleted successfully")
object DeleteResponse
@KompendiumRequest("Example Request")
data class ExampleRequest(
@KompendiumField(name = "field_name")
val fieldName: ExampleNested,
val b: Double,
val aaa: List<Long>
)
private const val HTTP_OK = 200
private const val HTTP_CREATED = 201
@KompendiumResponse(HTTP_OK, "A Successful Endeavor")
data class ExampleResponse(val c: String)
@KompendiumResponse(HTTP_CREATED, "Created Successfully")
data class ExampleCreatedResponse(val id: Int, val c: String)
object KompendiumTOC {
val testIdGetInfo = MethodInfo("Get Test", "Test for getting", tags = setOf("test", "example", "get"))
val testSingleGetInfo = MethodInfo("Another get test", "testing more")
val testSinglePostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
val testSinglePutInfo = MethodInfo("Test put endpoint", "Put your tests here!")
val testSingleDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes")
}
fun Application.mainModule() {
install(ContentNegotiation) {
jackson {
@ -120,6 +87,74 @@ fun Application.mainModule() {
}
}
data class ExampleParams(val a: String, val aa: Int)
data class ExampleNested(val nesty: String)
object DeleteResponse
data class ExampleRequest(
@KompendiumField(name = "field_name")
val fieldName: ExampleNested,
val b: Double,
val aaa: List<Long>
)
data class ExampleResponse(val c: String)
data class ExampleCreatedResponse(val id: Int, val c: String)
object KompendiumTOC {
val testIdGetInfo = MethodInfo(
summary = "Get Test",
description = "Test for the getting",
tags = setOf("test", "sample", "get"),
responseInfo = ResponseInfo(
status = KompendiumHttpCodes.OK,
description = "Returns sample info"
)
)
val testSingleGetInfo = MethodInfo(
summary = "Another get test",
description = "testing more",
tags = setOf("anotherTest", "sample"),
responseInfo = ResponseInfo(
status = KompendiumHttpCodes.OK,
description = "Returns a different sample"
)
)
val testSinglePostInfo = MethodInfo(
summary = "Test post endpoint",
description = "Post your tests here!",
requestInfo = RequestInfo(
description = "Simple request body"
),
responseInfo = ResponseInfo(
status = KompendiumHttpCodes.CREATED,
description = "Worlds most complex response"
)
)
val testSinglePutInfo = MethodInfo(
summary = "Test put endpoint",
description = "Put your tests here!",
requestInfo = RequestInfo(
description = "Info needed to perform this put request"
),
responseInfo = ResponseInfo(
status = KompendiumHttpCodes.CREATED,
description = "What we give you when u do the puts"
)
)
val testSingleDeleteInfo = MethodInfo(
summary = "Test delete endpoint",
description = "testing my deletes",
responseInfo = ResponseInfo(
status = KompendiumHttpCodes.NO_CONTENT,
description = "Signifies that your item was deleted succesfully"
)
)
}
fun Routing.openApi() {
route("/openapi.json") {
get {
@ -162,8 +197,7 @@ fun Routing.redoc() {
call.respondHtml {
head {
title {
// TODO Make this load project title
+"Docs"
+"${openApiSpec.info.title}"
}
meta {
charset = "utf-8"
@ -183,7 +217,7 @@ fun Routing.redoc() {
}
}
body {
// TODO Make this its own DSL class
// TODO needs to mirror openApi route
unsafe { +"<redoc spec-url='/openapi.json'></redoc>" }
script {
src = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"