parameter support (#27)

This commit is contained in:
Ryan Brink
2021-04-17 16:20:37 -04:00
committed by GitHub
parent 81e24f96dc
commit ae4999483b
26 changed files with 340 additions and 64 deletions

View File

@ -1,5 +1,15 @@
# Changelog # Changelog
## [0.4.0] - April 17th, 2021
### Added
- Basic Query and Path Parameter Support 🍻
### Changed
- No content workaround, flow will likely need refactoring for clarity.
## [0.3.0] - April 17th, 2021 ## [0.3.0] - April 17th, 2021
### Changed ### Changed

View File

@ -40,7 +40,6 @@ dependencies {
### Warning 🚨 ### Warning 🚨
Kompendium is still under active development ⚠️ There are a number of yet-to-be-implemented features, including Kompendium is still under active development ⚠️ There are a number of yet-to-be-implemented features, including
- Query and Path Parameters 🔍
- Tags 🏷 - Tags 🏷
- Multiple Responses 📜 - Multiple Responses 📜
- Security Schemas 🔏 - Security Schemas 🔏
@ -70,11 +69,18 @@ meaning that swapping in a default Ktor route and a Kompendium `notarized` route
In general, Kompendium tries to limit the number of annotations that developers need to use in order to get an app In general, Kompendium tries to limit the number of annotations that developers need to use in order to get an app
integrated. integrated.
Currently, there is only a single Kompendium annotation Currently, the annotations used by Kompendium are as follows
- `KompendiumField` - `KompendiumField`
- `PathParam`
- `QueryParam`
- `HeaderParam`
- `CookieParam`
The intended purpose is to offer field level overrides such as naming conventions (ie snake instead of camel). The intended purpose of `KompendiumField` is to offer field level overrides such as naming conventions (ie snake instead of camel).
The 4 "param" annotations are to offer supplemental information in data classes that describe the set of parameters types
that a notarized route needs to analyze.
## Examples ## Examples
@ -108,16 +114,16 @@ fun Application.mainModule() {
} }
} }
route("/single") { route("/single") {
notarizedGet<ExampleRequest, ExampleResponse>(testSingleGetInfo) { notarizedGet<ExampleParams, ExampleResponse>(testSingleGetInfo) {
call.respondText("get single") call.respondText("get single")
} }
notarizedPost<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) { notarizedPost<Unit, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) {
call.respondText("test post") call.respondText("test post")
} }
notarizedPut<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) { notarizedPut<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) {
call.respondText { "hey" } call.respondText { "hey" }
} }
notarizedDelete<Unit, DeleteResponse>(testSingleDeleteInfo) { notarizedDelete<Unit, Unit>(testSingleDeleteInfo) {
call.respondText { "heya" } call.respondText { "heya" }
} }
} }

View File

@ -65,7 +65,7 @@ complexity:
includePrivateDeclarations: false includePrivateDeclarations: false
ComplexMethod: ComplexMethod:
active: true active: true
threshold: 15 threshold: 25
ignoreSingleWhenExpression: false ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false ignoreNestingFunctions: false

View File

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

View File

