feat: enable creation of explicit parameter examples (#133)
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
## Unreleased
|
||||
### Added
|
||||
- Support for HTTP Patch, Head, and Options methods
|
||||
- Support for including parameter examples via `MethodInfo`
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -3,6 +3,7 @@ package io.bkbn.kompendium.core
|
||||
import io.bkbn.kompendium.annotations.Param
|
||||
import io.bkbn.kompendium.core.Kontent.generateKontent
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.MethodInfo
|
||||
@ -54,7 +55,7 @@ object MethodParser {
|
||||
operationId = info.operationId,
|
||||
tags = info.tags,
|
||||
deprecated = info.deprecated,
|
||||
parameters = paramType.toParameterSpec(feature),
|
||||
parameters = paramType.toParameterSpec(info, feature),
|
||||
responses = parseResponse(responseType, info.responseInfo, feature).plus(parseExceptions(info.canThrow, feature)),
|
||||
requestBody = when (info) {
|
||||
is PutInfo<*, *, *> -> requestType.toRequestSpec(info.requestInfo, feature)
|
||||
@ -161,7 +162,7 @@ object MethodParser {
|
||||
* @return list of valid parameter specs as detailed by the [KType] members
|
||||
* @throws [IllegalStateException] if the class could not be parsed properly
|
||||
*/
|
||||
private fun KType.toParameterSpec(feature: Kompendium): List<Parameter> {
|
||||
private fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List<Parameter> {
|
||||
val clazz = classifier as KClass<*>
|
||||
return clazz.memberProperties.filter { prop ->
|
||||
prop.findAnnotation<Param>() != null
|
||||
@ -177,11 +178,21 @@ object MethodParser {
|
||||
`in` = anny.type.name.lowercase(Locale.getDefault()),
|
||||
schema = schema.addDefault(defaultValue),
|
||||
description = schema.description,
|
||||
required = !prop.returnType.isMarkedNullable && defaultValue == null
|
||||
required = !prop.returnType.isMarkedNullable && defaultValue == null,
|
||||
examples = info.parameterExamples.mapToSpec(prop.name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Set<ParameterExample>.mapToSpec(parameterName: String): Map<String, Parameter.Example>? {
|
||||
val filtered = filter { it.parameterName == parameterName }
|
||||
return if (filtered.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
filtered.associate { it.exampleName to Parameter.Example(it.exampleValue) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolutely disgusting reflection to determine if a default value is available for a given property.
|
||||
* @param clazz to which the property belongs
|
||||
|
@ -0,0 +1,3 @@
|
||||
package io.bkbn.kompendium.core.metadata
|
||||
|
||||
data class ParameterExample(val parameterName: String, val exampleName: String, val exampleValue: Any)
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.metadata.method
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
|
||||
data class DeleteInfo<TParam, TResp>(
|
||||
@ -11,6 +12,6 @@ data class DeleteInfo<TParam, TResp>(
|
||||
override val deprecated: Boolean = false,
|
||||
override val securitySchemes: Set<String> = emptySet(),
|
||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
||||
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||
override val operationId: String? = null
|
||||
) : MethodInfo<TParam, TResp>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.metadata.method
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
|
||||
data class GetInfo<TParam, TResp>(
|
||||
@ -11,6 +12,6 @@ data class GetInfo<TParam, TResp>(
|
||||
override val deprecated: Boolean = false,
|
||||
override val securitySchemes: Set<String> = emptySet(),
|
||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
||||
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||
override val operationId: String? = null
|
||||
) : MethodInfo<TParam, TResp>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.metadata.method
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
|
||||
data class HeadInfo<TParam>(
|
||||
@ -11,6 +12,6 @@ data class HeadInfo<TParam>(
|
||||
override val deprecated: Boolean = false,
|
||||
override val securitySchemes: Set<String> = emptySet(),
|
||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
||||
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||
override val operationId: String? = null
|
||||
) : MethodInfo<TParam, Unit>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.metadata.method
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
|
||||
sealed interface MethodInfo<TParam, TResp> {
|
||||
@ -16,9 +17,13 @@ sealed interface MethodInfo<TParam, TResp> {
|
||||
val canThrow: Set<ExceptionInfo<*>>
|
||||
get() = emptySet()
|
||||
val responseInfo: ResponseInfo<TResp>
|
||||
// TODO Is this even used anywhere?
|
||||
val parameterExamples: Map<String, TParam>
|
||||
get() = emptyMap()
|
||||
val parameterExamples: Set<ParameterExample>
|
||||
get() = emptySet()
|
||||
val operationId: String?
|
||||
get() = null
|
||||
}
|
||||
|
||||
fun main() {
|
||||
data class Potato(val a: String)
|
||||
println(Potato::a)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.metadata.method
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
|
||||
data class OptionsInfo<TParam, TResp>(
|
||||
@ -11,6 +12,6 @@ data class OptionsInfo<TParam, TResp>(
|
||||
override val deprecated: Boolean = false,
|
||||
override val securitySchemes: Set<String> = emptySet(),
|
||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
||||
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||
override val operationId: String? = null
|
||||
) : MethodInfo<TParam, TResp>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.metadata.method
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
|
||||
@ -13,6 +14,6 @@ data class PatchInfo<TParam, TReq, TResp>(
|
||||
override val deprecated: Boolean = false,
|
||||
override val securitySchemes: Set<String> = emptySet(),
|
||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
||||
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||
override val operationId: String? = null
|
||||
) : MethodInfo<TParam, TResp>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.metadata.method
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
|
||||
@ -13,6 +14,6 @@ data class PostInfo<TParam, TReq, TResp>(
|
||||
override val deprecated: Boolean = false,
|
||||
override val securitySchemes: Set<String> = emptySet(),
|
||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
||||
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||
override val operationId: String? = null
|
||||
) : MethodInfo<TParam, TResp>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.metadata.method
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
|
||||
@ -13,6 +14,6 @@ data class PutInfo<TParam, TReq, TResp>(
|
||||
override val deprecated: Boolean = false,
|
||||
override val securitySchemes: Set<String> = emptySet(),
|
||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
||||
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||
override val operationId: String? = null
|
||||
) : MethodInfo<TParam, TResp>
|
||||
|
@ -8,6 +8,7 @@ import io.bkbn.kompendium.core.util.constrainedDoubleInfo
|
||||
import io.bkbn.kompendium.core.util.constrainedIntInfo
|
||||
import io.bkbn.kompendium.core.util.defaultField
|
||||
import io.bkbn.kompendium.core.util.defaultParameter
|
||||
import io.bkbn.kompendium.core.util.exampleParams
|
||||
import io.bkbn.kompendium.core.util.exclusiveMinMax
|
||||
import io.bkbn.kompendium.core.util.formattedParam
|
||||
import io.bkbn.kompendium.core.util.freeFormObject
|
||||
@ -150,6 +151,9 @@ class KompendiumTest : DescribeSpec({
|
||||
it("Can generate example response and request bodies") {
|
||||
openApiTest("example_req_and_resp.json") { withExamples() }
|
||||
}
|
||||
it("Can describe example parameters") {
|
||||
openApiTest("example_parameters.json") { exampleParams() }
|
||||
}
|
||||
}
|
||||
describe("Defaults") {
|
||||
it("Can generate a default parameter values") {
|
||||
|
@ -564,3 +564,13 @@ fun Application.minMaxFreeForm() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Application.exampleParams() {
|
||||
routing {
|
||||
route("/test/{a}") {
|
||||
notarizedGet(TestResponseInfo.exampleParams) {
|
||||
call.respondText { "Hi 🌊" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
97
kompendium-core/src/test/resources/example_parameters.json
Normal file
97
kompendium-core/src/test/resources/example_parameters.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "Test API",
|
||||
"version": "1.33.7",
|
||||
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||
"termsOfService": "https://example.com",
|
||||
"contact": {
|
||||
"name": "Homer Simpson",
|
||||
"url": "https://gph.is/1NPUDiM",
|
||||
"email": "chunkylover53@aol.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "MIT",
|
||||
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://myawesomeapi.com",
|
||||
"description": "Production instance of my API"
|
||||
},
|
||||
{
|
||||
"url": "https://staging.myawesomeapi.com",
|
||||
"description": "Where the fun stuff happens"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/test/{a}": {
|
||||
"get": {
|
||||
"tags": [],
|
||||
"summary": "param stuff",
|
||||
"description": "Cool stuff",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "a",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"examples": {
|
||||
"Testerino": {
|
||||
"value": "a"
|
||||
},
|
||||
"Testerina": {
|
||||
"value": "b"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aa",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"examples": {
|
||||
"Wowza": {
|
||||
"value": 6
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful endeavor",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"c": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"c"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
"tags": []
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package io.bkbn.kompendium.core.fixtures
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.PutInfo
|
||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||
@ -266,5 +267,16 @@ object TestResponseInfo {
|
||||
requestInfo = RequestInfo("cool")
|
||||
)
|
||||
|
||||
val exampleParams = GetInfo<TestParams, TestResponse>(
|
||||
summary = "param stuff",
|
||||
description = "Cool stuff",
|
||||
responseInfo = simpleOkResponse(),
|
||||
parameterExamples = setOf(
|
||||
ParameterExample(TestParams::a.name, "Testerino", "a"),
|
||||
ParameterExample(TestParams::a.name, "Testerina", "b"),
|
||||
ParameterExample(TestParams::aa.name, "Wowza", 6),
|
||||
)
|
||||
)
|
||||
|
||||
private fun <T> simpleOkResponse() = ResponseInfo<T>(HttpStatusCode.OK, "A successful endeavor")
|
||||
}
|
||||
|
@ -11,5 +11,8 @@ data class Parameter(
|
||||
val deprecated: Boolean = false,
|
||||
val allowEmptyValue: Boolean? = null,
|
||||
val style: String? = null,
|
||||
val explode: Boolean? = null
|
||||
)
|
||||
val explode: Boolean? = null,
|
||||
val examples: Map<String, Example>? = null
|
||||
) {
|
||||
data class Example(val value: Any)
|
||||
}
|
||||
|
Reference in New Issue
Block a user