From c6ed261fe46b02792a7d91d2190b7c07901b9257 Mon Sep 17 00:00:00 2001 From: Ryan Brink <5607577+unredundant@users.noreply.github.com> Date: Mon, 3 Jan 2022 10:34:02 -0500 Subject: [PATCH] feat: enable creation of explicit parameter examples (#133) --- CHANGELOG.md | 1 + .../io/bkbn/kompendium/core/MethodParser.kt | 17 +++- .../core/metadata/ParameterExample.kt | 3 + .../core/metadata/method/DeleteInfo.kt | 3 +- .../core/metadata/method/GetInfo.kt | 3 +- .../core/metadata/method/HeadInfo.kt | 3 +- .../core/metadata/method/MethodInfo.kt | 11 ++- .../core/metadata/method/OptionsInfo.kt | 3 +- .../core/metadata/method/PatchInfo.kt | 3 +- .../core/metadata/method/PostInfo.kt | 3 +- .../core/metadata/method/PutInfo.kt | 3 +- .../io/bkbn/kompendium/core/KompendiumTest.kt | 4 + .../bkbn/kompendium/core/util/TestModules.kt | 10 ++ .../test/resources/example_parameters.json | 97 +++++++++++++++++++ .../core/fixtures/TestResponseInfo.kt | 12 +++ .../bkbn/kompendium/oas/payload/Parameter.kt | 7 +- 16 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ParameterExample.kt create mode 100644 kompendium-core/src/test/resources/example_parameters.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 119984fe7..e0b2f1eba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Added - Support for HTTP Patch, Head, and Options methods +- Support for including parameter examples via `MethodInfo` ### Changed diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/MethodParser.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/MethodParser.kt index 117a17d75..ecf9591b7 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/MethodParser.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/MethodParser.kt @@ -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 { + private fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List { val clazz = classifier as KClass<*> return clazz.memberProperties.filter { prop -> prop.findAnnotation() != 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.mapToSpec(parameterName: String): Map? { + 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 diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ParameterExample.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ParameterExample.kt new file mode 100644 index 000000000..11dcafebd --- /dev/null +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ParameterExample.kt @@ -0,0 +1,3 @@ +package io.bkbn.kompendium.core.metadata + +data class ParameterExample(val parameterName: String, val exampleName: String, val exampleValue: Any) diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/DeleteInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/DeleteInfo.kt index 07548b36c..f27875984 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/DeleteInfo.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/DeleteInfo.kt @@ -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( @@ -11,6 +12,6 @@ data class DeleteInfo( override val deprecated: Boolean = false, override val securitySchemes: Set = emptySet(), override val canThrow: Set> = emptySet(), - override val parameterExamples: Map = emptyMap(), + override val parameterExamples: Set = emptySet(), override val operationId: String? = null ) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/GetInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/GetInfo.kt index 420c83501..b1737591b 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/GetInfo.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/GetInfo.kt @@ -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( @@ -11,6 +12,6 @@ data class GetInfo( override val deprecated: Boolean = false, override val securitySchemes: Set = emptySet(), override val canThrow: Set> = emptySet(), - override val parameterExamples: Map = emptyMap(), + override val parameterExamples: Set = emptySet(), override val operationId: String? = null ) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/HeadInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/HeadInfo.kt index c97a45758..f7de1ced9 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/HeadInfo.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/HeadInfo.kt @@ -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( @@ -11,6 +12,6 @@ data class HeadInfo( override val deprecated: Boolean = false, override val securitySchemes: Set = emptySet(), override val canThrow: Set> = emptySet(), - override val parameterExamples: Map = emptyMap(), + override val parameterExamples: Set = emptySet(), override val operationId: String? = null ) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/MethodInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/MethodInfo.kt index 9ffe33a90..6a53f111e 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/MethodInfo.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/MethodInfo.kt @@ -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 { @@ -16,9 +17,13 @@ sealed interface MethodInfo { val canThrow: Set> get() = emptySet() val responseInfo: ResponseInfo - // TODO Is this even used anywhere? - val parameterExamples: Map - get() = emptyMap() + val parameterExamples: Set + get() = emptySet() val operationId: String? get() = null } + +fun main() { + data class Potato(val a: String) + println(Potato::a) +} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/OptionsInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/OptionsInfo.kt index 55aa7373e..0150ce175 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/OptionsInfo.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/OptionsInfo.kt @@ -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( @@ -11,6 +12,6 @@ data class OptionsInfo( override val deprecated: Boolean = false, override val securitySchemes: Set = emptySet(), override val canThrow: Set> = emptySet(), - override val parameterExamples: Map = emptyMap(), + override val parameterExamples: Set = emptySet(), override val operationId: String? = null ) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PatchInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PatchInfo.kt index ea7759de5..6cd819bc7 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PatchInfo.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PatchInfo.kt @@ -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( override val deprecated: Boolean = false, override val securitySchemes: Set = emptySet(), override val canThrow: Set> = emptySet(), - override val parameterExamples: Map = emptyMap(), + override val parameterExamples: Set = emptySet(), override val operationId: String? = null ) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PostInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PostInfo.kt index 25058686a..cc8dd688c 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PostInfo.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PostInfo.kt @@ -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( override val deprecated: Boolean = false, override val securitySchemes: Set = emptySet(), override val canThrow: Set> = emptySet(), - override val parameterExamples: Map = emptyMap(), + override val parameterExamples: Set = emptySet(), override val operationId: String? = null ) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PutInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PutInfo.kt index 9331ef2e9..409e56937 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PutInfo.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PutInfo.kt @@ -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( override val deprecated: Boolean = false, override val securitySchemes: Set = emptySet(), override val canThrow: Set> = emptySet(), - override val parameterExamples: Map = emptyMap(), + override val parameterExamples: Set = emptySet(), override val operationId: String? = null ) : MethodInfo diff --git a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt index a711dab54..d3efd9b13 100644 --- a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt +++ b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt @@ -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") { diff --git a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt index f1d0467a6..c6ecbd9ae 100644 --- a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt +++ b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt @@ -564,3 +564,13 @@ fun Application.minMaxFreeForm() { } } } + +fun Application.exampleParams() { + routing { + route("/test/{a}") { + notarizedGet(TestResponseInfo.exampleParams) { + call.respondText { "Hi 🌊" } + } + } + } +} diff --git a/kompendium-core/src/test/resources/example_parameters.json b/kompendium-core/src/test/resources/example_parameters.json new file mode 100644 index 000000000..6d9508c26 --- /dev/null +++ b/kompendium-core/src/test/resources/example_parameters.json @@ -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": [] +} diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt index 9ecb89ab7..d9763ad88 100644 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt +++ b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt @@ -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( + 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 simpleOkResponse() = ResponseInfo(HttpStatusCode.OK, "A successful endeavor") } diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt index 5fe191bb3..0d216b6cb 100644 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt +++ b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt @@ -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? = null +) { + data class Example(val value: Any) +}