@ -5,25 +5,39 @@ import io.ktor.http.HttpMethod
import io.ktor.routing.Route import io.ktor.routing.Route
import io.ktor.routing.method import io.ktor.routing.method
import io.ktor.util.pipeline.PipelineInterceptor import io.ktor.util.pipeline.PipelineInterceptor
import kotlin.reflect.KClass
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
import org.leafygreens.kompendium.Kontent.generateKontent import org.leafygreens.kompendium.Kontent.generateKontent
import org.leafygreens.kompendium.Kontent.generateParameterKontent
import org.leafygreens.kompendium.annotations.CookieParam
import org.leafygreens.kompendium.annotations.HeaderParam
import org.leafygreens.kompendium.annotations.PathParam
import org.leafygreens.kompendium.annotations.QueryParam
import org.leafygreens.kompendium.models.meta.MethodInfo import org.leafygreens.kompendium.models.meta.MethodInfo
import org.leafygreens.kompendium.models.meta.RequestInfo import org.leafygreens.kompendium.models.meta.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.meta.SchemaMap
import org.leafygreens.kompendium.models.oas.OpenApiSpec import org.leafygreens.kompendium.models.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaRef
import org.leafygreens.kompendium.util.Helpers.calculatePath import org.leafygreens.kompendium.util.Helpers.calculatePath
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
object Kompendium { object Kompendium {
var cache: SchemaMap = emptyMap()
var openApiSpec = OpenApiSpec( var openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(), info = OpenApiSpecInfo(),
servers = mutableListOf(), servers = mutableListOf(),
@ -34,46 +48,47 @@ object Kompendium {
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet( inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
info: MethodInfo, info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall> noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = notarizationPreFlight<Unit, TResp>() { requestType, responseType -> ): Route = notarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
val path = calculatePath() val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.get = info.parseMethodInfo(HttpMethod.Get, requestType, responseType) openApiSpec.paths[path]?.get = info.parseMethodInfo(HttpMethod.Get, paramType, requestType, responseType)
return method(HttpMethod.Get) { handle(body) } return method(HttpMethod.Get) { handle(body) }
} }
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost( inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost(
info: MethodInfo, info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall> noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = notarizationPreFlight<TReq, TResp>() { requestType, responseType -> ): Route = notarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
val path = calculatePath() val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.post = info.parseMethodInfo(HttpMethod.Post, requestType, responseType) openApiSpec.paths[path]?.post = info.parseMethodInfo(HttpMethod.Post, paramType, requestType, responseType)
return method(HttpMethod.Post) { handle(body) } return method(HttpMethod.Post) { handle(body) }
} }
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut( inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut(
info: MethodInfo, info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall>, noinline body: PipelineInterceptor<Unit, ApplicationCall>,
): Route = notarizationPreFlight<TReq, TResp>() { requestType, responseType -> ): Route = notarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
val path = calculatePath() val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.put = info.parseMethodInfo(HttpMethod.Put, requestType, responseType) openApiSpec.paths[path]?.put = info.parseMethodInfo(HttpMethod.Put, paramType, requestType, responseType)
return method(HttpMethod.Put) { handle(body) } return method(HttpMethod.Put) { handle(body) }
} }
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete( inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
info: MethodInfo, info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall> noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = notarizationPreFlight<Unit, TResp> { requestType, responseType -> ): Route = notarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
val path = calculatePath() val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.delete = info.parseMethodInfo(HttpMethod.Delete, requestType, responseType) openApiSpec.paths[path]?.delete = info.parseMethodInfo(HttpMethod.Delete, paramType, requestType, responseType)
return method(HttpMethod.Delete) { handle(body) } return method(HttpMethod.Delete) { handle(body) }
} }
// TODO here down is a mess, needs refactor once core functionality is in place // TODO here down is a mess, needs refactor once core functionality is in place
fun MethodInfo.parseMethodInfo( fun MethodInfo.parseMethodInfo(
method: HttpMethod, method: HttpMethod,
paramType: KType,
requestType: KType, requestType: KType,
responseType: KType responseType: KType
) = OpenApiSpecPathItemOperation( ) = OpenApiSpecPathItemOperation(
@ -81,25 +96,27 @@ object Kompendium {
description = this.description, description = this.description,
tags = this.tags, tags = this.tags,
deprecated = this.deprecated, deprecated = this.deprecated,
responses = responseType.toSpec(responseInfo)?.let { mapOf(it) }, parameters = paramType.toParameterSpec(),
requestBody = if (method != HttpMethod.Get) requestType.toSpec(requestInfo) else null responses = responseType.toResponseSpec(responseInfo)?.let { mapOf(it) },
requestBody = if (method != HttpMethod.Get) requestType.toRequestSpec(requestInfo) else null
) )
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
inline fun <reified TReq : Any, reified TResp : Any> notarizationPreFlight( inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> notarizationPreFlight(
block: (KType, KType) -> Route block: (KType, KType, KType) -> Route
): Route { ): Route {
val responseKontent = generateKontent<TResp>() cache = generateKontent<TResp>(cache)
val requestKontent = generateKontent<TReq>() cache = generateKontent<TReq>(cache)
openApiSpec.components.schemas.putAll(responseKontent) cache = generateParameterKontent<TParam>(cache)
openApiSpec.components.schemas.putAll(requestKontent) openApiSpec.components.schemas.putAll(cache)
val requestType = typeOf<TReq>() val requestType = typeOf<TReq>()
val responseType = typeOf<TResp>() val responseType = typeOf<TResp>()
return block.invoke(requestType, responseType) val paramType = typeOf<TParam>()
return block.invoke(paramType, requestType, responseType)
} }
// TODO These two lookin' real similar 👀 Combine? // TODO These two lookin' real similar 👀 Combine?
private fun KType.toSpec(requestInfo: RequestInfo?): OpenApiSpecRequest? = when (this) { private fun KType.toRequestSpec(requestInfo: RequestInfo?): OpenApiSpecRequest? = when (this) {
Unit::class -> null Unit::class -> null
else -> when (requestInfo) { else -> when (requestInfo) {
null -> null null -> null
@ -113,28 +130,62 @@ object Kompendium {
} }
} }
private fun KType.toSpec(responseInfo: ResponseInfo?): Pair<Int, OpenApiSpecResponse>? = when (this) { private fun KType.toResponseSpec(responseInfo: ResponseInfo?): Pair<Int, OpenApiSpecResponse>? = when (this) {
Unit::class -> null // TODO Maybe not though? could be unit but 200 🤔 Unit::class -> null // TODO Maybe not though? could be unit but 200 🤔
else -> when (responseInfo) { else -> when (responseInfo) {
null -> null // TODO again probably revisit this null -> null // TODO again probably revisit this
else -> { else -> {
val content = responseInfo.mediaTypes.associateWith {
val ref = getReferenceSlug()
OpenApiSpecMediaType.Referenced(OpenApiSpecReferenceObject(ref))
}
val specResponse = OpenApiSpecResponse( val specResponse = OpenApiSpecResponse(
description = responseInfo.description, description = responseInfo.description,
content = responseInfo.mediaTypes.associateWith { content = content.ifEmpty { null }
val ref = getReferenceSlug()
OpenApiSpecMediaType.Referenced(OpenApiSpecReferenceObject(ref))
}
) )
Pair(responseInfo.status, specResponse) Pair(responseInfo.status, specResponse)
} }
} }
} }
// TODO God these annotations make this hideous... any way to improve?
private fun KType.toParameterSpec(): List<OpenApiSpecParameter> {
val clazz = classifier as KClass<*>
return clazz.memberProperties.map { prop ->
val field = prop.javaField?.type?.kotlin
?: error("Unable to parse field type from $prop")
val anny = prop.findAnnotation<PathParam>()
?: prop.findAnnotation<QueryParam>()
?: prop.findAnnotation<HeaderParam>()
?: prop.findAnnotation<CookieParam>()
?: error("Unable to find any relevant parameter specifier annotations on field ${prop.name}")
OpenApiSpecParameter(
name = prop.name,
`in` = when (anny) {
is PathParam -> "path"
is QueryParam -> "query"
is HeaderParam -> "header"
is CookieParam -> "cookie"
else -> error("should not be reachable")
},
schema = OpenApiSpecSchemaRef(field.getReferenceSlug(prop)),
description = when (anny) {
is PathParam -> anny.description.ifBlank { null }
is QueryParam -> anny.description.ifBlank { null }
is HeaderParam -> anny.description.ifBlank { null }
is CookieParam -> anny.description.ifBlank { null }
else -> error("should not be reachable")
}
)
}
}
internal fun resetSchema() { internal fun resetSchema() {
openApiSpec = OpenApiSpec( openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(), info = OpenApiSpecInfo(),
servers = mutableListOf(), servers = mutableListOf(),
paths = mutableMapOf() paths = mutableMapOf()
) )
cache = emptyMap()
} }
} }

