Default param values (#47)

This commit is contained in:
Ryan Brink
2021-05-05 21:33:17 -04:00
committed by GitHub
parent 938a604285
commit f23016e878
42 changed files with 679 additions and 490 deletions

View File

@ -1,5 +1,17 @@
# Changelog # 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 ## [0.8.0] - May 4th, 2021
### Added ### Added

View File

@ -79,15 +79,12 @@ integrated.
Currently, the annotations used by Kompendium are as follows Currently, the annotations used by Kompendium are as follows
- `KompendiumField` - `KompendiumField`
- `PathParam` - `KompendiumParam`
- `QueryParam`
- `HeaderParam`
- `CookieParam`
The intended purpose of `KompendiumField` is to offer field level overrides such as naming conventions (ie snake instead of camel). The intended purpose of `KompendiumField` is to offer field level overrides such as naming conventions (ie snake instead of camel).
The 4 "param" annotations are to offer supplemental information in data classes that describe the set of parameters types The purpose of `KompendiumParam` is to provide supplemental information needed to properly assign the type of parameter
that a notarized route needs to analyze. (cookie, header, query, path) as well as other parameter-level metadata.
## Examples ## Examples

View File

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

View File

@ -1,12 +1,12 @@
package org.leafygreens.kompendium.auth.util package org.leafygreens.kompendium.auth.util
import org.leafygreens.kompendium.annotations.KompendiumField import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.PathParam import org.leafygreens.kompendium.annotations.KompendiumParam
import org.leafygreens.kompendium.annotations.QueryParam import org.leafygreens.kompendium.annotations.ParamType
data class TestParams( data class TestParams(
@PathParam val a: String, @KompendiumParam(ParamType.PATH) val a: String,
@QueryParam val aa: Int @KompendiumParam(ParamType.QUERY) val aa: Int
) )
data class TestRequest( data class TestRequest(

View File

@ -12,7 +12,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -20,7 +20,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -12,7 +12,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -20,7 +20,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -12,7 +12,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -20,7 +20,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -12,7 +12,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -20,7 +20,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -12,7 +12,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -20,7 +20,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -1,36 +1,11 @@
package org.leafygreens.kompendium 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.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.meta.SchemaMap
import org.leafygreens.kompendium.models.oas.ExampleWrapper
import org.leafygreens.kompendium.models.oas.OpenApiSpec import org.leafygreens.kompendium.models.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
import org.leafygreens.kompendium.models.oas.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.CorePathCalculator
import org.leafygreens.kompendium.path.PathCalculator import org.leafygreens.kompendium.path.PathCalculator
import org.leafygreens.kompendium.util.Helpers
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
object Kompendium { object Kompendium {
@ -45,124 +20,6 @@ object Kompendium {
var pathCalculator: PathCalculator = CorePathCalculator() 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<KClass<*>>): Map<Int, OpenApiSpecReferencable> = throwables.mapNotNull {
errorMap[it.createType()]
}.toMap()
fun <TResp> ResponseInfo<TResp>.parseErrorInfo(
errorType: KType,
responseType: KType
) {
errorMap = errorMap.plus(errorType to responseType.toResponseSpec(this))
}
// TODO These two lookin' real similar 👀 Combine?
private fun <TReq> KType.toRequestSpec(requestInfo: RequestInfo<TReq>?): OpenApiSpecRequest<TReq>? =
when (requestInfo) {
null -> null
else -> {
OpenApiSpecRequest(
description = requestInfo.description,
content = resolveContent(requestInfo.mediaTypes, requestInfo.examples) ?: mapOf()
)
}
}
private fun <TResp> KType.toResponseSpec(responseInfo: ResponseInfo<TResp>?): Pair<Int, OpenApiSpecResponse<TResp>>? =
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 <F> KType.resolveContent(
mediaTypes: List<String>,
examples: Map<String, F>
): Map<String, OpenApiSpecMediaType<F>>? {
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<OpenApiSpecParameter> {
val clazz = classifier as KClass<*>
return clazz.memberProperties.map { prop ->
val field = prop.javaField?.type?.kotlin
?: error("Unable to parse field type from $prop")
val anny = prop.findAnnotation<PathParam>()
?: prop.findAnnotation<QueryParam>()
?: prop.findAnnotation<HeaderParam>()
?: prop.findAnnotation<CookieParam>()
?: error("Unable to find any relevant parameter specifier annotations on field ${prop.name}")
OpenApiSpecParameter(
name = prop.name,
`in` = when (anny) {
is PathParam -> "path"
is QueryParam -> "query"
is HeaderParam -> "header"
is CookieParam -> "cookie"
else -> error("should not be reachable")
},
schema = OpenApiSpecSchemaRef(field.getReferenceSlug(prop)),
description = when (anny) {
is PathParam -> anny.description.ifBlank { null }
is QueryParam -> anny.description.ifBlank { null }
is HeaderParam -> anny.description.ifBlank { null }
is CookieParam -> anny.description.ifBlank { null }
else -> error("should not be reachable")
},
required = !prop.returnType.isMarkedNullable
)
}
}
fun resetSchema() { fun resetSchema() {
openApiSpec = OpenApiSpec( openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(), info = OpenApiSpecInfo(),

View File

@ -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<KClass<*>>): Map<Int, OpenApiSpecReferencable> = throwables.mapNotNull {
Kompendium.errorMap[it.createType()]
}.toMap()
fun <TResp> ResponseInfo<TResp>.parseErrorInfo(
errorType: KType,
responseType: KType
) {
Kompendium.errorMap = Kompendium.errorMap.plus(errorType to responseType.toResponseSpec(this))
}
// TODO These two lookin' real similar 👀 Combine?
private fun <TReq> KType.toRequestSpec(requestInfo: RequestInfo<TReq>?): OpenApiSpecRequest<TReq>? =
when (requestInfo) {
null -> null
else -> {
OpenApiSpecRequest(
description = requestInfo.description,
content = resolveContent(requestInfo.mediaTypes, requestInfo.examples) ?: mapOf()
)
}
}
private fun <TResp> KType.toResponseSpec(responseInfo: ResponseInfo<TResp>?): Pair<Int, OpenApiSpecResponse<TResp>>? =
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 <F> KType.resolveContent(
mediaTypes: List<String>,
examples: Map<String, F>
): Map<String, OpenApiSpecMediaType<F>>? {
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<OpenApiSpecParameter> {
val clazz = classifier as KClass<*>
return clazz.memberProperties.map { prop ->
val field = prop.javaField?.type?.kotlin
?: error("Unable to parse field type from $prop")
val anny = prop.findAnnotation<KompendiumParam>()
?: 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")
}
}

View File

@ -7,9 +7,9 @@ import io.ktor.routing.Route
import io.ktor.routing.method import io.ktor.routing.method
import io.ktor.util.pipeline.PipelineContext import io.ktor.util.pipeline.PipelineContext
import io.ktor.util.pipeline.PipelineInterceptor 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.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.GetInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PutInfo import org.leafygreens.kompendium.models.meta.MethodInfo.PutInfo

View File

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

View File

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

View File

@ -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
}

View File

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

View File

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

View File

@ -1,26 +1,42 @@
package org.leafygreens.kompendium.models.oas package org.leafygreens.kompendium.models.oas
// TODO Enum for type? sealed class OpenApiSpecComponentSchema(open val default: Any? = null) {
sealed class OpenApiSpecComponentSchema
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( data class ObjectSchema(
val properties: Map<String, OpenApiSpecComponentSchema> val properties: Map<String, OpenApiSpecComponentSchema>,
) : TypedSchema("object") override val default: Any? = null
) : TypedSchema("object", default)
data class DictionarySchema( data class DictionarySchema(
val additionalProperties: OpenApiSpecComponentSchema val additionalProperties: OpenApiSpecComponentSchema,
) : TypedSchema("object") override val default: Any? = null
) : TypedSchema("object", default)
data class EnumSchema( data class EnumSchema(
val `enum`: Set<String> val `enum`: Set<String>, override val default: Any? = null
) : TypedSchema("string") ) : 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")

View File

@ -14,7 +14,7 @@ data class OpenApiSpecResponse<T>(
data class OpenApiSpecParameter( data class OpenApiSpecParameter(
val name: String, val name: String,
val `in`: String, // TODO Enum? "query", "header", "path" or "cookie" val `in`: String, // TODO Enum? "query", "header", "path" or "cookie"
val schema: OpenApiSpecSchema, val schema: OpenApiSpecComponentSchema,
val description: String? = null, val description: String? = null,
val required: Boolean = true, val required: Boolean = true,
val deprecated: Boolean = false, val deprecated: Boolean = false,

View File

@ -1,31 +0,0 @@
package org.leafygreens.kompendium.models.oas
sealed class OpenApiSpecSchema
sealed class OpenApiSpecSchemaTyped(
val type: String,
) : OpenApiSpecSchema()
data class OpenApiSpecSchemaArray<T : OpenApiSpecSchema>(
val items: T
) : OpenApiSpecSchemaTyped("array")
data class OpenApiSpecSchemaString(
val default: String,
val `enum`: Set<String>? = 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()

View File

@ -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,
)

View File

@ -1,17 +1,13 @@
package org.leafygreens.kompendium.util 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 java.lang.reflect.ParameterizedType
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.jvm.javaField
import org.slf4j.LoggerFactory
import kotlin.reflect.full.createType import kotlin.reflect.full.createType
import kotlin.reflect.jvm.javaField
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
import org.slf4j.LoggerFactory
object Helpers { object Helpers {
@ -53,6 +49,11 @@ object Helpers {
return result 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 { fun KType.getReferenceSlug(): String = when {
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}" arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}" else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}"

View File

@ -25,11 +25,12 @@ import org.leafygreens.kompendium.Notarized.notarizedException
import org.leafygreens.kompendium.Notarized.notarizedGet import org.leafygreens.kompendium.Notarized.notarizedGet
import org.leafygreens.kompendium.Notarized.notarizedPost import org.leafygreens.kompendium.Notarized.notarizedPost
import org.leafygreens.kompendium.Notarized.notarizedPut 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.GetInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PutInfo 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.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo 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.openApi
import org.leafygreens.kompendium.routes.redoc import org.leafygreens.kompendium.routes.redoc
import org.leafygreens.kompendium.util.ComplexRequest import org.leafygreens.kompendium.util.ComplexRequest
import org.leafygreens.kompendium.util.DefaultParameter
import org.leafygreens.kompendium.util.ExceptionResponse import org.leafygreens.kompendium.util.ExceptionResponse
import org.leafygreens.kompendium.util.KompendiumHttpCodes import org.leafygreens.kompendium.util.KompendiumHttpCodes
import org.leafygreens.kompendium.util.TestCreatedResponse 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 { private companion object {
val testGetResponse = ResponseInfo<TestResponse>(KompendiumHttpCodes.OK, "A Successful Endeavor") val testGetResponse = ResponseInfo<TestResponse>(KompendiumHttpCodes.OK, "A Successful Endeavor")
val testGetListResponse = ResponseInfo<List<TestResponse>>(KompendiumHttpCodes.OK, "A Successful List-y Endeavor") val testGetListResponse = ResponseInfo<List<TestResponse>>(KompendiumHttpCodes.OK, "A Successful List-y Endeavor")
@ -440,21 +458,55 @@ internal class KompendiumTest {
val testRequest = RequestInfo<TestRequest>("A Test request") val testRequest = RequestInfo<TestRequest>("A Test request")
val testRequestAgain = RequestInfo<Int>("A Test request") val testRequestAgain = RequestInfo<Int>("A Test request")
val complexRequest = RequestInfo<ComplexRequest>("A Complex request") val complexRequest = RequestInfo<ComplexRequest>("A Complex request")
val testGetInfo = GetInfo<TestParams, TestResponse>(summary = "Another get test", description = "testing more", responseInfo = testGetResponse) val testGetInfo = GetInfo<TestParams, TestResponse>(
val testGetInfoAgain = GetInfo<TestParams, List<TestResponse>>(summary = "Another get test", description = "testing more", responseInfo = testGetListResponse) summary = "Another get test",
description = "testing more",
responseInfo = testGetResponse
)
val testGetInfoAgain = GetInfo<TestParams, List<TestResponse>>(
summary = "Another get test",
description = "testing more",
responseInfo = testGetListResponse
)
val testGetWithException = testGetInfo.copy( val testGetWithException = testGetInfo.copy(
canThrow = setOf(Exception::class) canThrow = setOf(Exception::class)
) )
val testGetWithMultipleExceptions = testGetInfo.copy( val testGetWithMultipleExceptions = testGetInfo.copy(
canThrow = setOf(AccessDeniedException::class, Exception::class) canThrow = setOf(AccessDeniedException::class, Exception::class)
) )
val testPostInfo = PostInfo<TestParams, TestRequest, TestCreatedResponse>(summary = "Test post endpoint", description = "Post your tests here!", responseInfo = testPostResponse, requestInfo = testRequest) val testPostInfo = PostInfo<TestParams, TestRequest, TestCreatedResponse>(
val testPutInfo = PutInfo<Unit, ComplexRequest, TestCreatedResponse>(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponse, requestInfo = complexRequest) summary = "Test post endpoint",
val testPutInfoAlso = PutInfo<TestParams, TestRequest, TestCreatedResponse>(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponse, requestInfo = testRequest) description = "Post your tests here!",
val testPutInfoAgain = PutInfo<Unit, Int, Boolean>(summary = "Test put endpoint", description = "Put your tests here!", responseInfo = testPostResponseAgain, requestInfo = testRequestAgain) responseInfo = testPostResponse,
val testDeleteInfo = DeleteInfo<TestParams, Unit>(summary = "Test delete endpoint", description = "testing my deletes", responseInfo = testDeleteResponse) requestInfo = testRequest
val emptyTestGetInfo = GetInfo<OptionalParams, Unit>(summary = "No request params and response body", description = "testing more") )
val trulyEmptyTestGetInfo = GetInfo<Unit, Unit>(summary = "No request params and response body", description = "testing more") val testPutInfo = PutInfo<Unit, ComplexRequest, TestCreatedResponse>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponse,
requestInfo = complexRequest
)
val testPutInfoAlso = PutInfo<TestParams, TestRequest, TestCreatedResponse>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponse,
requestInfo = testRequest
)
val testPutInfoAgain = PutInfo<Unit, Int, Boolean>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponseAgain,
requestInfo = testRequestAgain
)
val testDeleteInfo = DeleteInfo<TestParams, Unit>(
summary = "Test delete endpoint",
description = "testing my deletes",
responseInfo = testDeleteResponse
)
val emptyTestGetInfo =
GetInfo<OptionalParams, Unit>(summary = "No request params and response body", description = "testing more")
val trulyEmptyTestGetInfo =
GetInfo<Unit, Unit>(summary = "No request params and response body", description = "testing more")
} }
private fun Application.configModule() { private fun Application.configModule() {
@ -642,7 +694,8 @@ internal class KompendiumTest {
private fun Application.withExamples() { private fun Application.withExamples() {
routing { routing {
route("/test/examples") { route("/test/examples") {
notarizedPost(info = PostInfo<Unit, TestRequest, TestResponse>( notarizedPost(
info = PostInfo<Unit, TestRequest, TestResponse>(
summary = "Example Parameters", summary = "Example Parameters",
description = "A test for setting parameter examples", description = "A test for setting parameter examples",
requestInfo = RequestInfo( requestInfo = RequestInfo(
@ -657,14 +710,34 @@ internal class KompendiumTest {
description = "nice", description = "nice",
examples = mapOf("test" to TestResponse(c = "spud")) examples = mapOf("test" to TestResponse(c = "spud"))
), ),
)) { )
) {
call.respond(HttpStatusCode.OK) 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<DefaultParameter, TestResponse>(
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() { private fun Application.nonRequiredParamsGet() {
routing { routing {
route("/test/optional") { route("/test/optional") {

View File

@ -1,28 +1,6 @@
package org.leafygreens.kompendium.util package org.leafygreens.kompendium.util
import java.io.File 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 { object TestHelpers {
fun getFileSnapshot(fileName: String): String { fun getFileSnapshot(fileName: String): String {

View File

@ -1,9 +1,12 @@
package org.leafygreens.kompendium.util package org.leafygreens.kompendium.util
import java.util.UUID 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.KompendiumField
import org.leafygreens.kompendium.annotations.PathParam import org.leafygreens.kompendium.annotations.KompendiumParam
import org.leafygreens.kompendium.annotations.QueryParam import org.leafygreens.kompendium.annotations.ParamType
data class TestSimpleModel(val a: String, val b: Int) data class TestSimpleModel(val a: String, val b: Int)
@ -20,8 +23,8 @@ data class TestSimpleWithEnumList(val a: Double, val b: List<SimpleEnum>)
data class TestInvalidMap(val a: Map<Int, TestSimpleModel>) data class TestInvalidMap(val a: Map<Int, TestSimpleModel>)
data class TestParams( data class TestParams(
@PathParam val a: String, @KompendiumParam(ParamType.PATH) val a: String,
@QueryParam val aa: Int @KompendiumParam(ParamType.QUERY) val aa: Int
) )
data class TestNested(val nesty: String) data class TestNested(val nesty: String)
@ -66,6 +69,12 @@ enum class SimpleEnum {
TWO 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) sealed class TestSealedClass(open val a: String)
data class SimpleTSC(val b: Int) : TestSealedClass("hey") data class SimpleTSC(val b: Int) : TestSealedClass("hey")

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "notRequired", "name" : "notRequired",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : false, "required" : false,
"deprecated" : false "deprecated" : false
@ -40,7 +40,7 @@
"name" : "required", "name" : "required",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -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" : [ ]
}

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -32,7 +32,7 @@
"name" : "a", "name" : "a",
"in" : "path", "in" : "path",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/String" "type" : "string"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false
@ -40,7 +40,8 @@
"name" : "aa", "name" : "aa",
"in" : "query", "in" : "query",
"schema" : { "schema" : {
"$ref" : "#/components/schemas/Int" "format" : "int32",
"type" : "integer"
}, },
"required" : true, "required" : true,
"deprecated" : false "deprecated" : false

View File

@ -19,69 +19,27 @@ import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty import io.ktor.server.netty.Netty
import io.ktor.webjars.Webjars 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.notarizedDelete
import org.leafygreens.kompendium.Notarized.notarizedException import org.leafygreens.kompendium.Notarized.notarizedException
import org.leafygreens.kompendium.Notarized.notarizedGet import org.leafygreens.kompendium.Notarized.notarizedGet
import org.leafygreens.kompendium.Notarized.notarizedPost import org.leafygreens.kompendium.Notarized.notarizedPost
import org.leafygreens.kompendium.Notarized.notarizedPut 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.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.meta.ResponseInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo import org.leafygreens.kompendium.playground.PlaygroundToC.testAuthenticatedSingleGetInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact import org.leafygreens.kompendium.playground.PlaygroundToC.testGetWithExamples
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense import org.leafygreens.kompendium.playground.PlaygroundToC.testIdGetInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer import org.leafygreens.kompendium.playground.PlaygroundToC.testPostWithExamples
import org.leafygreens.kompendium.playground.KompendiumTOC.testAuthenticatedSingleGetInfo import org.leafygreens.kompendium.playground.PlaygroundToC.testSingleDeleteInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testGetWithExamples import org.leafygreens.kompendium.playground.PlaygroundToC.testSingleGetInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testIdGetInfo import org.leafygreens.kompendium.playground.PlaygroundToC.testSingleGetInfoWithThrowable
import org.leafygreens.kompendium.playground.KompendiumTOC.testPostWithExamples import org.leafygreens.kompendium.playground.PlaygroundToC.testSinglePostInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleDeleteInfo import org.leafygreens.kompendium.playground.PlaygroundToC.testSinglePutInfo
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.routes.openApi import org.leafygreens.kompendium.routes.openApi
import org.leafygreens.kompendium.routes.redoc import org.leafygreens.kompendium.routes.redoc
import org.leafygreens.kompendium.swagger.swaggerUI import org.leafygreens.kompendium.swagger.swaggerUI
import org.leafygreens.kompendium.util.KompendiumHttpCodes 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() { fun main() {
embeddedServer( embeddedServer(
Netty, Netty,
@ -171,131 +129,9 @@ fun Application.mainModule() {
} }
} }
route("/error") { route("/error") {
notarizedGet<Unit, ExampleResponse>(testSingleGetInfoWithThrowable) { notarizedGet(testSingleGetInfoWithThrowable) {
error("bad things just happened") 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<Long>
)
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<Unit, ExampleResponse>(
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<ExampleParams, ExampleRequest, ExampleResponse>(
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<ExampleParams, ExampleResponse>(
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<Unit, ExampleResponse>(
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<Unit, ExampleRequest, ExampleCreatedResponse>(
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<JustQuery, ExampleRequest, ExampleCreatedResponse>(
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<Unit, Unit>(
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<Unit, Unit>(
summary = "Another get test",
description = "testing more",
tags = setOf("anotherTest", "sample"),
responseInfo = ResponseInfo(
status = KompendiumHttpCodes.OK,
description = "Returns a different sample"
),
securitySchemes = setOf("basic")
)
}

View File

@ -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<Long>
)
data class ExampleResponse(val c: String)
data class ExceptionResponse(val message: String)
data class ExampleCreatedResponse(val id: Int, val c: String)

View File

@ -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<Unit, ExampleResponse>(
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<ExampleParams, ExampleRequest, ExampleResponse>(
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<ExampleParams, ExampleResponse>(
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<Unit, ExampleResponse>(
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<Unit, ExampleRequest, ExampleCreatedResponse>(
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<JustQuery, ExampleRequest, ExampleCreatedResponse>(
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<Unit, Unit>(
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<Unit, Unit>(
summary = "Another get test",
description = "testing more",
tags = setOf("anotherTest", "sample"),
responseInfo = ResponseInfo(
status = KompendiumHttpCodes.OK,
description = "Returns a different sample"
),
securitySchemes = setOf("basic")
)
}

View File

@ -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"
)
)
)