diff --git a/CHANGELOG.md b/CHANGELOG.md index 34285790f..57c9cb2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +### [0.9.0] - May 5th, 2021 + +### Added + +- Support for default parameter responses + +### Changed + +- In order to facilitate default parameters, a couple changes were needed + - `KompendiumParam` was added in replacement of the four parameter annotations + - Specs now explicitly declare type of parameter rather than a reference in order to not override default values. + ## [0.8.0] - May 4th, 2021 ### Added diff --git a/README.md b/README.md index b2f393bdc..77334dfb6 100644 --- a/README.md +++ b/README.md @@ -79,15 +79,12 @@ integrated. Currently, the annotations used by Kompendium are as follows - `KompendiumField` -- `PathParam` -- `QueryParam` -- `HeaderParam` -- `CookieParam` +- `KompendiumParam` 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. +The purpose of `KompendiumParam` is to provide supplemental information needed to properly assign the type of parameter +(cookie, header, query, path) as well as other parameter-level metadata. ## Examples diff --git a/gradle.properties b/gradle.properties index e4456ad56..20203f766 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Kompendium -project.version=0.8.0 +project.version=0.9.0 # Kotlin kotlin.code.style=official # Gradle diff --git a/kompendium-auth/src/test/kotlin/org/leafygreens/kompendium/auth/util/TestModels.kt b/kompendium-auth/src/test/kotlin/org/leafygreens/kompendium/auth/util/TestModels.kt index 8d172c510..937c92c03 100644 --- a/kompendium-auth/src/test/kotlin/org/leafygreens/kompendium/auth/util/TestModels.kt +++ b/kompendium-auth/src/test/kotlin/org/leafygreens/kompendium/auth/util/TestModels.kt @@ -1,12 +1,12 @@ package org.leafygreens.kompendium.auth.util import org.leafygreens.kompendium.annotations.KompendiumField -import org.leafygreens.kompendium.annotations.PathParam -import org.leafygreens.kompendium.annotations.QueryParam +import org.leafygreens.kompendium.annotations.KompendiumParam +import org.leafygreens.kompendium.annotations.ParamType data class TestParams( - @PathParam val a: String, - @QueryParam val aa: Int + @KompendiumParam(ParamType.PATH) val a: String, + @KompendiumParam(ParamType.QUERY) val aa: Int ) data class TestRequest( diff --git a/kompendium-auth/src/test/resources/notarized_basic_authenticated_get.json b/kompendium-auth/src/test/resources/notarized_basic_authenticated_get.json index 808d1936a..385d5480e 100644 --- a/kompendium-auth/src/test/resources/notarized_basic_authenticated_get.json +++ b/kompendium-auth/src/test/resources/notarized_basic_authenticated_get.json @@ -12,7 +12,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -20,7 +20,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-auth/src/test/resources/notarized_jwt_authenticated_get.json b/kompendium-auth/src/test/resources/notarized_jwt_authenticated_get.json index 1bf1389e8..41da0680f 100644 --- a/kompendium-auth/src/test/resources/notarized_jwt_authenticated_get.json +++ b/kompendium-auth/src/test/resources/notarized_jwt_authenticated_get.json @@ -12,7 +12,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -20,7 +20,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-auth/src/test/resources/notarized_jwt_custom_header_authenticated_get.json b/kompendium-auth/src/test/resources/notarized_jwt_custom_header_authenticated_get.json index 971473bd1..47fe72e4c 100644 --- a/kompendium-auth/src/test/resources/notarized_jwt_custom_header_authenticated_get.json +++ b/kompendium-auth/src/test/resources/notarized_jwt_custom_header_authenticated_get.json @@ -12,7 +12,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -20,7 +20,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-auth/src/test/resources/notarized_jwt_custom_scheme_authenticated_get.json b/kompendium-auth/src/test/resources/notarized_jwt_custom_scheme_authenticated_get.json index 904bab9fa..b0e7a3dc2 100644 --- a/kompendium-auth/src/test/resources/notarized_jwt_custom_scheme_authenticated_get.json +++ b/kompendium-auth/src/test/resources/notarized_jwt_custom_scheme_authenticated_get.json @@ -12,7 +12,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -20,7 +20,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-auth/src/test/resources/notarized_multiple_jwt_authenticated_get.json b/kompendium-auth/src/test/resources/notarized_multiple_jwt_authenticated_get.json index 122920e4c..bdd38a419 100644 --- a/kompendium-auth/src/test/resources/notarized_multiple_jwt_authenticated_get.json +++ b/kompendium-auth/src/test/resources/notarized_multiple_jwt_authenticated_get.json @@ -12,7 +12,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -20,7 +20,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt index d1c4208ea..2ab746974 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt @@ -1,36 +1,11 @@ package org.leafygreens.kompendium -import io.ktor.http.HttpMethod -import kotlin.reflect.KClass -import kotlin.reflect.KType -import kotlin.reflect.full.createType -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.memberProperties -import kotlin.reflect.jvm.javaField -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.ErrorMap -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 -import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter -import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation -import org.leafygreens.kompendium.models.oas.OpenApiSpecReferencable -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.OpenApiSpecSchemaRef import org.leafygreens.kompendium.path.CorePathCalculator import org.leafygreens.kompendium.path.PathCalculator -import org.leafygreens.kompendium.util.Helpers -import org.leafygreens.kompendium.util.Helpers.getReferenceSlug object Kompendium { @@ -45,124 +20,6 @@ object Kompendium { var pathCalculator: PathCalculator = CorePathCalculator() - // TODO here down is a mess, needs refactor once core functionality is in place - fun parseMethodInfo( - info: MethodInfo<*, *>, - paramType: KType, - requestType: KType, - responseType: KType - ) = OpenApiSpecPathItemOperation( - summary = info.summary, - description = info.description, - tags = info.tags, - deprecated = info.deprecated, - parameters = paramType.toParameterSpec(), - responses = responseType.toResponseSpec(info.responseInfo)?.let { mapOf(it) }.let { - when (it) { - null -> { - val throwables = parseThrowables(info.canThrow) - when (throwables.isEmpty()) { - true -> null - false -> throwables - } - } - else -> it.plus(parseThrowables(info.canThrow)) - } - }, - 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 - info.securitySchemes.associateWith { listOf() } - ) else null - ) - - private fun parseThrowables(throwables: Set>): Map = throwables.mapNotNull { - errorMap[it.createType()] - }.toMap() - - fun ResponseInfo.parseErrorInfo( - errorType: KType, - responseType: KType - ) { - errorMap = errorMap.plus(errorType to responseType.toResponseSpec(this)) - } - - // 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, requestInfo.examples) ?: mapOf() - ) - } - } - - private fun KType.toResponseSpec(responseInfo: ResponseInfo?): Pair>? = - 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, - examples: Map - ): Map>? { - return if (this != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) { - mediaTypes.associateWith { - val ref = getReferenceSlug() - 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 { - 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() - ?: prop.findAnnotation() - ?: prop.findAnnotation() - ?: prop.findAnnotation() - ?: 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") - }, - required = !prop.returnType.isMarkedNullable - ) - } - } - fun resetSchema() { openApiSpec = OpenApiSpec( info = OpenApiSpecInfo(), diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/MethodParser.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/MethodParser.kt new file mode 100644 index 000000000..9b2a26667 --- /dev/null +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/MethodParser.kt @@ -0,0 +1,166 @@ +package org.leafygreens.kompendium + +import java.util.UUID +import kotlin.reflect.KClass +import kotlin.reflect.KParameter +import kotlin.reflect.KProperty +import kotlin.reflect.KType +import kotlin.reflect.full.createType +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.javaField +import org.leafygreens.kompendium.annotations.KompendiumParam +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.ExampleWrapper +import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType +import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter +import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation +import org.leafygreens.kompendium.models.oas.OpenApiSpecReferencable +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 +import org.leafygreens.kompendium.util.Helpers.getReferenceSlug +import org.leafygreens.kompendium.util.Helpers.getSimpleSlug + +object MethodParser { + fun parseMethodInfo( + info: MethodInfo<*, *>, + paramType: KType, + requestType: KType, + responseType: KType + ) = OpenApiSpecPathItemOperation( + summary = info.summary, + description = info.description, + tags = info.tags, + deprecated = info.deprecated, + parameters = paramType.toParameterSpec(), + responses = responseType.toResponseSpec(info.responseInfo)?.let { mapOf(it) }.let { + when (it) { + null -> { + val throwables = parseThrowables(info.canThrow) + when (throwables.isEmpty()) { + true -> null + false -> throwables + } + } + else -> it.plus(parseThrowables(info.canThrow)) + } + }, + 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 + info.securitySchemes.associateWith { listOf() } + ) else null + ) + + private fun parseThrowables(throwables: Set>): Map = throwables.mapNotNull { + Kompendium.errorMap[it.createType()] + }.toMap() + + fun ResponseInfo.parseErrorInfo( + errorType: KType, + responseType: KType + ) { + Kompendium.errorMap = Kompendium.errorMap.plus(errorType to responseType.toResponseSpec(this)) + } + + // 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, requestInfo.examples) ?: mapOf() + ) + } + } + + private fun KType.toResponseSpec(responseInfo: ResponseInfo?): Pair>? = + 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, + examples: Map + ): Map>? { + return if (this != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) { + mediaTypes.associateWith { + val ref = getReferenceSlug() + OpenApiSpecMediaType( + schema = OpenApiSpecReferenceObject(ref), + examples = examples.mapValues { (_, v) -> ExampleWrapper(v) }.ifEmpty { null } + ) + } + } else null + } + + private fun KType.toParameterSpec(): List { + 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() + ?: error("Field ${prop.name} is not annotated with KompendiumParam") + val schema = Kompendium.cache[field.getSimpleSlug(prop)] + ?: error("Could not find component type for $prop") + val defaultValue = getDefaultParameterValue(clazz, prop) + OpenApiSpecParameter( + name = prop.name, + `in` = anny.type.name.toLowerCase(), + schema = schema.addDefault(defaultValue), + description = anny.description.ifBlank { null }, + required = !prop.returnType.isMarkedNullable + ) + } + } + + private fun getDefaultParameterValue(clazz: KClass<*>, prop: KProperty<*>): Any? { + val constructor = clazz.primaryConstructor + val parameterInQuestion = constructor + ?.parameters + ?.find { it.name == prop.name } + ?: error("could not find parameter ${prop.name}") + if (!parameterInQuestion.isOptional) { + return null + } + val values = constructor + .parameters + .filterNot { it.isOptional } + .associateWith { defaultValueInjector(it) } + val instance = constructor.callBy(values) + val methods = clazz.java.methods + val getterName = "get${prop.name.capitalize()}" + val getterFunction = methods.find { it.name == getterName } + ?: error("Could not associate ${prop.name} with a getter") + return getterFunction.invoke(instance) + } + + private fun defaultValueInjector(param: KParameter): Any = when (param.type.classifier) { + String::class -> "test" + Boolean::class -> false + Int::class -> 1 + Long::class -> 2 + Double::class -> 1.0 + Float::class -> 1.0 + UUID::class -> UUID.randomUUID() + else -> error("Unsupported Type") + } + +} diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Notarized.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Notarized.kt index 9b6f8deb1..f6b583c1c 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Notarized.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Notarized.kt @@ -7,9 +7,9 @@ import io.ktor.routing.Route import io.ktor.routing.method import io.ktor.util.pipeline.PipelineContext 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.MethodParser.parseErrorInfo +import org.leafygreens.kompendium.MethodParser.parseMethodInfo import org.leafygreens.kompendium.models.meta.MethodInfo.GetInfo import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo import org.leafygreens.kompendium.models.meta.MethodInfo.PutInfo diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/CookieParam.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/CookieParam.kt deleted file mode 100644 index 41fd10cbc..000000000 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/CookieParam.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.leafygreens.kompendium.annotations - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class CookieParam(val description: String = "") diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/HeaderParam.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/HeaderParam.kt deleted file mode 100644 index fcbca2eff..000000000 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/HeaderParam.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.leafygreens.kompendium.annotations - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class HeaderParam(val description: String = "") diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumParam.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumParam.kt new file mode 100644 index 000000000..2c8511b1d --- /dev/null +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumParam.kt @@ -0,0 +1,12 @@ +package org.leafygreens.kompendium.annotations + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.PROPERTY) +annotation class KompendiumParam(val type: ParamType, val description: String = "") + +enum class ParamType { + COOKIE, + HEADER, + PATH, + QUERY +} diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/PathParam.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/PathParam.kt deleted file mode 100644 index 31ec0e4f0..000000000 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/PathParam.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.leafygreens.kompendium.annotations - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class PathParam(val description: String = "") diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/QueryParam.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/QueryParam.kt deleted file mode 100644 index 501df22c5..000000000 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/QueryParam.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.leafygreens.kompendium.annotations - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class QueryParam(val description: String = "") diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecComponentSchema.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecComponentSchema.kt index 75474f0fe..ea5a5d87b 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecComponentSchema.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecComponentSchema.kt @@ -1,26 +1,42 @@ package org.leafygreens.kompendium.models.oas -// TODO Enum for type? -sealed class OpenApiSpecComponentSchema +sealed class OpenApiSpecComponentSchema(open val default: Any? = null) { -sealed class TypedSchema(open val type: String) : OpenApiSpecComponentSchema() + fun addDefault(default: Any?): OpenApiSpecComponentSchema = when (this) { + is ReferencedSchema -> this.copy(default = default) + is ObjectSchema -> this.copy(default = default) + is DictionarySchema -> this.copy(default = default) + is EnumSchema -> this.copy(default = default) + is SimpleSchema -> this.copy(default = default) + is FormatSchema -> this.copy(default = default) + is ArraySchema -> this.copy(default = default) + } -data class ReferencedSchema(val `$ref`: String) : OpenApiSpecComponentSchema() +} + +sealed class TypedSchema(open val type: String, override val default: Any? = null) : OpenApiSpecComponentSchema(default) + +data class ReferencedSchema(val `$ref`: String, override val default: Any? = null) : OpenApiSpecComponentSchema(default) data class ObjectSchema( - val properties: Map -) : TypedSchema("object") + val properties: Map, + override val default: Any? = null +) : TypedSchema("object", default) data class DictionarySchema( - val additionalProperties: OpenApiSpecComponentSchema -) : TypedSchema("object") + val additionalProperties: OpenApiSpecComponentSchema, + override val default: Any? = null +) : TypedSchema("object", default) data class EnumSchema( - val `enum`: Set -) : TypedSchema("string") + val `enum`: Set, override val default: Any? = null +) : TypedSchema("string", default) -data class SimpleSchema(override val type: String) : TypedSchema(type) +data class SimpleSchema(override val type: String, override val default: Any? = null) : TypedSchema(type, default) -data class FormatSchema(val format: String, override val type: String) : TypedSchema(type) +data class FormatSchema(val format: String, override val type: String, override val default: Any? = null) : + TypedSchema(type, default) + +data class ArraySchema(val items: OpenApiSpecComponentSchema, override val default: Any? = null) : + TypedSchema("array", default) -data class ArraySchema(val items: OpenApiSpecComponentSchema) : TypedSchema("array") diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecReferencable.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecReferencable.kt index c72443234..0b0cac552 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecReferencable.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecReferencable.kt @@ -14,7 +14,7 @@ data class OpenApiSpecResponse( data class OpenApiSpecParameter( val name: String, val `in`: String, // TODO Enum? "query", "header", "path" or "cookie" - val schema: OpenApiSpecSchema, + val schema: OpenApiSpecComponentSchema, val description: String? = null, val required: Boolean = true, val deprecated: Boolean = false, diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecSchema.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecSchema.kt deleted file mode 100644 index c999deeea..000000000 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecSchema.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.leafygreens.kompendium.models.oas - -sealed class OpenApiSpecSchema - -sealed class OpenApiSpecSchemaTyped( - val type: String, -) : OpenApiSpecSchema() - -data class OpenApiSpecSchemaArray( - val items: T -) : OpenApiSpecSchemaTyped("array") - -data class OpenApiSpecSchemaString( - val default: String, - val `enum`: Set? = null -) : OpenApiSpecSchemaTyped("string") - -// TODO In Kt 1.5 Should be able to reference external sealed classes -data class OpenApiSpecSchemaRef( - val `$ref`: String -) : OpenApiSpecSchema() - -data class OpenApiSpecSchemaSecurity( - val type: String? = null, // TODO Enum? "apiKey", "http", "oauth2", "openIdConnect" - val name: String? = null, - val `in`: String? = null, - val scheme: String? = null, - val flows: OpenApiSpecOAuthFlows? = null, - val bearerFormat: String? = null, - val description: String? = null, -) : OpenApiSpecSchema() diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecSchemaSecurity.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecSchemaSecurity.kt new file mode 100644 index 000000000..7732b8ece --- /dev/null +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecSchemaSecurity.kt @@ -0,0 +1,11 @@ +package org.leafygreens.kompendium.models.oas + +data class OpenApiSpecSchemaSecurity( + val type: String? = null, // TODO Enum? "apiKey", "http", "oauth2", "openIdConnect" + val name: String? = null, + val `in`: String? = null, + val scheme: String? = null, + val flows: OpenApiSpecOAuthFlows? = null, + val bearerFormat: String? = null, + val description: String? = null, +) diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt index a5120bb32..5cc11afca 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt @@ -1,17 +1,13 @@ package org.leafygreens.kompendium.util -import io.ktor.routing.PathSegmentConstantRouteSelector -import io.ktor.routing.PathSegmentParameterRouteSelector -import io.ktor.routing.RootRouteSelector -import io.ktor.routing.Route -import io.ktor.util.InternalAPI import java.lang.reflect.ParameterizedType import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlin.reflect.KType -import kotlin.reflect.jvm.javaField -import org.slf4j.LoggerFactory import kotlin.reflect.full.createType +import kotlin.reflect.jvm.javaField +import org.leafygreens.kompendium.util.Helpers.getReferenceSlug +import org.slf4j.LoggerFactory object Helpers { @@ -53,6 +49,11 @@ object Helpers { return result } + fun KClass<*>.getSimpleSlug(prop: KProperty<*>): String = when { + this.typeParameters.isNotEmpty() -> genericNameAdapter(this, prop) + else -> simpleName ?: error("Could not determine simple name for $this") + } + fun KType.getReferenceSlug(): String = when { arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}" else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}" diff --git a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt index bb9ff4770..aca8622bf 100644 --- a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt +++ b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt @@ -25,11 +25,12 @@ import org.leafygreens.kompendium.Notarized.notarizedException 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.annotations.KompendiumParam +import org.leafygreens.kompendium.annotations.ParamType +import org.leafygreens.kompendium.models.meta.MethodInfo.DeleteInfo 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,6 +40,7 @@ import org.leafygreens.kompendium.models.oas.OpenApiSpecServer import org.leafygreens.kompendium.routes.openApi import org.leafygreens.kompendium.routes.redoc import org.leafygreens.kompendium.util.ComplexRequest +import org.leafygreens.kompendium.util.DefaultParameter import org.leafygreens.kompendium.util.ExceptionResponse import org.leafygreens.kompendium.util.KompendiumHttpCodes import org.leafygreens.kompendium.util.TestCreatedResponse @@ -430,6 +432,22 @@ internal class KompendiumTest { } } + @Test + fun `Can generate a default parameter value`() { + withTestApplication({ + configModule() + docs() + withDefaultParameter() + }) { + // do + val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content + + // expect + val expected = getFileSnapshot("query_with_default_parameter.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 testGetListResponse = ResponseInfo>(KompendiumHttpCodes.OK, "A Successful List-y Endeavor") @@ -440,21 +458,55 @@ internal class KompendiumTest { val testRequest = RequestInfo("A Test request") val testRequestAgain = RequestInfo("A Test request") val complexRequest = RequestInfo("A Complex request") - val testGetInfo = GetInfo(summary = "Another get test", description = "testing more", responseInfo = testGetResponse) - val testGetInfoAgain = GetInfo>(summary = "Another get test", description = "testing more", responseInfo = testGetListResponse) + val testGetInfo = GetInfo( + summary = "Another get test", + description = "testing more", + responseInfo = testGetResponse + ) + val testGetInfoAgain = GetInfo>( + 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 = PostInfo(summary = "Test post endpoint", description = "Post your tests here!", responseInfo = testPostResponse, requestInfo = testRequest) - val testPutInfo = PutInfo(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponse, requestInfo = complexRequest) - val testPutInfoAlso = PutInfo(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponse, requestInfo = testRequest) - val testPutInfoAgain = PutInfo(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponseAgain, requestInfo = testRequestAgain) - val testDeleteInfo = DeleteInfo(summary = "Test delete endpoint", description = "testing my deletes", responseInfo = testDeleteResponse) - val emptyTestGetInfo = GetInfo(summary = "No request params and response body", description = "testing more") - val trulyEmptyTestGetInfo = GetInfo(summary = "No request params and response body", description = "testing more") + val testPostInfo = PostInfo( + summary = "Test post endpoint", + description = "Post your tests here!", + responseInfo = testPostResponse, + requestInfo = testRequest + ) + val testPutInfo = PutInfo( + summary = "Test put endpoint", + description = "Put your tests here!", + responseInfo = testPostResponse, + requestInfo = complexRequest + ) + val testPutInfoAlso = PutInfo( + summary = "Test put endpoint", + description = "Put your tests here!", + responseInfo = testPostResponse, + requestInfo = testRequest + ) + val testPutInfoAgain = PutInfo( + summary = "Test put endpoint", + description = "Put your tests here!", + responseInfo = testPostResponseAgain, + requestInfo = testRequestAgain + ) + val testDeleteInfo = DeleteInfo( + summary = "Test delete endpoint", + description = "testing my deletes", + responseInfo = testDeleteResponse + ) + val emptyTestGetInfo = + GetInfo(summary = "No request params and response body", description = "testing more") + val trulyEmptyTestGetInfo = + GetInfo(summary = "No request params and response body", description = "testing more") } private fun Application.configModule() { @@ -642,29 +694,50 @@ internal class KompendiumTest { private fun Application.withExamples() { routing { route("/test/examples") { - notarizedPost(info = PostInfo( - 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")) - ), - )) { + notarizedPost( + info = PostInfo( + 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) } } } } - data class OptionalParams(@QueryParam val required: String, @QueryParam val notRequired: String?) + private fun Application.withDefaultParameter() { + routing { + route("/test") { + notarizedGet( + info = GetInfo( + summary = "Testing Default Params", + description = "Should have a default parameter value" + ) + ) { + call.respond(TestResponse("hey")) + } + } + } + } + + data class OptionalParams( + @KompendiumParam(ParamType.QUERY) val required: String, + @KompendiumParam(ParamType.QUERY) val notRequired: String? + ) + private fun Application.nonRequiredParamsGet() { routing { route("/test/optional") { diff --git a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestHelpers.kt b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestHelpers.kt index 7464e737b..33ab1d827 100644 --- a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestHelpers.kt +++ b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestHelpers.kt @@ -1,28 +1,6 @@ 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 { diff --git a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestModels.kt b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestModels.kt index a07c89619..397da0d0e 100644 --- a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestModels.kt +++ b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestModels.kt @@ -1,9 +1,12 @@ package org.leafygreens.kompendium.util import java.util.UUID +import kotlin.reflect.KParameter +import kotlin.reflect.full.primaryConstructor +import org.leafygreens.kompendium.MethodParser import org.leafygreens.kompendium.annotations.KompendiumField -import org.leafygreens.kompendium.annotations.PathParam -import org.leafygreens.kompendium.annotations.QueryParam +import org.leafygreens.kompendium.annotations.KompendiumParam +import org.leafygreens.kompendium.annotations.ParamType data class TestSimpleModel(val a: String, val b: Int) @@ -20,8 +23,8 @@ data class TestSimpleWithEnumList(val a: Double, val b: List) data class TestInvalidMap(val a: Map) data class TestParams( - @PathParam val a: String, - @QueryParam val aa: Int + @KompendiumParam(ParamType.PATH) val a: String, + @KompendiumParam(ParamType.QUERY) val aa: Int ) data class TestNested(val nesty: String) @@ -66,6 +69,12 @@ enum class SimpleEnum { TWO } +data class DefaultParameter( + @KompendiumParam(ParamType.QUERY) val a: Int = 100, + @KompendiumParam(ParamType.PATH) val b: String?, + @KompendiumParam(ParamType.PATH) val c: Boolean +) + sealed class TestSealedClass(open val a: String) data class SimpleTSC(val b: Int) : TestSealedClass("hey") diff --git a/kompendium-core/src/test/resources/nested_under_root.json b/kompendium-core/src/test/resources/nested_under_root.json index 8e5cbc12c..99fdd0d41 100644 --- a/kompendium-core/src/test/resources/nested_under_root.json +++ b/kompendium-core/src/test/resources/nested_under_root.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/non_required_params.json b/kompendium-core/src/test/resources/non_required_params.json index c1a6023f3..e10d51866 100644 --- a/kompendium-core/src/test/resources/non_required_params.json +++ b/kompendium-core/src/test/resources/non_required_params.json @@ -32,7 +32,7 @@ "name" : "notRequired", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : false, "deprecated" : false @@ -40,7 +40,7 @@ "name" : "required", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/notarized_delete.json b/kompendium-core/src/test/resources/notarized_delete.json index 5217d02b0..2f76fcad2 100644 --- a/kompendium-core/src/test/resources/notarized_delete.json +++ b/kompendium-core/src/test/resources/notarized_delete.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/notarized_get.json b/kompendium-core/src/test/resources/notarized_get.json index 55edcdba9..b69b3d7cc 100644 --- a/kompendium-core/src/test/resources/notarized_get.json +++ b/kompendium-core/src/test/resources/notarized_get.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/notarized_get_with_exception_response.json b/kompendium-core/src/test/resources/notarized_get_with_exception_response.json index cf131b17b..43fe4e28e 100644 --- a/kompendium-core/src/test/resources/notarized_get_with_exception_response.json +++ b/kompendium-core/src/test/resources/notarized_get_with_exception_response.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/notarized_get_with_multiple_exception_responses.json b/kompendium-core/src/test/resources/notarized_get_with_multiple_exception_responses.json index e17dd3f56..c5c65d6a4 100644 --- a/kompendium-core/src/test/resources/notarized_get_with_multiple_exception_responses.json +++ b/kompendium-core/src/test/resources/notarized_get_with_multiple_exception_responses.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/notarized_post.json b/kompendium-core/src/test/resources/notarized_post.json index aa59951b9..687d58aa7 100644 --- a/kompendium-core/src/test/resources/notarized_post.json +++ b/kompendium-core/src/test/resources/notarized_post.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/notarized_put.json b/kompendium-core/src/test/resources/notarized_put.json index 4322a95c9..782290819 100644 --- a/kompendium-core/src/test/resources/notarized_put.json +++ b/kompendium-core/src/test/resources/notarized_put.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/path_parser.json b/kompendium-core/src/test/resources/path_parser.json index 6eff1c46e..ef36c3b91 100644 --- a/kompendium-core/src/test/resources/path_parser.json +++ b/kompendium-core/src/test/resources/path_parser.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/query_with_default_parameter.json b/kompendium-core/src/test/resources/query_with_default_parameter.json new file mode 100644 index 000000000..ce8e3f297 --- /dev/null +++ b/kompendium-core/src/test/resources/query_with_default_parameter.json @@ -0,0 +1,87 @@ +{ + "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" : "Testing Default Params", + "description" : "Should have a default parameter value", + "parameters" : [ { + "name" : "a", + "in" : "query", + "schema" : { + "format" : "int32", + "type" : "integer", + "default" : 100 + }, + "required" : true, + "deprecated" : false + }, { + "name" : "b", + "in" : "path", + "schema" : { + "type" : "string" + }, + "required" : false, + "deprecated" : false + }, { + "name" : "c", + "in" : "path", + "schema" : { + "type" : "boolean" + }, + "required" : true, + "deprecated" : false + } ], + "deprecated" : false + } + } + }, + "components" : { + "schemas" : { + "String" : { + "type" : "string" + }, + "TestResponse" : { + "properties" : { + "c" : { + "$ref" : "#/components/schemas/String" + } + }, + "type" : "object" + }, + "Int" : { + "format" : "int32", + "type" : "integer" + }, + "Boolean" : { + "type" : "boolean" + } + }, + "securitySchemes" : { } + }, + "security" : [ ], + "tags" : [ ] +} diff --git a/kompendium-core/src/test/resources/response_list.json b/kompendium-core/src/test/resources/response_list.json index ccf4aa649..a467f3122 100644 --- a/kompendium-core/src/test/resources/response_list.json +++ b/kompendium-core/src/test/resources/response_list.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/root_route.json b/kompendium-core/src/test/resources/root_route.json index 66981b718..a1d352fdb 100644 --- a/kompendium-core/src/test/resources/root_route.json +++ b/kompendium-core/src/test/resources/root_route.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-core/src/test/resources/trailing_slash.json b/kompendium-core/src/test/resources/trailing_slash.json index 9ed8fad4a..6cb35dc64 100644 --- a/kompendium-core/src/test/resources/trailing_slash.json +++ b/kompendium-core/src/test/resources/trailing_slash.json @@ -32,7 +32,7 @@ "name" : "a", "in" : "path", "schema" : { - "$ref" : "#/components/schemas/String" + "type" : "string" }, "required" : true, "deprecated" : false @@ -40,7 +40,8 @@ "name" : "aa", "in" : "query", "schema" : { - "$ref" : "#/components/schemas/Int" + "format" : "int32", + "type" : "integer" }, "required" : true, "deprecated" : false diff --git a/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt index 4ba2e0585..2c520be11 100644 --- a/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt +++ b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt @@ -19,69 +19,27 @@ import io.ktor.routing.routing import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import io.ktor.webjars.Webjars -import java.net.URI -import org.leafygreens.kompendium.Kompendium import org.leafygreens.kompendium.Notarized.notarizedDelete import org.leafygreens.kompendium.Notarized.notarizedException import org.leafygreens.kompendium.Notarized.notarizedGet import org.leafygreens.kompendium.Notarized.notarizedPost import org.leafygreens.kompendium.Notarized.notarizedPut -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.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 -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.testPostWithExamples -import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleDeleteInfo -import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo -import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfoWithThrowable -import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePostInfo -import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePutInfo +import org.leafygreens.kompendium.playground.PlaygroundToC.testAuthenticatedSingleGetInfo +import org.leafygreens.kompendium.playground.PlaygroundToC.testGetWithExamples +import org.leafygreens.kompendium.playground.PlaygroundToC.testIdGetInfo +import org.leafygreens.kompendium.playground.PlaygroundToC.testPostWithExamples +import org.leafygreens.kompendium.playground.PlaygroundToC.testSingleDeleteInfo +import org.leafygreens.kompendium.playground.PlaygroundToC.testSingleGetInfo +import org.leafygreens.kompendium.playground.PlaygroundToC.testSingleGetInfoWithThrowable +import org.leafygreens.kompendium.playground.PlaygroundToC.testSinglePostInfo +import org.leafygreens.kompendium.playground.PlaygroundToC.testSinglePutInfo import org.leafygreens.kompendium.routes.openApi import org.leafygreens.kompendium.routes.redoc import org.leafygreens.kompendium.swagger.swaggerUI import org.leafygreens.kompendium.util.KompendiumHttpCodes -private val oas = Kompendium.openApiSpec.copy( - info = OpenApiSpecInfo( - title = "Test API", - version = "1.33.7", - description = "An amazing, fully-ish 😉 generated API spec", - termsOfService = URI("https://example.com"), - contact = OpenApiSpecInfoContact( - name = "Homer Simpson", - email = "chunkylover53@aol.com", - url = URI("https://gph.is/1NPUDiM") - ), - license = OpenApiSpecInfoLicense( - name = "MIT", - url = URI("https://github.com/lg-backbone/kompendium/blob/main/LICENSE") - ) - ), - servers = mutableListOf( - OpenApiSpecServer( - url = URI("https://myawesomeapi.com"), - description = "Production instance of my API" - ), - OpenApiSpecServer( - url = URI("https://staging.myawesomeapi.com"), - description = "Where the fun stuff happens" - ) - ) -) - fun main() { embeddedServer( Netty, @@ -171,131 +129,9 @@ fun Application.mainModule() { } } route("/error") { - notarizedGet(testSingleGetInfoWithThrowable) { + notarizedGet(testSingleGetInfoWithThrowable) { error("bad things just happened") } } } } - -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 ExampleRequest( - @KompendiumField(name = "field_name") - val fieldName: ExampleNested, - val b: Double, - val aaa: List -) - -data class ExampleResponse(val c: String) - -data class ExceptionResponse(val message: String) - -data class ExampleCreatedResponse(val id: Int, val c: String) - -object KompendiumTOC { - val testGetWithExamples = GetInfo( - 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) - ) - @Suppress("MagicNumber") - val testPostWithExamples = PostInfo( - summary = "Full Example", - description = "Throws just about all Kompendium has to offer into one endpoint", - requestInfo = RequestInfo( - description = "Necessary deetz", - examples = mapOf( - "Send This" to ExampleRequest(ExampleNested("potato"), 13.37, listOf(12341)) - ) - ), - responseInfo = ResponseInfo( - status = KompendiumHttpCodes.CREATED, - description = "Congratz you hit da endpoint", - examples = mapOf( - "Expect This" to ExampleResponse(c = "Hi"), - "Or This" to ExampleResponse(c = "Hey") - ) - ), - canThrow = setOf(Exception::class) - ) - - val testIdGetInfo = GetInfo( - summary = "Get Test", - description = "Test for the getting", - tags = setOf("test", "sample", "get"), - responseInfo = ResponseInfo( - status = KompendiumHttpCodes.OK, - description = "Returns sample info" - ) - ) - val testSingleGetInfo = GetInfo( - summary = "Another get test", - description = "testing more", - tags = setOf("anotherTest", "sample"), - responseInfo = ResponseInfo( - status = KompendiumHttpCodes.OK, - description = "Returns a different sample" - ) - ) - val testSingleGetInfoWithThrowable = testSingleGetInfo.copy( - summary = "Show me the error baby 🙏", - canThrow = setOf(Exception::class) - ) - val testSinglePostInfo = PostInfo( - 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 = PutInfo( - 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 = DeleteInfo( - summary = "Test delete endpoint", - description = "testing my deletes", - responseInfo = ResponseInfo( - status = KompendiumHttpCodes.NO_CONTENT, - description = "Signifies that your item was deleted successfully", - mediaTypes = emptyList() - ) - ) - val testAuthenticatedSingleGetInfo = GetInfo( - summary = "Another get test", - description = "testing more", - tags = setOf("anotherTest", "sample"), - responseInfo = ResponseInfo( - status = KompendiumHttpCodes.OK, - description = "Returns a different sample" - ), - securitySchemes = setOf("basic") - ) -} diff --git a/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Models.kt b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Models.kt new file mode 100644 index 000000000..23912da6c --- /dev/null +++ b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Models.kt @@ -0,0 +1,30 @@ +package org.leafygreens.kompendium.playground + +import org.leafygreens.kompendium.annotations.KompendiumField +import org.leafygreens.kompendium.annotations.KompendiumParam +import org.leafygreens.kompendium.annotations.ParamType + +data class ExampleParams( + @KompendiumParam(ParamType.PATH) val id: Int, + @KompendiumParam(ParamType.QUERY) val name: String +) + +data class JustQuery( + @KompendiumParam(ParamType.QUERY) val potato: Boolean, + @KompendiumParam(ParamType.QUERY) val tomato: String +) + +data class ExampleNested(val nesty: String) + +data class ExampleRequest( + @KompendiumField(name = "field_name") + val fieldName: ExampleNested, + val b: Double, + val aaa: List +) + +data class ExampleResponse(val c: String) + +data class ExceptionResponse(val message: String) + +data class ExampleCreatedResponse(val id: Int, val c: String) diff --git a/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/PlaygroundToC.kt b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/PlaygroundToC.kt new file mode 100644 index 000000000..dc6fa21c6 --- /dev/null +++ b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/PlaygroundToC.kt @@ -0,0 +1,103 @@ +package org.leafygreens.kompendium.playground + +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.util.KompendiumHttpCodes + +object PlaygroundToC { + val testGetWithExamples = MethodInfo.GetInfo( + 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) + ) + @Suppress("MagicNumber") + val testPostWithExamples = MethodInfo.PostInfo( + summary = "Full Example", + description = "Throws just about all Kompendium has to offer into one endpoint", + requestInfo = RequestInfo( + description = "Necessary deetz", + examples = mapOf( + "Send This" to ExampleRequest(ExampleNested("potato"), 13.37, listOf(12341)) + ) + ), + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.CREATED, + description = "Congratz you hit da endpoint", + examples = mapOf( + "Expect This" to ExampleResponse(c = "Hi"), + "Or This" to ExampleResponse(c = "Hey") + ) + ), + canThrow = setOf(Exception::class) + ) + + val testIdGetInfo = MethodInfo.GetInfo( + 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.GetInfo( + summary = "Another get test", + description = "testing more", + tags = setOf("anotherTest", "sample"), + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.OK, + description = "Returns a different sample" + ) + ) + val testSingleGetInfoWithThrowable = testSingleGetInfo.copy( + summary = "Show me the error baby 🙏", + canThrow = setOf(Exception::class) + ) + val testSinglePostInfo = MethodInfo.PostInfo( + 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.PutInfo( + 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.DeleteInfo( + summary = "Test delete endpoint", + description = "testing my deletes", + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.NO_CONTENT, + description = "Signifies that your item was deleted successfully", + mediaTypes = emptyList() + ) + ) + val testAuthenticatedSingleGetInfo = MethodInfo.GetInfo( + summary = "Another get test", + description = "testing more", + tags = setOf("anotherTest", "sample"), + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.OK, + description = "Returns a different sample" + ), + securitySchemes = setOf("basic") + ) +} diff --git a/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Spec.kt b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Spec.kt new file mode 100644 index 000000000..5af4c2a71 --- /dev/null +++ b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Spec.kt @@ -0,0 +1,36 @@ +package org.leafygreens.kompendium.playground + +import java.net.URI +import org.leafygreens.kompendium.Kompendium +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 + +val oas = Kompendium.openApiSpec.copy( + info = OpenApiSpecInfo( + title = "Test API", + version = "1.33.7", + description = "An amazing, fully-ish 😉 generated API spec", + termsOfService = URI("https://example.com"), + contact = OpenApiSpecInfoContact( + name = "Homer Simpson", + email = "chunkylover53@aol.com", + url = URI("https://gph.is/1NPUDiM") + ), + license = OpenApiSpecInfoLicense( + name = "MIT", + url = URI("https://github.com/lg-backbone/kompendium/blob/main/LICENSE") + ) + ), + servers = mutableListOf( + OpenApiSpecServer( + url = URI("https://myawesomeapi.com"), + description = "Production instance of my API" + ), + OpenApiSpecServer( + url = URI("https://staging.myawesomeapi.com"), + description = "Where the fun stuff happens" + ) + ) +)