View File

@ -33,6 +33,15 @@ object Kontent {
return generateKTypeKontent(kontentType, cache) return generateKTypeKontent(kontentType, cache)
} }
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> generateParameterKontent(
cache: SchemaMap = emptyMap()
): SchemaMap {
val kontentType = typeOf<T>()
return generateKTypeKontent(kontentType, cache)
.filterNot { (slug, _) -> slug == (kontentType.classifier as KClass<*>).simpleName }
}
fun generateKTypeKontent( fun generateKTypeKontent(
type: KType, type: KType,
cache: SchemaMap = emptyMap() cache: SchemaMap = emptyMap()

View File

@ -0,0 +1,5 @@
package org.leafygreens.kompendium.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class CookieParam(val description: String = "")

View File

@ -0,0 +1,5 @@
package org.leafygreens.kompendium.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class HeaderParam(val description: String = "")

View File

@ -0,0 +1,5 @@
package org.leafygreens.kompendium.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class PathParam(val description: String = "")

View File

@ -0,0 +1,5 @@
package org.leafygreens.kompendium.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class QueryParam(val description: String = "")

View File

@ -24,13 +24,13 @@ data class OpenApiSpecHeader(
data class OpenApiSpecParameter( data class OpenApiSpecParameter(
val name: String, val name: String,
val `in`: String, // TODO Enum? "query", "header", "path" or "cookie" val `in`: String, // TODO Enum? "query", "header", "path" or "cookie"
val description: String?, val schema: OpenApiSpecSchema,
val description: String? = null,
val required: Boolean = true, val required: Boolean = true,
val deprecated: Boolean = false, val deprecated: Boolean = false,
val allowEmptyValue: Boolean = false, val allowEmptyValue: Boolean? = null,
val style: String? = null, val style: String? = null,
val explode: Boolean? = false, val explode: Boolean? = null
val schema: OpenApiSpecSchema? = null
) : OpenApiSpecReferencable() ) : OpenApiSpecReferencable()
data class OpenApiSpecRequest( data class OpenApiSpecRequest(

View File

@ -311,7 +311,7 @@ internal class KompendiumTest {
private companion object { private companion object {
val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor") val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor")
val testPostResponse = ResponseInfo(KompendiumHttpCodes.CREATED, "A Successful Endeavor") val testPostResponse = ResponseInfo(KompendiumHttpCodes.CREATED, "A Successful Endeavor")
val testDeleteResponse = ResponseInfo(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor") val testDeleteResponse = ResponseInfo(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor", mediaTypes = emptyList())
val testRequest = RequestInfo("A Test request") val testRequest = RequestInfo("A Test request")
val testGetInfo = MethodInfo("Another get test", "testing more", testGetResponse) val testGetInfo = MethodInfo("Another get test", "testing more", testGetResponse)
val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!", testPostResponse, testRequest) val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!", testPostResponse, testRequest)
@ -351,7 +351,7 @@ internal class KompendiumTest {
private fun Application.notarizedDeleteModule() { private fun Application.notarizedDeleteModule() {
routing { routing {
route("/test") { route("/test") {
notarizedDelete<TestParams, TestDeleteResponse>(testDeleteInfo) { notarizedDelete<TestParams, Unit>(testDeleteInfo) {
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }
} }

View File

@ -4,9 +4,11 @@ import java.util.UUID
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
import org.leafygreens.kompendium.Kontent.generateKontent import org.leafygreens.kompendium.Kontent.generateKontent
import org.leafygreens.kompendium.Kontent.generateParameterKontent
import org.leafygreens.kompendium.models.oas.DictionarySchema import org.leafygreens.kompendium.models.oas.DictionarySchema
import org.leafygreens.kompendium.models.oas.FormatSchema import org.leafygreens.kompendium.models.oas.FormatSchema
import org.leafygreens.kompendium.models.oas.ObjectSchema import org.leafygreens.kompendium.models.oas.ObjectSchema
@ -173,4 +175,15 @@ internal class KontentTest {
assertEquals(ReferencedSchema("#/components/schemas/CrazyItem"), rs) assertEquals(ReferencedSchema("#/components/schemas/CrazyItem"), rs)
} }
@Test
fun `Parameter kontent filters out top level declaration`() {
// do
val result = generateParameterKontent<TestSimpleModel>()
// expect
assertNotNull(result)
assertEquals(2, result.count())
assertFalse { result.containsKey(TestSimpleModel::class.simpleName) }
}
} }

View File

@ -2,6 +2,8 @@ package org.leafygreens.kompendium.util
import java.util.UUID import java.util.UUID
import org.leafygreens.kompendium.annotations.KompendiumField import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.PathParam
import org.leafygreens.kompendium.annotations.QueryParam
data class TestSimpleModel(val a: String, val b: Int) data class TestSimpleModel(val a: String, val b: Int)
@ -17,7 +19,10 @@ data class TestSimpleWithEnumList(val a: Double, val b: List<SimpleEnum>)
data class TestInvalidMap(val a: Map<Int, TestSimpleModel>) data class TestInvalidMap(val a: Map<Int, TestSimpleModel>)
data class TestParams(val a: String, val aa: Int) data class TestParams(
@PathParam val a: String,
@QueryParam val aa: Int
)
data class TestNested(val nesty: String) data class TestNested(val nesty: String)

View File

@ -28,6 +28,7 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Test put endpoint", "summary" : "Test put endpoint",
"description" : "Put your tests here!", "description" : "Put your tests here!",
"parameters" : [ ],
"requestBody" : { "requestBody" : {
"description" : "A Test request", "description" : "A Test request",
"content" : { "content" : {

View File

@ -28,16 +28,26 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Test delete endpoint", "summary" : "Test delete endpoint",
"description" : "testing my deletes", "description" : "testing my deletes",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"$ref" : "#/components/schemas/String"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"$ref" : "#/components/schemas/Int"
},
"required" : true,
"deprecated" : false
} ],
"responses" : { "responses" : {
"204" : { "204" : {
"description" : "A Successful Endeavor", "description" : "A Successful Endeavor"
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/TestDeleteResponse"
}
}
}
} }
}, },
"deprecated" : false "deprecated" : false
@ -46,9 +56,12 @@
}, },
"components" : { "components" : {
"schemas" : { "schemas" : {
"TestDeleteResponse" : { "String" : {
"properties" : { }, "type" : "string"
"type" : "object" },
"Int" : {
"format" : "int32",
"type" : "integer"
} }
}, },
"securitySchemes" : { } "securitySchemes" : { }

View File

@ -28,6 +28,23 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Another get test", "summary" : "Another get test",
"description" : "testing more", "description" : "testing more",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"$ref" : "#/components/schemas/String"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"$ref" : "#/components/schemas/Int"
},
"required" : true,
"deprecated" : false
} ],
"responses" : { "responses" : {
"200" : { "200" : {
"description" : "A Successful Endeavor", "description" : "A Successful Endeavor",
@ -56,6 +73,10 @@
} }
}, },
"type" : "object" "type" : "object"
},
"Int" : {
"format" : "int32",
"type" : "integer"
} }
}, },
"securitySchemes" : { } "securitySchemes" : { }

