Default param values (#47)
This commit is contained in:
12
CHANGELOG.md
12
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
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Kompendium
|
||||
project.version=0.8.0
|
||||
project.version=0.9.0
|
||||
# Kotlin
|
||||
kotlin.code.style=official
|
||||
# Gradle
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<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() {
|
||||
openApiSpec = OpenApiSpec(
|
||||
info = OpenApiSpecInfo(),
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -1,5 +0,0 @@
|
||||
package org.leafygreens.kompendium.annotations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
annotation class CookieParam(val description: String = "")
|
@ -1,5 +0,0 @@
|
||||
package org.leafygreens.kompendium.annotations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
annotation class HeaderParam(val description: String = "")
|
@ -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
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.leafygreens.kompendium.annotations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
annotation class PathParam(val description: String = "")
|
@ -1,5 +0,0 @@
|
||||
package org.leafygreens.kompendium.annotations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
annotation class QueryParam(val description: String = "")
|
@ -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<String, OpenApiSpecComponentSchema>
|
||||
) : TypedSchema("object")
|
||||
val properties: Map<String, OpenApiSpecComponentSchema>,
|
||||
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<String>
|
||||
) : TypedSchema("string")
|
||||
val `enum`: Set<String>, 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")
|
||||
|
@ -14,7 +14,7 @@ data class OpenApiSpecResponse<T>(
|
||||
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,
|
||||
|
@ -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()
|
@ -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,
|
||||
)
|
@ -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}"
|
||||
|
@ -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<TestResponse>(KompendiumHttpCodes.OK, "A Successful 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 testRequestAgain = RequestInfo<Int>("A Test request")
|
||||
val complexRequest = RequestInfo<ComplexRequest>("A Complex request")
|
||||
val testGetInfo = GetInfo<TestParams, TestResponse>(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 testGetInfo = GetInfo<TestParams, TestResponse>(
|
||||
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(
|
||||
canThrow = setOf(Exception::class)
|
||||
)
|
||||
val testGetWithMultipleExceptions = testGetInfo.copy(
|
||||
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 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")
|
||||
val testPostInfo = PostInfo<TestParams, TestRequest, TestCreatedResponse>(
|
||||
summary = "Test post endpoint",
|
||||
description = "Post your tests here!",
|
||||
responseInfo = testPostResponse,
|
||||
requestInfo = testRequest
|
||||
)
|
||||
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() {
|
||||
@ -642,7 +694,8 @@ internal class KompendiumTest {
|
||||
private fun Application.withExamples() {
|
||||
routing {
|
||||
route("/test/examples") {
|
||||
notarizedPost(info = PostInfo<Unit, TestRequest, TestResponse>(
|
||||
notarizedPost(
|
||||
info = PostInfo<Unit, TestRequest, TestResponse>(
|
||||
summary = "Example Parameters",
|
||||
description = "A test for setting parameter examples",
|
||||
requestInfo = RequestInfo(
|
||||
@ -657,14 +710,34 @@ internal class KompendiumTest {
|
||||
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<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() {
|
||||
routing {
|
||||
route("/test/optional") {
|
||||
|
@ -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 {
|
||||
|
@ -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<SimpleEnum>)
|
||||
data class TestInvalidMap(val a: Map<Int, TestSimpleModel>)
|
||||
|
||||
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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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" : [ ]
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<Unit, ExampleResponse>(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<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")
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
@ -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")
|
||||
)
|
||||
}
|
@ -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"
|
||||
)
|
||||
)
|
||||
)
|
Reference in New Issue
Block a user