View File

@ -28,6 +28,23 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Test post endpoint", "summary" : "Test post endpoint",
"description" : "Post your tests here!", "description" : "Post your tests here!",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"$ref" : "#/components/schemas/String"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"$ref" : "#/components/schemas/Int"
},
"required" : true,
"deprecated" : false
} ],
"requestBody" : { "requestBody" : {
"description" : "A Test request", "description" : "A Test request",
"content" : { "content" : {

View File

@ -28,6 +28,7 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Test put endpoint", "summary" : "Test put endpoint",
"description" : "Put your tests here!", "description" : "Put your tests here!",
"parameters" : [ ],
"requestBody" : { "requestBody" : {
"description" : "A Test request", "description" : "A Test request",
"content" : { "content" : {

View File

@ -28,6 +28,23 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Test put endpoint", "summary" : "Test put endpoint",
"description" : "Put your tests here!", "description" : "Put your tests here!",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"$ref" : "#/components/schemas/String"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"$ref" : "#/components/schemas/Int"
},
"required" : true,
"deprecated" : false
} ],
"requestBody" : { "requestBody" : {
"description" : "A Test request", "description" : "A Test request",
"content" : { "content" : {

View File

@ -28,6 +28,23 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Another get test", "summary" : "Another get test",
"description" : "testing more", "description" : "testing more",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"$ref" : "#/components/schemas/String"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"$ref" : "#/components/schemas/Int"
},
"required" : true,
"deprecated" : false
} ],
"responses" : { "responses" : {
"200" : { "200" : {
"description" : "A Successful Endeavor", "description" : "A Successful Endeavor",
@ -56,6 +73,10 @@
} }
}, },
"type" : "object" "type" : "object"
},
"Int" : {
"format" : "int32",
"type" : "integer"
} }
}, },
"securitySchemes" : { } "securitySchemes" : { }

View File

@ -103,12 +103,6 @@
"parameters" : [ { "parameters" : [ {
"name" : "status", "name" : "status",
"in" : "query", "in" : "query",
"description" : "Status values that need to be considered for filter",
"required" : true,
"deprecated" : false,
"allowEmptyValue" : false,
"style" : "form",
"explode" : true,
"schema" : { "schema" : {
"items" : { "items" : {
"default" : "available", "default" : "available",
@ -116,7 +110,12 @@
"type" : "string" "type" : "string"
}, },
"type" : "array" "type" : "array"
} },
"description" : "Status values that need to be considered for filter",
"required" : true,
"deprecated" : false,
"style" : "form",
"explode" : true
} ], } ],
"responses" : { "responses" : {
"200" : { "200" : {

View File

@ -28,6 +28,23 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Another get test", "summary" : "Another get test",
"description" : "testing more", "description" : "testing more",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"$ref" : "#/components/schemas/String"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"$ref" : "#/components/schemas/Int"
},
"required" : true,
"deprecated" : false
} ],
"responses" : { "responses" : {
"200" : { "200" : {
"description" : "A Successful Endeavor", "description" : "A Successful Endeavor",
@ -62,6 +79,10 @@
"$ref" : "#/components/schemas/TestResponse" "$ref" : "#/components/schemas/TestResponse"
}, },
"type" : "array" "type" : "array"
},
"Int" : {
"format" : "int32",
"type" : "integer"
} }
}, },
"securitySchemes" : { } "securitySchemes" : { }

View File

@ -28,6 +28,23 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Another get test", "summary" : "Another get test",
"description" : "testing more", "description" : "testing more",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"$ref" : "#/components/schemas/String"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"$ref" : "#/components/schemas/Int"
},
"required" : true,
"deprecated" : false
} ],
"responses" : { "responses" : {
"200" : { "200" : {
"description" : "A Successful Endeavor", "description" : "A Successful Endeavor",
@ -56,6 +73,10 @@
} }
}, },
"type" : "object" "type" : "object"
},
"Int" : {
"format" : "int32",
"type" : "integer"
} }
}, },
"securitySchemes" : { } "securitySchemes" : { }

View File

@ -28,6 +28,23 @@
"tags" : [ ], "tags" : [ ],
"summary" : "Another get test", "summary" : "Another get test",
"description" : "testing more", "description" : "testing more",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"$ref" : "#/components/schemas/String"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"$ref" : "#/components/schemas/Int"
},
"required" : true,
"deprecated" : false
} ],
"responses" : { "responses" : {
"200" : { "200" : {
"description" : "A Successful Endeavor", "description" : "A Successful Endeavor",
@ -56,6 +73,10 @@
} }
}, },
"type" : "object" "type" : "object"
},
"Int" : {
"format" : "int32",
"type" : "integer"
} }
}, },
"securitySchemes" : { } "securitySchemes" : { }

View File

@ -31,6 +31,8 @@ import org.leafygreens.kompendium.Kompendium.notarizedPost
import org.leafygreens.kompendium.Kompendium.notarizedPut import org.leafygreens.kompendium.Kompendium.notarizedPut
import org.leafygreens.kompendium.Kompendium.openApiSpec import org.leafygreens.kompendium.Kompendium.openApiSpec
import org.leafygreens.kompendium.annotations.KompendiumField import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.PathParam
import org.leafygreens.kompendium.annotations.QueryParam
import org.leafygreens.kompendium.models.meta.MethodInfo import org.leafygreens.kompendium.models.meta.MethodInfo
import org.leafygreens.kompendium.models.meta.RequestInfo import org.leafygreens.kompendium.models.meta.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.meta.ResponseInfo
@ -70,16 +72,16 @@ fun Application.mainModule() {
} }
} }
route("/single") { route("/single") {
notarizedGet<ExampleRequest, ExampleResponse>(testSingleGetInfo) { notarizedGet<Unit, ExampleResponse>(testSingleGetInfo) {
call.respondText("get single") call.respondText("get single")
} }
notarizedPost<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) { notarizedPost<Unit, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) {
call.respondText("test post") call.respondText("test post")
} }
notarizedPut<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) { notarizedPut<JustQuery, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) {
call.respondText { "hey" } call.respondText { "hey" }
} }
notarizedDelete<Unit, DeleteResponse>(testSingleDeleteInfo) { notarizedDelete<Unit, Unit>(testSingleDeleteInfo) {
call.respondText { "heya" } call.respondText { "heya" }
} }
} }
@ -87,12 +89,18 @@ fun Application.mainModule() {
} }
} }
data class ExampleParams(val a: String, val aa: Int) data class ExampleParams(
@PathParam val id: Int,
@QueryParam val name: String
)
data class JustQuery(
@QueryParam val potato: Boolean,
@QueryParam val tomato: String
)
data class ExampleNested(val nesty: String) data class ExampleNested(val nesty: String)
object DeleteResponse
data class ExampleRequest( data class ExampleRequest(
@KompendiumField(name = "field_name") @KompendiumField(name = "field_name")
val fieldName: ExampleNested, val fieldName: ExampleNested,
@ -150,7 +158,8 @@ object KompendiumTOC {
description = "testing my deletes", description = "testing my deletes",
responseInfo = ResponseInfo( responseInfo = ResponseInfo(
status = KompendiumHttpCodes.NO_CONTENT, status = KompendiumHttpCodes.NO_CONTENT,
description = "Signifies that your item was deleted succesfully" description = "Signifies that your item was deleted successfully",
mediaTypes = emptyList()
) )
) )
} }