Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
7b39e448bf | |||
3c57c8f5e4 | |||
01b6b59cf5 | |||
2c1c8fbcdc | |||
0dc2b9538f | |||
1ca7abaf0d | |||
a5376cfa82 | |||
2492661f1f | |||
a7b52ec114 | |||
8ebab04a83 | |||
921f6f9691 | |||
558b9fea62 | |||
752cb238d3 |
12
.github/workflows/autoupdate.yml
vendored
12
.github/workflows/autoupdate.yml
vendored
@ -1,12 +0,0 @@
|
|||||||
name: autoupdate
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
jobs:
|
|
||||||
autoupdate:
|
|
||||||
name: autoupdate
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: docker://chinthakagodawita/autoupdate-action:v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
|
23
CHANGELOG.md
23
CHANGELOG.md
@ -12,6 +12,29 @@
|
|||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
|
## [3.8.0] - November 9th, 2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for NotarizedResource plugin scoped to route
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Support registering same path with different authentication and methods
|
||||||
|
|
||||||
|
## [3.7.0] - November 5th, 2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Allow users to override media type in request and response
|
||||||
|
|
||||||
|
## [3.6.0] - November 5th, 2022
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Schemas for types in nullable properties are no longer nullable themselves
|
||||||
|
- Enums are now generated as references, which makes it possible to generate types for them
|
||||||
|
|
||||||
## [3.5.0] - October 29th, 2022
|
## [3.5.0] - October 29th, 2022
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.7.20" apply false
|
kotlin("jvm") version "1.7.21" apply false
|
||||||
kotlin("plugin.serialization") version "1.7.20" apply false
|
kotlin("plugin.serialization") version "1.7.21" apply false
|
||||||
id("io.bkbn.sourdough.library.jvm") version "0.12.0" apply false
|
id("io.bkbn.sourdough.library.jvm") version "0.12.0" apply false
|
||||||
id("io.bkbn.sourdough.application.jvm") version "0.12.0" apply false
|
id("io.bkbn.sourdough.application.jvm") version "0.12.0" apply false
|
||||||
id("io.bkbn.sourdough.root") version "0.12.0"
|
id("io.bkbn.sourdough.root") version "0.12.0"
|
||||||
|
@ -58,7 +58,7 @@ dependencies {
|
|||||||
testFixturesApi("io.ktor:ktor-client:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-client:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-client-cio:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-client-cio:$ktorVersion")
|
||||||
|
|
||||||
testFixturesApi("dev.forst:ktor-api-key:2.1.2")
|
testFixturesApi("dev.forst:ktor-api-key:2.1.3")
|
||||||
|
|
||||||
testFixturesApi("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
testFixturesApi("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ import kotlin.reflect.typeOf
|
|||||||
class RequestInfo private constructor(
|
class RequestInfo private constructor(
|
||||||
val requestType: KType,
|
val requestType: KType,
|
||||||
val description: String,
|
val description: String,
|
||||||
val examples: Map<String, MediaType.Example>?
|
val examples: Map<String, MediaType.Example>?,
|
||||||
|
val mediaTypes: Set<String>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -22,6 +23,7 @@ class RequestInfo private constructor(
|
|||||||
private var requestType: KType? = null
|
private var requestType: KType? = null
|
||||||
private var description: String? = null
|
private var description: String? = null
|
||||||
private var examples: Map<String, MediaType.Example>? = null
|
private var examples: Map<String, MediaType.Example>? = null
|
||||||
|
private var mediaTypes: Set<String>? = null
|
||||||
|
|
||||||
fun requestType(t: KType) = apply {
|
fun requestType(t: KType) = apply {
|
||||||
this.requestType = t
|
this.requestType = t
|
||||||
@ -35,10 +37,15 @@ class RequestInfo private constructor(
|
|||||||
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mediaTypes(vararg m: String) = apply {
|
||||||
|
this.mediaTypes = m.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
fun build() = RequestInfo(
|
fun build() = RequestInfo(
|
||||||
requestType = requestType ?: error("Request type must be present"),
|
requestType = requestType ?: error("Request type must be present"),
|
||||||
description = description ?: error("Description must be present"),
|
description = description ?: error("Description must be present"),
|
||||||
examples = examples
|
examples = examples,
|
||||||
|
mediaTypes = mediaTypes ?: setOf("application/json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ class ResponseInfo private constructor(
|
|||||||
val responseCode: HttpStatusCode,
|
val responseCode: HttpStatusCode,
|
||||||
val responseType: KType,
|
val responseType: KType,
|
||||||
val description: String,
|
val description: String,
|
||||||
val examples: Map<String, MediaType.Example>?
|
val examples: Map<String, MediaType.Example>?,
|
||||||
|
val mediaTypes: Set<String>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -25,6 +26,7 @@ class ResponseInfo private constructor(
|
|||||||
private var responseType: KType? = null
|
private var responseType: KType? = null
|
||||||
private var description: String? = null
|
private var description: String? = null
|
||||||
private var examples: Map<String, MediaType.Example>? = null
|
private var examples: Map<String, MediaType.Example>? = null
|
||||||
|
private var mediaTypes: Set<String>? = null
|
||||||
|
|
||||||
fun responseCode(code: HttpStatusCode) = apply {
|
fun responseCode(code: HttpStatusCode) = apply {
|
||||||
this.responseCode = code
|
this.responseCode = code
|
||||||
@ -42,11 +44,16 @@ class ResponseInfo private constructor(
|
|||||||
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mediaTypes(vararg m: String) = apply {
|
||||||
|
this.mediaTypes = m.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
fun build() = ResponseInfo(
|
fun build() = ResponseInfo(
|
||||||
responseCode = responseCode ?: error("You must provide a response code in order to build a Response!"),
|
responseCode = responseCode ?: error("You must provide a response code in order to build a Response!"),
|
||||||
responseType = responseType ?: error("You must provide a response type in order to build a Response!"),
|
responseType = responseType ?: error("You must provide a response type in order to build a Response!"),
|
||||||
description = description ?: error("You must provide a description in order to build a Response!"),
|
description = description ?: error("You must provide a description in order to build a Response!"),
|
||||||
examples = examples
|
examples = examples,
|
||||||
|
mediaTypes = mediaTypes ?: setOf("application/json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,16 @@ import io.bkbn.kompendium.core.metadata.PostInfo
|
|||||||
import io.bkbn.kompendium.core.metadata.PutInfo
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
||||||
import io.bkbn.kompendium.core.util.SpecConfig
|
import io.bkbn.kompendium.core.util.SpecConfig
|
||||||
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
import io.bkbn.kompendium.oas.path.Path
|
import io.bkbn.kompendium.oas.path.Path
|
||||||
import io.bkbn.kompendium.oas.path.PathOperation
|
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.ktor.server.application.ApplicationCallPipeline
|
import io.ktor.server.application.ApplicationCallPipeline
|
||||||
import io.ktor.server.application.Hook
|
import io.ktor.server.application.Hook
|
||||||
|
import io.ktor.server.application.PluginBuilder
|
||||||
import io.ktor.server.application.createRouteScopedPlugin
|
import io.ktor.server.application.createRouteScopedPlugin
|
||||||
import io.ktor.server.routing.Route
|
import io.ktor.server.routing.Route
|
||||||
|
|
||||||
object NotarizedRoute {
|
object NotarizedRoute {
|
||||||
|
|
||||||
class Config : SpecConfig {
|
class Config : SpecConfig {
|
||||||
override var tags: Set<String> = emptySet()
|
override var tags: Set<String> = emptySet()
|
||||||
override var parameters: List<Parameter> = emptyList()
|
override var parameters: List<Parameter> = emptyList()
|
||||||
@ -31,7 +31,6 @@ object NotarizedRoute {
|
|||||||
override var head: HeadInfo? = null
|
override var head: HeadInfo? = null
|
||||||
override var options: OptionsInfo? = null
|
override var options: OptionsInfo? = null
|
||||||
override var security: Map<String, List<String>>? = null
|
override var security: Map<String, List<String>>? = null
|
||||||
internal var path: Path? = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private object InstallHook : Hook<(ApplicationCallPipeline) -> Unit> {
|
private object InstallHook : Hook<(ApplicationCallPipeline) -> Unit> {
|
||||||
@ -51,62 +50,37 @@ object NotarizedRoute {
|
|||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
val routePath = route.calculateRoutePath()
|
val routePath = route.calculateRoutePath()
|
||||||
val authMethods = route.collectAuthMethods()
|
val authMethods = route.collectAuthMethods()
|
||||||
pluginConfig.path?.addDefaultAuthMethods(authMethods)
|
|
||||||
require(spec.paths[routePath] == null) {
|
addToSpec(spec, routePath, authMethods)
|
||||||
"""
|
|
||||||
The specified path ${Parameter.Location.path} has already been documented!
|
|
||||||
Please make sure that all notarized paths are unique
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
spec.paths[routePath] = pluginConfig.path
|
|
||||||
?: error("This indicates a bug in Kompendium. Please file a GitHub issue!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
|
||||||
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
|
||||||
|
|
||||||
val path = Path()
|
|
||||||
path.parameters = pluginConfig.parameters
|
|
||||||
|
|
||||||
pluginConfig.get?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.delete?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.head?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.options?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.post?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.put?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.patch?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
|
|
||||||
pluginConfig.path = path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
fun <T : SpecConfig> PluginBuilder<T>.addToSpec(
|
||||||
private fun Route.collectAuthMethods() = toString()
|
spec: OpenApiSpec,
|
||||||
|
fullPath: String,
|
||||||
|
authMethods: List<String>
|
||||||
|
) {
|
||||||
|
val path = spec.paths[fullPath] ?: Path()
|
||||||
|
|
||||||
|
path.parameters = path.parameters?.plus(pluginConfig.parameters) ?: pluginConfig.parameters
|
||||||
|
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
||||||
|
|
||||||
|
pluginConfig.get?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.delete?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.head?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.options?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.post?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.put?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.patch?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
|
||||||
|
spec.paths[fullPath] = path
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
||||||
|
fun Route.collectAuthMethods() = toString()
|
||||||
.split("/")
|
.split("/")
|
||||||
.filter { it.contains(Regex("\\(authenticate .*\\)")) }
|
.filter { it.contains(Regex("\\(authenticate .*\\)")) }
|
||||||
.map { it.replace("(authenticate ", "").replace(")", "") }
|
.map { it.replace("(authenticate ", "").replace(")", "") }
|
||||||
.map { it.split(", ") }
|
.map { it.split(", ") }
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
||||||
private fun Path.addDefaultAuthMethods(methods: List<String>) {
|
|
||||||
get?.addDefaultAuthMethods(methods)
|
|
||||||
put?.addDefaultAuthMethods(methods)
|
|
||||||
post?.addDefaultAuthMethods(methods)
|
|
||||||
delete?.addDefaultAuthMethods(methods)
|
|
||||||
options?.addDefaultAuthMethods(methods)
|
|
||||||
head?.addDefaultAuthMethods(methods)
|
|
||||||
patch?.addDefaultAuthMethods(methods)
|
|
||||||
trace?.addDefaultAuthMethods(methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PathOperation.addDefaultAuthMethods(methods: List<String>) {
|
|
||||||
methods.forEach { m ->
|
|
||||||
if (security == null || security?.all { s -> !s.containsKey(m) } == true) {
|
|
||||||
if (security == null) {
|
|
||||||
security = mutableListOf(mapOf(m to emptyList()))
|
|
||||||
} else {
|
|
||||||
security?.add(mapOf(m to emptyList()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ import io.bkbn.kompendium.core.metadata.PutInfo
|
|||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||||
@ -22,11 +24,31 @@ import io.bkbn.kompendium.oas.payload.MediaType
|
|||||||
import io.bkbn.kompendium.oas.payload.Request
|
import io.bkbn.kompendium.oas.payload.Request
|
||||||
import io.bkbn.kompendium.oas.payload.Response
|
import io.bkbn.kompendium.oas.payload.Response
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KMutableProperty1
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
object Helpers {
|
object Helpers {
|
||||||
|
|
||||||
fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: SpecConfig, schemaConfigurator: SchemaConfigurator) {
|
private fun PathOperation.addDefaultAuthMethods(methods: List<String>) {
|
||||||
|
methods.forEach { m ->
|
||||||
|
if (security == null || security?.all { s -> !s.containsKey(m) } == true) {
|
||||||
|
if (security == null) {
|
||||||
|
security = mutableListOf(mapOf(m to emptyList()))
|
||||||
|
} else {
|
||||||
|
security?.add(mapOf(m to emptyList()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MethodInfo.addToSpec(
|
||||||
|
path: Path,
|
||||||
|
spec: OpenApiSpec,
|
||||||
|
config: SpecConfig,
|
||||||
|
schemaConfigurator: SchemaConfigurator,
|
||||||
|
routePath: String,
|
||||||
|
authMethods: List<String> = emptyList()
|
||||||
|
) {
|
||||||
SchemaGenerator.fromTypeOrUnit(
|
SchemaGenerator.fromTypeOrUnit(
|
||||||
this.response.responseType,
|
this.response.responseType,
|
||||||
spec.components.schemas, schemaConfigurator
|
spec.components.schemas, schemaConfigurator
|
||||||
@ -55,15 +77,25 @@ object Helpers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val operations = this.toPathOperation(config)
|
val operations = this.toPathOperation(config)
|
||||||
|
operations.addDefaultAuthMethods(authMethods)
|
||||||
|
|
||||||
when (this) {
|
fun setOperation(
|
||||||
is DeleteInfo -> path.delete = operations
|
property: KMutableProperty1<Path, PathOperation?>
|
||||||
is GetInfo -> path.get = operations
|
) {
|
||||||
is HeadInfo -> path.head = operations
|
require(property.get(path) == null) {
|
||||||
is PatchInfo -> path.patch = operations
|
"A route has already been registered for path: $routePath and method: ${property.name.uppercase()}"
|
||||||
is PostInfo -> path.post = operations
|
}
|
||||||
is PutInfo -> path.put = operations
|
property.set(path, operations)
|
||||||
is OptionsInfo -> path.options = operations
|
}
|
||||||
|
|
||||||
|
return when (this) {
|
||||||
|
is DeleteInfo -> setOperation(Path::delete)
|
||||||
|
is GetInfo -> setOperation(Path::get)
|
||||||
|
is HeadInfo -> setOperation(Path::head)
|
||||||
|
is PatchInfo -> setOperation(Path::patch)
|
||||||
|
is PostInfo -> setOperation(Path::post)
|
||||||
|
is PutInfo -> setOperation(Path::put)
|
||||||
|
is OptionsInfo -> setOperation(Path::options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +114,7 @@ object Helpers {
|
|||||||
requestBody = when (this) {
|
requestBody = when (this) {
|
||||||
is MethodInfoWithRequest -> Request(
|
is MethodInfoWithRequest -> Request(
|
||||||
description = this.request.description,
|
description = this.request.description,
|
||||||
content = this.request.requestType.toReferenceContent(this.request.examples),
|
content = this.request.requestType.toReferenceContent(this.request.examples, this.request.mediaTypes),
|
||||||
required = true
|
required = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -91,7 +123,7 @@ object Helpers {
|
|||||||
responses = mapOf(
|
responses = mapOf(
|
||||||
this.response.responseCode.value to Response(
|
this.response.responseCode.value to Response(
|
||||||
description = this.response.description,
|
description = this.response.description,
|
||||||
content = this.response.responseType.toReferenceContent(this.response.examples)
|
content = this.response.responseType.toReferenceContent(this.response.examples, this.response.mediaTypes)
|
||||||
)
|
)
|
||||||
).plus(this.errors.toResponseMap())
|
).plus(this.errors.toResponseMap())
|
||||||
)
|
)
|
||||||
@ -99,18 +131,24 @@ object Helpers {
|
|||||||
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
||||||
error.responseCode.value to Response(
|
error.responseCode.value to Response(
|
||||||
description = error.description,
|
description = error.description,
|
||||||
content = error.responseType.toReferenceContent(error.examples)
|
content = error.responseType.toReferenceContent(error.examples, error.mediaTypes)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KType.toReferenceContent(examples: Map<String, MediaType.Example>?): Map<String, MediaType>? =
|
private fun KType.toReferenceContent(
|
||||||
|
examples: Map<String, MediaType.Example>?,
|
||||||
|
mediaTypes: Set<String>
|
||||||
|
): Map<String, MediaType>? =
|
||||||
when (this.classifier as KClass<*>) {
|
when (this.classifier as KClass<*>) {
|
||||||
Unit::class -> null
|
Unit::class -> null
|
||||||
else -> mapOf(
|
else -> mediaTypes.associateWith {
|
||||||
"application/json" to MediaType(
|
MediaType(
|
||||||
schema = ReferenceDefinition(this.getReferenceSlug()),
|
schema = if (this.isMarkedNullable) OneOfDefinition(
|
||||||
|
NullableDefinition(),
|
||||||
|
ReferenceDefinition(this.getReferenceSlug())
|
||||||
|
) else ReferenceDefinition(this.getReferenceSlug()),
|
||||||
examples = examples
|
examples = examples
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,56 +2,60 @@ package io.bkbn.kompendium.core
|
|||||||
|
|
||||||
import dev.forst.ktor.apikey.apiKey
|
import dev.forst.ktor.apikey.apiKey
|
||||||
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
||||||
import io.bkbn.kompendium.core.util.TestModules.complexRequest
|
import io.bkbn.kompendium.core.util.complexRequest
|
||||||
import io.bkbn.kompendium.core.util.TestModules.customAuthConfig
|
import io.bkbn.kompendium.core.util.customAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.TestModules.customFieldNameResponse
|
import io.bkbn.kompendium.core.util.customFieldNameResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.dateTimeString
|
import io.bkbn.kompendium.core.util.dateTimeString
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultAuthConfig
|
import io.bkbn.kompendium.core.util.defaultAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultField
|
import io.bkbn.kompendium.core.util.defaultField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultParameter
|
import io.bkbn.kompendium.core.util.defaultParameter
|
||||||
import io.bkbn.kompendium.core.util.TestModules.exampleParams
|
import io.bkbn.kompendium.core.util.exampleParams
|
||||||
import io.bkbn.kompendium.core.util.TestModules.genericException
|
import io.bkbn.kompendium.core.util.genericException
|
||||||
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponse
|
import io.bkbn.kompendium.core.util.genericPolymorphicResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls
|
import io.bkbn.kompendium.core.util.genericPolymorphicResponseMultipleImpls
|
||||||
import io.bkbn.kompendium.core.util.TestModules.gnarlyGenericResponse
|
import io.bkbn.kompendium.core.util.gnarlyGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.headerParameter
|
import io.bkbn.kompendium.core.util.headerParameter
|
||||||
import io.bkbn.kompendium.core.util.TestModules.ignoredFieldsResponse
|
import io.bkbn.kompendium.core.util.ignoredFieldsResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.multipleAuthStrategies
|
import io.bkbn.kompendium.core.util.multipleAuthStrategies
|
||||||
import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
|
import io.bkbn.kompendium.core.util.multipleExceptions
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection
|
import io.bkbn.kompendium.core.util.nestedGenericCollection
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericMultipleParamsCollection
|
import io.bkbn.kompendium.core.util.nestedGenericMultipleParamsCollection
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericResponse
|
import io.bkbn.kompendium.core.util.nestedGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedTypeName
|
import io.bkbn.kompendium.core.util.nestedTypeName
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedUnderRoot
|
import io.bkbn.kompendium.core.util.nonRequiredParam
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nonRequiredParam
|
import io.bkbn.kompendium.core.util.nonRequiredParams
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nonRequiredParams
|
import io.bkbn.kompendium.core.util.notarizedDelete
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedDelete
|
import io.bkbn.kompendium.core.util.notarizedGet
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedGet
|
import io.bkbn.kompendium.core.util.notarizedHead
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedHead
|
import io.bkbn.kompendium.core.util.notarizedOptions
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedOptions
|
import io.bkbn.kompendium.core.util.notarizedPatch
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedPatch
|
import io.bkbn.kompendium.core.util.notarizedPost
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedPost
|
import io.bkbn.kompendium.core.util.notarizedPut
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedPut
|
import io.bkbn.kompendium.core.util.nullableEnumField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableEnumField
|
import io.bkbn.kompendium.core.util.nullableField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableField
|
import io.bkbn.kompendium.core.util.nullableNestedObject
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableNestedObject
|
import io.bkbn.kompendium.core.util.nullableReference
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableReference
|
import io.bkbn.kompendium.core.util.overrideMediaTypes
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicCollectionResponse
|
import io.bkbn.kompendium.core.util.polymorphicCollectionResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicException
|
import io.bkbn.kompendium.core.util.polymorphicException
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicMapResponse
|
import io.bkbn.kompendium.core.util.polymorphicMapResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicResponse
|
import io.bkbn.kompendium.core.util.polymorphicResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.primitives
|
import io.bkbn.kompendium.core.util.primitives
|
||||||
import io.bkbn.kompendium.core.util.TestModules.reqRespExamples
|
import io.bkbn.kompendium.core.util.reqRespExamples
|
||||||
import io.bkbn.kompendium.core.util.TestModules.requiredParams
|
import io.bkbn.kompendium.core.util.requiredParams
|
||||||
import io.bkbn.kompendium.core.util.TestModules.returnsList
|
import io.bkbn.kompendium.core.util.returnsList
|
||||||
import io.bkbn.kompendium.core.util.TestModules.rootRoute
|
import io.bkbn.kompendium.core.util.samePathDifferentMethodsAndAuth
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simpleGenericResponse
|
import io.bkbn.kompendium.core.util.samePathSameMethod
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simplePathParsing
|
import io.bkbn.kompendium.core.util.simpleGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simpleRecursive
|
import io.bkbn.kompendium.core.util.simpleRecursive
|
||||||
import io.bkbn.kompendium.core.util.TestModules.singleException
|
import io.bkbn.kompendium.core.util.singleException
|
||||||
import io.bkbn.kompendium.core.util.TestModules.trailingSlash
|
import io.bkbn.kompendium.core.util.topLevelNullable
|
||||||
import io.bkbn.kompendium.core.util.TestModules.unbackedFieldsResponse
|
import io.bkbn.kompendium.core.util.unbackedFieldsResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.withOperationId
|
import io.bkbn.kompendium.core.util.withOperationId
|
||||||
|
import io.bkbn.kompendium.core.util.nestedUnderRoot
|
||||||
|
import io.bkbn.kompendium.core.util.rootRoute
|
||||||
|
import io.bkbn.kompendium.core.util.simplePathParsing
|
||||||
|
import io.bkbn.kompendium.core.util.trailingSlash
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import io.bkbn.kompendium.json.schema.exception.UnknownSchemaException
|
import io.bkbn.kompendium.json.schema.exception.UnknownSchemaException
|
||||||
import io.bkbn.kompendium.oas.component.Components
|
import io.bkbn.kompendium.oas.component.Components
|
||||||
@ -73,8 +77,8 @@ import io.ktor.server.auth.UserIdPrincipal
|
|||||||
import io.ktor.server.auth.basic
|
import io.ktor.server.auth.basic
|
||||||
import io.ktor.server.auth.jwt.jwt
|
import io.ktor.server.auth.jwt.jwt
|
||||||
import io.ktor.server.auth.oauth
|
import io.ktor.server.auth.oauth
|
||||||
import kotlin.reflect.typeOf
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
class KompendiumTest : DescribeSpec({
|
class KompendiumTest : DescribeSpec({
|
||||||
describe("Notarized Open API Metadata Tests") {
|
describe("Notarized Open API Metadata Tests") {
|
||||||
@ -111,6 +115,9 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can notarize a route with non-required params") {
|
it("Can notarize a route with non-required params") {
|
||||||
openApiTestAllSerializers("T0011__non_required_params.json") { nonRequiredParams() }
|
openApiTestAllSerializers("T0011__non_required_params.json") { nonRequiredParams() }
|
||||||
}
|
}
|
||||||
|
it("Can override media types") {
|
||||||
|
openApiTestAllSerializers("T0052__override_media_types.json") { overrideMediaTypes() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Route Parsing") {
|
describe("Route Parsing") {
|
||||||
it("Can parse a simple path and store it under the expected route") {
|
it("Can parse a simple path and store it under the expected route") {
|
||||||
@ -243,12 +250,45 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can handle nested type names") {
|
it("Can handle nested type names") {
|
||||||
openApiTestAllSerializers("T0044__nested_type_name.json") { nestedTypeName() }
|
openApiTestAllSerializers("T0044__nested_type_name.json") { nestedTypeName() }
|
||||||
}
|
}
|
||||||
|
it("Can handle top level nullable types") {
|
||||||
|
openApiTestAllSerializers("T0051__top_level_nullable.json") { topLevelNullable() }
|
||||||
|
}
|
||||||
|
it("Can handle multiple registrations for different methods with the same path and different auth") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
"T0053__same_path_different_methods_and_auth.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
basic("basic") {
|
||||||
|
realm = "Ktor Server"
|
||||||
|
validate { UserIdPrincipal("Placeholder") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"basic" to BasicAuth()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { samePathDifferentMethodsAndAuth() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Error Handling") {
|
describe("Error Handling") {
|
||||||
it("Throws a clear exception when an unidentified type is encountered") {
|
it("Throws a clear exception when an unidentified type is encountered") {
|
||||||
val exception = shouldThrow<UnknownSchemaException> { openApiTestAllSerializers("") { dateTimeString() } }
|
val exception = shouldThrow<UnknownSchemaException> { openApiTestAllSerializers("") { dateTimeString() } }
|
||||||
exception.message should startWith("An unknown type was encountered: class java.time.Instant")
|
exception.message should startWith("An unknown type was encountered: class java.time.Instant")
|
||||||
}
|
}
|
||||||
|
it("Throws an exception when same method for same path has been previously registered") {
|
||||||
|
val exception = shouldThrow<IllegalArgumentException> {
|
||||||
|
openApiTestAllSerializers("") {
|
||||||
|
samePathSameMethod()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exception.message should startWith("A route has already been registered for path: /test/{a} and method: GET")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Constraints") {
|
describe("Constraints") {
|
||||||
// TODO Assess strategies here
|
// TODO Assess strategies here
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.authenticate
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.defaultAuthConfig() {
|
||||||
|
authenticate("basic") {
|
||||||
|
route(rootPath) {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.customAuthConfig() {
|
||||||
|
authenticate("auth-oauth-google") {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
security = mapOf(
|
||||||
|
"auth-oauth-google" to listOf("read:pets")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.multipleAuthStrategies() {
|
||||||
|
authenticate("jwt", "api-key") {
|
||||||
|
route(rootPath) {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.SerialNameObject
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TransientObject
|
||||||
|
import io.bkbn.kompendium.core.fixtures.UnbackedObject
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
|
||||||
|
fun Routing.ignoredFieldsResponse() = basicGetGenerator<TransientObject>()
|
||||||
|
fun Routing.unbackedFieldsResponse() = basicGetGenerator<UnbackedObject>()
|
||||||
|
fun Routing.customFieldNameResponse() = basicGetGenerator<SerialNameObject>()
|
@ -0,0 +1,16 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
|
||||||
|
fun Routing.defaultParameter() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING.withDefault("IDK")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
@ -0,0 +1,16 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPath
|
||||||
|
import io.ktor.server.auth.authenticate
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.samePathSameMethod() {
|
||||||
|
route(defaultPath) {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
authenticate {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestNested
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultRequestDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.reqRespExamples() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
description(defaultRequestDescription)
|
||||||
|
requestType<TestRequest>()
|
||||||
|
examples(
|
||||||
|
"Testerina" to TestRequest(TestNested("asdf"), 1.5, emptyList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
examples(
|
||||||
|
"Testerino" to TestResponse("Heya")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.exampleParams() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING,
|
||||||
|
examples = mapOf(
|
||||||
|
"foo" to Parameter.Example("testing")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
105
core/src/test/kotlin/io/bkbn/kompendium/core/util/Exceptions.kt
Normal file
105
core/src/test/kotlin/io/bkbn/kompendium/core/util/Exceptions.kt
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ExceptionResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Flibbity
|
||||||
|
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.singleException() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Bad Things Happened")
|
||||||
|
responseCode(HttpStatusCode.BadRequest)
|
||||||
|
responseType<ExceptionResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.multipleExceptions() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Bad Things Happened")
|
||||||
|
responseCode(HttpStatusCode.BadRequest)
|
||||||
|
responseType<ExceptionResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Access Denied")
|
||||||
|
responseCode(HttpStatusCode.Forbidden)
|
||||||
|
responseType<ExceptionResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.polymorphicException() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Bad Things Happened")
|
||||||
|
responseCode(HttpStatusCode.InternalServerError)
|
||||||
|
responseType<FlibbityGibbit>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.genericException() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Bad Things Happened")
|
||||||
|
responseCode(HttpStatusCode.BadRequest)
|
||||||
|
responseType<Flibbity<String>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ColumnSchema
|
||||||
|
import io.bkbn.kompendium.core.fixtures.DateTimeString
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ManyThings
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Nested
|
||||||
|
import io.bkbn.kompendium.core.fixtures.NullableEnum
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ProfileUpdateRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultParams
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPath
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultRequestDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.authenticate
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.withOperationId() = basicGetGenerator<TestResponse>(operationId = "getThisDude")
|
||||||
|
fun Routing.nullableNestedObject() = basicGetGenerator<ProfileUpdateRequest>()
|
||||||
|
fun Routing.nullableEnumField() = basicGetGenerator<NullableEnum>()
|
||||||
|
fun Routing.nullableReference() = basicGetGenerator<ManyThings>()
|
||||||
|
fun Routing.dateTimeString() = basicGetGenerator<DateTimeString>()
|
||||||
|
fun Routing.headerParameter() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "X-User-Email",
|
||||||
|
`in` = Parameter.Location.header,
|
||||||
|
schema = TypeDefinition.STRING,
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Routing.nestedTypeName() = basicGetGenerator<Nested.Response>()
|
||||||
|
fun Routing.topLevelNullable() = basicGetGenerator<TestResponse?>()
|
||||||
|
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
|
||||||
|
fun Routing.samePathDifferentMethodsAndAuth() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authenticate("basic") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
description(defaultRequestDescription)
|
||||||
|
requestType<TestSimpleRequest>()
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,301 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
||||||
|
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.HeadInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultParams
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPath
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondText
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.delete
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.head
|
||||||
|
import io.ktor.server.routing.options
|
||||||
|
import io.ktor.server.routing.patch
|
||||||
|
import io.ktor.server.routing.post
|
||||||
|
import io.ktor.server.routing.put
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.notarizedGet() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
get = GetInfo.builder {
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
call.respondText { "hey dude ‼️ congrats on the get request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedPost() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<TestSimpleRequest>()
|
||||||
|
description("A Test request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
call.respondText { "hey dude ‼️ congrats on the post request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedPut() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<TestSimpleRequest>()
|
||||||
|
description("A Test request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
put {
|
||||||
|
call.respondText { "hey dude ‼️ congrats on the post request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedDelete() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
delete = DeleteInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.NoContent)
|
||||||
|
responseType<Unit>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete {
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedPatch() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
patch = PatchInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
description("A Test request")
|
||||||
|
requestType<TestSimpleRequest>()
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patch {
|
||||||
|
call.respond(HttpStatusCode.Created) { TestCreatedResponse(123, "Nice!") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedHead() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
head = HeadInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
|
||||||
|
response {
|
||||||
|
description("great!")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<Unit>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
head {
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedOptions() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
options = OptionsInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("nice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options {
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.complexRequest() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<ComplexRequest>()
|
||||||
|
description("A Complex request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patch {
|
||||||
|
call.respond(HttpStatusCode.Created, TestCreatedResponse(123, "nice!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.primitives() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<Int>()
|
||||||
|
description("A Test Request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<Boolean>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.returnsList() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description("A Successful List-y Endeavor")
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<List<TestResponse>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.nonRequiredParams() {
|
||||||
|
route("/optional") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "notRequired",
|
||||||
|
`in` = Parameter.Location.query,
|
||||||
|
schema = TypeDefinition.STRING,
|
||||||
|
required = false,
|
||||||
|
),
|
||||||
|
Parameter(
|
||||||
|
name = "required",
|
||||||
|
`in` = Parameter.Location.query,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseType<Unit>()
|
||||||
|
description("Empty")
|
||||||
|
responseCode(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.overrideMediaTypes() {
|
||||||
|
route("/media_types") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
mediaTypes("multipart/form-data", "application/json")
|
||||||
|
requestType<TestRequest>()
|
||||||
|
description("A cool request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
mediaTypes("application/xml")
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("A good response")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Barzo
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Flibbity
|
||||||
|
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Foosy
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Gibbity
|
||||||
|
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Page
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
|
||||||
|
fun Routing.polymorphicResponse() = basicGetGenerator<FlibbityGibbit>()
|
||||||
|
fun Routing.polymorphicCollectionResponse() = basicGetGenerator<List<FlibbityGibbit>>()
|
||||||
|
fun Routing.polymorphicMapResponse() = basicGetGenerator<Map<String, FlibbityGibbit>>()
|
||||||
|
fun Routing.simpleGenericResponse() = basicGetGenerator<Gibbity<String>>()
|
||||||
|
fun Routing.gnarlyGenericResponse() = basicGetGenerator<Foosy<Barzo<Int>, String>>()
|
||||||
|
fun Routing.nestedGenericResponse() = basicGetGenerator<Gibbity<Map<String, String>>>()
|
||||||
|
fun Routing.genericPolymorphicResponse() = basicGetGenerator<Flibbity<Double>>()
|
||||||
|
fun Routing.genericPolymorphicResponseMultipleImpls() = basicGetGenerator<Flibbity<FlibbityGibbit>>()
|
||||||
|
fun Routing.nestedGenericCollection() = basicGetGenerator<Page<Int>>()
|
||||||
|
fun Routing.nestedGenericMultipleParamsCollection() = basicGetGenerator<MultiNestedGenerics<String, ComplexRequest>>()
|
@ -0,0 +1,32 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.DefaultField
|
||||||
|
import io.bkbn.kompendium.core.fixtures.NullableField
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
|
||||||
|
fun Routing.requiredParams() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Routing.nonRequiredParam() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.query,
|
||||||
|
schema = TypeDefinition.STRING,
|
||||||
|
required = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Routing.defaultField() = basicGetGenerator<DefaultField>()
|
||||||
|
fun Routing.nullableField() = basicGetGenerator<NullableField>()
|
@ -0,0 +1,102 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultParams
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.simplePathParsing() {
|
||||||
|
route("/this") {
|
||||||
|
route("/is") {
|
||||||
|
route("/a") {
|
||||||
|
route("/complex") {
|
||||||
|
route("path") {
|
||||||
|
route("with/an/{id}") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
parameters = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.rootRoute() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = listOf(defaultParams.last())
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.nestedUnderRoot() {
|
||||||
|
route("/") {
|
||||||
|
route("/testerino") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.trailingSlash() {
|
||||||
|
route("/test") {
|
||||||
|
route("/") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,66 +1,29 @@
|
|||||||
package io.bkbn.kompendium.core.util
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Barzo
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ColumnSchema
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.DateTimeString
|
|
||||||
import io.bkbn.kompendium.core.fixtures.DefaultField
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ExceptionResponse
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Flibbity
|
|
||||||
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Foosy
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Gibbity
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ManyThings
|
|
||||||
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Nested
|
|
||||||
import io.bkbn.kompendium.core.fixtures.NullableEnum
|
|
||||||
import io.bkbn.kompendium.core.fixtures.NullableField
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Page
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ProfileUpdateRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.SerialNameObject
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestNested
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestResponse
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TransientObject
|
|
||||||
import io.bkbn.kompendium.core.fixtures.UnbakcedObject
|
|
||||||
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
|
||||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
import io.bkbn.kompendium.core.metadata.HeadInfo
|
|
||||||
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
|
||||||
import io.bkbn.kompendium.core.metadata.PatchInfo
|
|
||||||
import io.bkbn.kompendium.core.metadata.PostInfo
|
|
||||||
import io.bkbn.kompendium.core.metadata.PutInfo
|
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.auth.authenticate
|
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.response.respondText
|
|
||||||
import io.ktor.server.routing.Route
|
import io.ktor.server.routing.Route
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.routing.delete
|
|
||||||
import io.ktor.server.routing.get
|
|
||||||
import io.ktor.server.routing.head
|
|
||||||
import io.ktor.server.routing.options
|
|
||||||
import io.ktor.server.routing.patch
|
|
||||||
import io.ktor.server.routing.post
|
|
||||||
import io.ktor.server.routing.put
|
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
object TestModules {
|
object TestModules {
|
||||||
private const val defaultPath = "/test/{a}"
|
|
||||||
private const val rootPath = "/"
|
|
||||||
private const val defaultResponseDescription = "A Successful Endeavor"
|
|
||||||
private const val defaultRequestDescription = "You gotta send it"
|
|
||||||
private const val defaultPathSummary = "Great Summary!"
|
|
||||||
private const val defaultPathDescription = "testing more"
|
|
||||||
|
|
||||||
private val defaultParams = listOf(
|
const val defaultPath = "/test/{a}"
|
||||||
|
const val rootPath = "/"
|
||||||
|
const val defaultResponseDescription = "A Successful Endeavor"
|
||||||
|
const val defaultRequestDescription = "You gotta send it"
|
||||||
|
const val defaultPathSummary = "Great Summary!"
|
||||||
|
const val defaultPathDescription = "testing more"
|
||||||
|
|
||||||
|
val defaultParams = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
name = "a",
|
name = "a",
|
||||||
`in` = Parameter.Location.path,
|
`in` = Parameter.Location.path,
|
||||||
@ -72,610 +35,31 @@ object TestModules {
|
|||||||
schema = TypeDefinition.INT
|
schema = TypeDefinition.INT
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun Routing.notarizedGet() {
|
internal inline fun <reified T> Routing.basicGetGenerator(
|
||||||
route(defaultPath) {
|
params: List<Parameter> = emptyList(),
|
||||||
install(NotarizedRoute()) {
|
operationId: String? = null
|
||||||
parameters = defaultParams
|
) {
|
||||||
get = GetInfo.builder {
|
route(rootPath) {
|
||||||
response {
|
basicGetGenerator<T>(params, operationId)
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get {
|
|
||||||
call.respondText { "hey dude ‼️ congrats on the get request" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Routing.notarizedPost() {
|
internal inline fun <reified T> Route.basicGetGenerator(
|
||||||
route(defaultPath) {
|
params: List<Parameter> = emptyList(),
|
||||||
install(NotarizedRoute()) {
|
operationId: String? = null
|
||||||
parameters = defaultParams
|
) {
|
||||||
post = PostInfo.builder {
|
install(NotarizedRoute()) {
|
||||||
summary(defaultPathSummary)
|
get = GetInfo.builder {
|
||||||
description(defaultPathDescription)
|
summary(defaultPathSummary)
|
||||||
request {
|
description(defaultPathDescription)
|
||||||
requestType<TestSimpleRequest>()
|
operationId?.let { operationId(it) }
|
||||||
description("A Test request")
|
parameters = params
|
||||||
}
|
response {
|
||||||
response {
|
description(defaultResponseDescription)
|
||||||
responseCode(HttpStatusCode.Created)
|
responseCode(HttpStatusCode.OK)
|
||||||
responseType<TestCreatedResponse>()
|
responseType<T>()
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
call.respondText { "hey dude ‼️ congrats on the post request" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedPut() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
put = PutInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
requestType<TestSimpleRequest>()
|
|
||||||
description("A Test request")
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<TestCreatedResponse>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
put {
|
|
||||||
call.respondText { "hey dude ‼️ congrats on the post request" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedDelete() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
delete = DeleteInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.NoContent)
|
|
||||||
responseType<Unit>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete {
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedPatch() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
patch = PatchInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
description("A Test request")
|
|
||||||
requestType<TestSimpleRequest>()
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<TestCreatedResponse>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
patch {
|
|
||||||
call.respond(HttpStatusCode.Created) { TestCreatedResponse(123, "Nice!") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedHead() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
head = HeadInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
|
|
||||||
response {
|
|
||||||
description("great!")
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<Unit>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
head {
|
|
||||||
call.respond(HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedOptions() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
options = OptionsInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
description("nice")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
options {
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.complexRequest() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
put = PutInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
requestType<ComplexRequest>()
|
|
||||||
description("A Complex request")
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<TestCreatedResponse>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
patch {
|
|
||||||
call.respond(HttpStatusCode.Created, TestCreatedResponse(123, "nice!"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.primitives() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
put = PutInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
requestType<Int>()
|
|
||||||
description("A Test Request")
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<Boolean>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.returnsList() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description("A Successful List-y Endeavor")
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<List<TestResponse>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.nonRequiredParams() {
|
|
||||||
route("/optional") {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "notRequired",
|
|
||||||
`in` = Parameter.Location.query,
|
|
||||||
schema = TypeDefinition.STRING,
|
|
||||||
required = false,
|
|
||||||
),
|
|
||||||
Parameter(
|
|
||||||
name = "required",
|
|
||||||
`in` = Parameter.Location.query,
|
|
||||||
schema = TypeDefinition.STRING
|
|
||||||
)
|
|
||||||
)
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
responseType<Unit>()
|
|
||||||
description("Empty")
|
|
||||||
responseCode(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.simplePathParsing() {
|
|
||||||
route("/this") {
|
|
||||||
route("/is") {
|
|
||||||
route("/a") {
|
|
||||||
route("/complex") {
|
|
||||||
route("path") {
|
|
||||||
route("with/an/{id}") {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
parameters = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.path,
|
|
||||||
schema = TypeDefinition.STRING
|
|
||||||
)
|
|
||||||
)
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.rootRoute() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = listOf(defaultParams.last())
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.nestedUnderRoot() {
|
|
||||||
route("/") {
|
|
||||||
route("/testerino") {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.trailingSlash() {
|
|
||||||
route("/test") {
|
|
||||||
route("/") {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.singleException() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Bad Things Happened")
|
|
||||||
responseCode(HttpStatusCode.BadRequest)
|
|
||||||
responseType<ExceptionResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.multipleExceptions() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Bad Things Happened")
|
|
||||||
responseCode(HttpStatusCode.BadRequest)
|
|
||||||
responseType<ExceptionResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Access Denied")
|
|
||||||
responseCode(HttpStatusCode.Forbidden)
|
|
||||||
responseType<ExceptionResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.polymorphicException() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Bad Things Happened")
|
|
||||||
responseCode(HttpStatusCode.InternalServerError)
|
|
||||||
responseType<FlibbityGibbit>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.genericException() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Bad Things Happened")
|
|
||||||
responseCode(HttpStatusCode.BadRequest)
|
|
||||||
responseType<Flibbity<String>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.reqRespExamples() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
post = PostInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
description(defaultRequestDescription)
|
|
||||||
requestType<TestRequest>()
|
|
||||||
examples(
|
|
||||||
"Testerina" to TestRequest(TestNested("asdf"), 1.5, emptyList())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
examples(
|
|
||||||
"Testerino" to TestResponse("Heya")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.exampleParams() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.path,
|
|
||||||
schema = TypeDefinition.STRING,
|
|
||||||
examples = mapOf(
|
|
||||||
"foo" to Parameter.Example("testing")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.defaultParameter() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.path,
|
|
||||||
schema = TypeDefinition.STRING.withDefault("IDK")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.requiredParams() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.path,
|
|
||||||
schema = TypeDefinition.STRING
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.nonRequiredParam() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.query,
|
|
||||||
schema = TypeDefinition.STRING,
|
|
||||||
required = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.defaultField() = basicGetGenerator<DefaultField>()
|
|
||||||
|
|
||||||
fun Routing.nullableField() = basicGetGenerator<NullableField>()
|
|
||||||
|
|
||||||
fun Routing.polymorphicResponse() = basicGetGenerator<FlibbityGibbit>()
|
|
||||||
|
|
||||||
fun Routing.ignoredFieldsResponse() = basicGetGenerator<TransientObject>()
|
|
||||||
|
|
||||||
fun Routing.unbackedFieldsResponse() = basicGetGenerator<UnbakcedObject>()
|
|
||||||
|
|
||||||
fun Routing.customFieldNameResponse() = basicGetGenerator<SerialNameObject>()
|
|
||||||
|
|
||||||
fun Routing.polymorphicCollectionResponse() = basicGetGenerator<List<FlibbityGibbit>>()
|
|
||||||
|
|
||||||
fun Routing.polymorphicMapResponse() = basicGetGenerator<Map<String, FlibbityGibbit>>()
|
|
||||||
|
|
||||||
fun Routing.simpleGenericResponse() = basicGetGenerator<Gibbity<String>>()
|
|
||||||
|
|
||||||
fun Routing.gnarlyGenericResponse() = basicGetGenerator<Foosy<Barzo<Int>, String>>()
|
|
||||||
|
|
||||||
fun Routing.nestedGenericResponse() = basicGetGenerator<Gibbity<Map<String, String>>>()
|
|
||||||
|
|
||||||
fun Routing.genericPolymorphicResponse() = basicGetGenerator<Flibbity<Double>>()
|
|
||||||
|
|
||||||
fun Routing.genericPolymorphicResponseMultipleImpls() = basicGetGenerator<Flibbity<FlibbityGibbit>>()
|
|
||||||
|
|
||||||
fun Routing.nestedGenericCollection() = basicGetGenerator<Page<Int>>()
|
|
||||||
|
|
||||||
fun Routing.nestedGenericMultipleParamsCollection() = basicGetGenerator<MultiNestedGenerics<String, ComplexRequest>>()
|
|
||||||
|
|
||||||
fun Routing.withOperationId() = basicGetGenerator<TestResponse>(operationId = "getThisDude")
|
|
||||||
|
|
||||||
fun Routing.nullableNestedObject() = basicGetGenerator<ProfileUpdateRequest>()
|
|
||||||
|
|
||||||
fun Routing.nullableEnumField() = basicGetGenerator<NullableEnum>()
|
|
||||||
|
|
||||||
fun Routing.nullableReference() = basicGetGenerator<ManyThings>()
|
|
||||||
|
|
||||||
fun Routing.dateTimeString() = basicGetGenerator<DateTimeString>()
|
|
||||||
|
|
||||||
fun Routing.headerParameter() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "X-User-Email",
|
|
||||||
`in` = Parameter.Location.header,
|
|
||||||
schema = TypeDefinition.STRING,
|
|
||||||
required = true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.nestedTypeName() = basicGetGenerator<Nested.Response>()
|
|
||||||
|
|
||||||
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
|
|
||||||
|
|
||||||
fun Routing.defaultAuthConfig() {
|
|
||||||
authenticate("basic") {
|
|
||||||
route(rootPath) {
|
|
||||||
basicGetGenerator<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.customAuthConfig() {
|
|
||||||
authenticate("auth-oauth-google") {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
security = mapOf(
|
|
||||||
"auth-oauth-google" to listOf("read:pets")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.multipleAuthStrategies() {
|
|
||||||
authenticate("jwt", "api-key") {
|
|
||||||
route(rootPath) {
|
|
||||||
basicGetGenerator<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T> Routing.basicGetGenerator(
|
|
||||||
params: List<Parameter> = emptyList(),
|
|
||||||
operationId: String? = null
|
|
||||||
) {
|
|
||||||
route(rootPath) {
|
|
||||||
basicGetGenerator<T>(params, operationId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T> Route.basicGetGenerator(
|
|
||||||
params: List<Parameter> = emptyList(),
|
|
||||||
operationId: String? = null
|
|
||||||
) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
operationId?.let { operationId(it) }
|
|
||||||
parameters = params
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<T>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,15 +124,19 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"enumeration": {
|
"enumeration": {
|
||||||
"enum": [
|
"$ref": "#/components/schemas/SimpleEnum"
|
||||||
"ONE",
|
|
||||||
"TWO"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"enumeration"
|
"enumeration"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"SimpleEnum": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ONE",
|
||||||
|
"TWO"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
||||||
|
@ -91,37 +91,30 @@
|
|||||||
"required": []
|
"required": []
|
||||||
},
|
},
|
||||||
"ProfileMetadataUpdateRequest": {
|
"ProfileMetadataUpdateRequest": {
|
||||||
"oneOf": [
|
"type": "object",
|
||||||
{
|
"properties": {
|
||||||
"type": "null"
|
"isPrivate": {
|
||||||
},
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "null"
|
||||||
"properties": {
|
|
||||||
"isPrivate": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"otherThing": {
|
{
|
||||||
"oneOf": [
|
"type": "boolean"
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
"required": []
|
},
|
||||||
|
"otherThing": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
|
"required": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
||||||
|
@ -62,16 +62,21 @@
|
|||||||
"type": "null"
|
"type": "null"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"$ref": "#/components/schemas/TestEnum"
|
||||||
"YES",
|
|
||||||
"NO"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
}
|
},
|
||||||
|
"TestEnum":
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"YES",
|
||||||
|
"NO"
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
||||||
},
|
},
|
||||||
|
@ -57,10 +57,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"enumeration": {
|
"enumeration": {
|
||||||
"enum": [
|
"$ref": "#/components/schemas/SimpleEnum"
|
||||||
"ONE",
|
|
||||||
"TWO"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@ -120,6 +117,13 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"content"
|
"content"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"SimpleEnum": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ONE",
|
||||||
|
"TWO"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
||||||
|
@ -53,6 +53,14 @@
|
|||||||
"webhooks": {},
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
|
"ColumnMode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"NULLABLE",
|
||||||
|
"REQUIRED",
|
||||||
|
"REPEATED"
|
||||||
|
]
|
||||||
|
},
|
||||||
"ColumnSchema": {
|
"ColumnSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -60,11 +68,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"enum": [
|
"$ref": "#/components/schemas/ColumnMode"
|
||||||
"NULLABLE",
|
|
||||||
"REQUIRED",
|
|
||||||
"REPEATED"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/UnbakcedObject"
|
"$ref": "#/components/schemas/UnbackedObject"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@
|
|||||||
"webhooks": {},
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"UnbakcedObject": {
|
"UnbackedObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"backed": {
|
"backed": {
|
||||||
|
79
core/src/test/resources/T0051__top_level_nullable.json
Normal file
79
core/src/test/resources/T0051__top_level_nullable.json
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
123
core/src/test/resources/T0052__override_media_types.json
Normal file
123
core/src/test/resources/T0052__override_media_types.json
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/media_types": {
|
||||||
|
"put": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A cool request",
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestRequest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A good response",
|
||||||
|
"content": {
|
||||||
|
"application/xml": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"aaa": {
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"fieldName": {
|
||||||
|
"$ref": "#/components/schemas/TestNested"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"aaa",
|
||||||
|
"b",
|
||||||
|
"fieldName"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestNested": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nesty": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nesty"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "You gotta send it",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestSimpleRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestCreatedResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestCreatedResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestSimpleRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
|
"basic": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "basic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -163,7 +163,7 @@ data class TransientObject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UnbakcedObject(
|
data class UnbackedObject(
|
||||||
val backed: String
|
val backed: String
|
||||||
) {
|
) {
|
||||||
val unbacked: String get() = "unbacked"
|
val unbacked: String get() = "unbacked"
|
||||||
@ -176,3 +176,14 @@ data class SerialNameObject(
|
|||||||
@SerialName("snake_case_name")
|
@SerialName("snake_case_name")
|
||||||
val camelCaseName: String
|
val camelCaseName: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
enum class Color {
|
||||||
|
RED,
|
||||||
|
GREEN,
|
||||||
|
BLUE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ObjectWithEnum(
|
||||||
|
val color: Color
|
||||||
|
)
|
||||||
|
@ -4,6 +4,12 @@ You can read more about it [here](https://ktor.io/docs/type-safe-routing.html).
|
|||||||
|
|
||||||
Kompendium supports Ktor-Resources through an ancillary module `kompendium-resources`
|
Kompendium supports Ktor-Resources through an ancillary module `kompendium-resources`
|
||||||
|
|
||||||
|
{% hint style="warning" %}
|
||||||
|
The resources module contains _two_ plugins: `KompendiumResources` and `KompendiumResource`. You will find more
|
||||||
|
information on both below, but in a nutshell, the former is an application level plugin intended to define your entire
|
||||||
|
application, while the latter is a route level approach should you wish to split out your route definitions.
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
## Adding the Artifact
|
## Adding the Artifact
|
||||||
|
|
||||||
Prior to documenting your resources, you will need to add the artifact to your gradle build file.
|
Prior to documenting your resources, you will need to add the artifact to your gradle build file.
|
||||||
@ -14,9 +20,11 @@ dependencies {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installing Plugin
|
## NotarizedResources
|
||||||
|
|
||||||
Once you have installed the dependency, you can install the plugin. The `NotarizedResources` plugin is an _application_ level plugin, and **must** be install after both the `NotarizedApplication` plugin and the Ktor `Resources` plugin.
|
The `NotarizedResources` plugin is an _application_ level plugin, and **must** be installed after both the
|
||||||
|
`NotarizedApplication` plugin and the Ktor `Resources` plugin. It is intended to be used to document your entire
|
||||||
|
application in a single block.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
@ -54,7 +62,64 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, the `resources` property is a map of `KClass<*>` to `ResourceMetadata` instance describing that resource. This metadata is functionally identical to how a standard `NotarizedRoute` is defined.
|
Here, the `resources` property is a map of `KClass<*>` to `ResourceMetadata` instance describing that resource. This
|
||||||
|
metadata is functionally identical to how a standard `NotarizedRoute` is defined.
|
||||||
|
|
||||||
> ⚠️ If you try to map a class that is not annotated with the ktor `@Resource` annotation, you will get a runtime
|
{% hint style="danger" %}
|
||||||
> exception!
|
If you try to map a class that is not annotated with the ktor `@Resource` annotation, you will get a runtime exception!
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
|
## NotarizedResource
|
||||||
|
|
||||||
|
If you prefer a route-based approach similar to `NotarizedRoute`, you can use the `NotarizedResource<MyResourceType>()`
|
||||||
|
plugin instead of `NotarizedResources`. It will combine paths from any parent route with the route defined in the
|
||||||
|
resource, exactly as Ktor itself does:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Serializable
|
||||||
|
@Resource("/list/{name}/page/{page}")
|
||||||
|
data class Listing(val name: String, val page: Int)
|
||||||
|
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
install(Resources)
|
||||||
|
route("/api") {
|
||||||
|
listingDocumentation()
|
||||||
|
get<Listing> { listing ->
|
||||||
|
call.respondText("Listing ${listing.name}, page ${listing.page}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.listingDocumentation() {
|
||||||
|
install(NotarizedResource<Listing>()) {
|
||||||
|
parameters = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "name",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
),
|
||||||
|
Parameter(
|
||||||
|
name = "page",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.INT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get user by id")
|
||||||
|
description("A very neat endpoint!")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<ExampleResponse>()
|
||||||
|
description("Will return whether or not the user is real 😱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, the generated path will be `/api/list/{name}/page/{page}`, combining the route prefix with the path in the
|
||||||
|
resource.
|
||||||
|
|
||||||
|
{% hint style="danger" %}
|
||||||
|
If you try to map a class that is not annotated with the ktor `@Resource` annotation, you will get a runtime exception!
|
||||||
|
{% endhint %}
|
||||||
|
@ -165,3 +165,42 @@ get = GetInfo.builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Media Types
|
||||||
|
|
||||||
|
By default, Kompendium will set the only media type to "application/json". If you would like to override the media type
|
||||||
|
for a specific request or response (including errors), you can do so with the `mediaTypes` method
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get user by id")
|
||||||
|
description("A very neat endpoint!")
|
||||||
|
response {
|
||||||
|
mediaTypes("application/xml")
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<ExampleResponse>()
|
||||||
|
description("Will return whether or not the user is real 😱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Partial Authentication
|
||||||
|
|
||||||
|
One might want to have a public GET endpoint but a protected PUT endpoint. This can be achieved by registering two
|
||||||
|
separate notarized routes. Note that you will get an error if you try to register the same method twice, as each path
|
||||||
|
can only have one registration per method. Example:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
route("/user/{id}") {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
authenticate {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=3.5.0
|
project.version=3.8.0
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
@ -9,5 +9,5 @@ org.gradle.jvmargs=-Xmx2000m
|
|||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
ktorVersion=2.1.3
|
ktorVersion=2.1.3
|
||||||
kotestVersion=5.5.3
|
kotestVersion=5.5.4
|
||||||
detektVersion=1.21.0
|
detektVersion=1.21.0
|
||||||
|
@ -20,7 +20,7 @@ dependencies {
|
|||||||
// Versions
|
// Versions
|
||||||
val detektVersion: String by project
|
val detektVersion: String by project
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.20")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.21")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
||||||
|
|
||||||
// Formatting
|
// Formatting
|
||||||
|
@ -48,7 +48,7 @@ object SchemaGenerator {
|
|||||||
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
|
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
|
||||||
UUID::class -> checkForNull(type, TypeDefinition.UUID)
|
UUID::class -> checkForNull(type, TypeDefinition.UUID)
|
||||||
else -> when {
|
else -> when {
|
||||||
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz)
|
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz, cache)
|
||||||
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache, schemaConfigurator)
|
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache, schemaConfigurator)
|
||||||
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache, schemaConfigurator)
|
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache, schemaConfigurator)
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -4,5 +4,6 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class EnumDefinition(
|
data class EnumDefinition(
|
||||||
|
val type: String,
|
||||||
val enum: Set<String>
|
val enum: Set<String>
|
||||||
) : JsonSchema
|
) : JsonSchema
|
||||||
|
@ -2,18 +2,17 @@ package io.bkbn.kompendium.json.schema.handler
|
|||||||
|
|
||||||
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
|
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
|
||||||
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
object EnumHandler {
|
object EnumHandler {
|
||||||
fun handle(type: KType, clazz: KClass<*>): JsonSchema {
|
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
||||||
|
cache[type.getSimpleSlug()] = ReferenceDefinition(type.getReferenceSlug())
|
||||||
|
|
||||||
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
|
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
|
||||||
val definition = EnumDefinition(enum = options)
|
return EnumDefinition(type = "string", enum = options)
|
||||||
return when (type.isMarkedNullable) {
|
|
||||||
true -> OneOfDefinition(NullableDefinition(), definition)
|
|
||||||
false -> definition
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package io.bkbn.kompendium.json.schema.handler
|
|||||||
|
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
||||||
@ -71,16 +72,11 @@ object SimpleObjectHandler {
|
|||||||
.map { schemaConfigurator.serializableName(it) }
|
.map { schemaConfigurator.serializableName(it) }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
val definition = TypeDefinition(
|
return TypeDefinition(
|
||||||
type = "object",
|
type = "object",
|
||||||
properties = props,
|
properties = props,
|
||||||
required = required
|
required = required
|
||||||
)
|
)
|
||||||
|
|
||||||
return when (type.isMarkedNullable) {
|
|
||||||
true -> OneOfDefinition(NullableDefinition(), definition)
|
|
||||||
false -> definition
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KProperty<*>.needsToInjectGenerics(
|
private fun KProperty<*>.needsToInjectGenerics(
|
||||||
@ -103,7 +99,7 @@ object SimpleObjectHandler {
|
|||||||
}
|
}
|
||||||
val constructedType = propClass.createType(types)
|
val constructedType = propClass.createType(types)
|
||||||
return SchemaGenerator.fromTypeToSchema(constructedType, cache, schemaConfigurator).let {
|
return SchemaGenerator.fromTypeToSchema(constructedType, cache, schemaConfigurator).let {
|
||||||
if (it.isOrContainsObjectDef()) {
|
if (it.isOrContainsObjectOrEnumDef()) {
|
||||||
cache[constructedType.getSimpleSlug()] = it
|
cache[constructedType.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
||||||
} else {
|
} else {
|
||||||
@ -121,7 +117,7 @@ object SimpleObjectHandler {
|
|||||||
val type = typeMap[prop.returnType.classifier]?.type
|
val type = typeMap[prop.returnType.classifier]?.type
|
||||||
?: error("This indicates a bug in Kompendium, please open a GitHub issue")
|
?: error("This indicates a bug in Kompendium, please open a GitHub issue")
|
||||||
return SchemaGenerator.fromTypeToSchema(type, cache, schemaConfigurator).let {
|
return SchemaGenerator.fromTypeToSchema(type, cache, schemaConfigurator).let {
|
||||||
if (it.isOrContainsObjectDef()) {
|
if (it.isOrContainsObjectOrEnumDef()) {
|
||||||
cache[type.getSimpleSlug()] = it
|
cache[type.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(type.getReferenceSlug())
|
ReferenceDefinition(type.getReferenceSlug())
|
||||||
} else {
|
} else {
|
||||||
@ -136,7 +132,7 @@ object SimpleObjectHandler {
|
|||||||
schemaConfigurator: SchemaConfigurator
|
schemaConfigurator: SchemaConfigurator
|
||||||
): JsonSchema =
|
): JsonSchema =
|
||||||
SchemaGenerator.fromTypeToSchema(prop.returnType, cache, schemaConfigurator).let {
|
SchemaGenerator.fromTypeToSchema(prop.returnType, cache, schemaConfigurator).let {
|
||||||
if (it.isOrContainsObjectDef()) {
|
if (it.isOrContainsObjectOrEnumDef()) {
|
||||||
cache[prop.returnType.getSimpleSlug()] = it
|
cache[prop.returnType.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
||||||
} else {
|
} else {
|
||||||
@ -144,10 +140,12 @@ object SimpleObjectHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JsonSchema.isOrContainsObjectDef(): Boolean {
|
private fun JsonSchema.isOrContainsObjectOrEnumDef(): Boolean {
|
||||||
val isTypeDef = this is TypeDefinition && type == "object"
|
val isTypeDef = this is TypeDefinition && type == "object"
|
||||||
val isTypeDefOneOf = this is OneOfDefinition && this.oneOf.any { js -> js is TypeDefinition && js.type == "object" }
|
val isTypeDefOneOf = this is OneOfDefinition && this.oneOf.any { js -> js is TypeDefinition && js.type == "object" }
|
||||||
return isTypeDef || isTypeDefOneOf
|
val isEnumDef = this is EnumDefinition
|
||||||
|
val isEnumDefOneOf = this is OneOfDefinition && this.oneOf.any { js -> js is EnumDefinition }
|
||||||
|
return isTypeDef || isTypeDefOneOf || isEnumDef || isEnumDefOneOf
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JsonSchema.isNullable(): Boolean = this is OneOfDefinition && this.oneOf.any { it is NullableDefinition }
|
private fun JsonSchema.isNullable(): Boolean = this is OneOfDefinition && this.oneOf.any { it is NullableDefinition }
|
||||||
|
@ -2,6 +2,7 @@ package io.bkbn.kompendium.json.schema
|
|||||||
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ObjectWithEnum
|
||||||
import io.bkbn.kompendium.core.fixtures.SerialNameObject
|
import io.bkbn.kompendium.core.fixtures.SerialNameObject
|
||||||
import io.bkbn.kompendium.core.fixtures.SimpleEnum
|
import io.bkbn.kompendium.core.fixtures.SimpleEnum
|
||||||
import io.bkbn.kompendium.core.fixtures.SlammaJamma
|
import io.bkbn.kompendium.core.fixtures.SlammaJamma
|
||||||
@ -9,7 +10,7 @@ import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot
|
|||||||
import io.bkbn.kompendium.core.fixtures.TestResponse
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
||||||
import io.bkbn.kompendium.core.fixtures.TransientObject
|
import io.bkbn.kompendium.core.fixtures.TransientObject
|
||||||
import io.bkbn.kompendium.core.fixtures.UnbakcedObject
|
import io.bkbn.kompendium.core.fixtures.UnbackedObject
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.kotest.assertions.json.shouldEqualJson
|
import io.kotest.assertions.json.shouldEqualJson
|
||||||
import io.kotest.assertions.throwables.shouldThrow
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
@ -40,6 +41,7 @@ class SchemaGeneratorTest : DescribeSpec({
|
|||||||
jsonSchemaTest<ComplexRequest>("T0005__complex_object.json")
|
jsonSchemaTest<ComplexRequest>("T0005__complex_object.json")
|
||||||
}
|
}
|
||||||
it("Can generate the schema for a nullable object") {
|
it("Can generate the schema for a nullable object") {
|
||||||
|
// Same schema as a non-nullable type, since the nullability will be handled on the property
|
||||||
jsonSchemaTest<TestSimpleRequest?>("T0006__nullable_object.json")
|
jsonSchemaTest<TestSimpleRequest?>("T0006__nullable_object.json")
|
||||||
}
|
}
|
||||||
it("Can generate the schema for a polymorphic object") {
|
it("Can generate the schema for a polymorphic object") {
|
||||||
@ -52,7 +54,7 @@ class SchemaGeneratorTest : DescribeSpec({
|
|||||||
jsonSchemaTest<TransientObject>("T0018__transient_object.json")
|
jsonSchemaTest<TransientObject>("T0018__transient_object.json")
|
||||||
}
|
}
|
||||||
it("Can generate the schema for object with unbacked property") {
|
it("Can generate the schema for object with unbacked property") {
|
||||||
jsonSchemaTest<UnbakcedObject>("T0019__unbacked_object.json")
|
jsonSchemaTest<UnbackedObject>("T0019__unbacked_object.json")
|
||||||
}
|
}
|
||||||
it("Can generate the schema for object with SerialName annotation") {
|
it("Can generate the schema for object with SerialName annotation") {
|
||||||
jsonSchemaTest<SerialNameObject>("T0020__serial_name_object.json")
|
jsonSchemaTest<SerialNameObject>("T0020__serial_name_object.json")
|
||||||
@ -63,8 +65,12 @@ class SchemaGeneratorTest : DescribeSpec({
|
|||||||
jsonSchemaTest<SimpleEnum>("T0007__simple_enum.json")
|
jsonSchemaTest<SimpleEnum>("T0007__simple_enum.json")
|
||||||
}
|
}
|
||||||
it("Can generate the schema for a nullable enum") {
|
it("Can generate the schema for a nullable enum") {
|
||||||
|
// Same schema as a non-nullable enum, since the nullability will be handled on the property
|
||||||
jsonSchemaTest<SimpleEnum?>("T0008__nullable_enum.json")
|
jsonSchemaTest<SimpleEnum?>("T0008__nullable_enum.json")
|
||||||
}
|
}
|
||||||
|
it("Can generate the schema for an object with an enum property") {
|
||||||
|
jsonSchemaTest<ObjectWithEnum>("T0021__object_with_enum.json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Arrays") {
|
describe("Arrays") {
|
||||||
it("Can generate the schema for an array of scalars") {
|
it("Can generate the schema for an array of scalars") {
|
||||||
|
@ -1,23 +1,16 @@
|
|||||||
{
|
{
|
||||||
"oneOf": [
|
"type": "object",
|
||||||
{
|
"properties": {
|
||||||
"type": "null"
|
"a": {
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
{
|
"b": {
|
||||||
"type": "object",
|
"type": "number",
|
||||||
"properties": {
|
"format": "int32"
|
||||||
"a": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"b": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "int32"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"a",
|
|
||||||
"b"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"enum": [ "ONE", "TWO" ]
|
"enum": [ "ONE", "TWO" ],
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,4 @@
|
|||||||
{
|
{
|
||||||
"oneOf": [
|
"enum": [ "ONE", "TWO" ],
|
||||||
{
|
"type": "string"
|
||||||
"type": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"enum": [
|
|
||||||
"ONE",
|
|
||||||
"TWO"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
11
json-schema/src/test/resources/T0021__object_with_enum.json
Normal file
11
json-schema/src/test/resources/T0021__object_with_enum.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": {
|
||||||
|
"$ref": "#/components/schemas/Color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"color"
|
||||||
|
]
|
||||||
|
}
|
@ -22,8 +22,8 @@ dependencies {
|
|||||||
// IMPLEMENTATION
|
// IMPLEMENTATION
|
||||||
|
|
||||||
implementation(projects.kompendiumCore)
|
implementation(projects.kompendiumCore)
|
||||||
implementation("io.ktor:ktor-server-core:2.1.2")
|
implementation("io.ktor:ktor-server-core:2.1.3")
|
||||||
implementation("io.ktor:ktor-server-locations:2.1.2")
|
implementation("io.ktor:ktor-server-locations:2.1.3")
|
||||||
|
|
||||||
// TESTING
|
// TESTING
|
||||||
|
|
||||||
|
@ -51,17 +51,17 @@ object NotarizedLocations {
|
|||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
||||||
pluginConfig.locations.forEach { (k, v) ->
|
pluginConfig.locations.forEach { (k, v) ->
|
||||||
val path = Path()
|
|
||||||
path.parameters = v.parameters
|
|
||||||
v.get?.addToSpec(path, spec, v, serializableReader)
|
|
||||||
v.delete?.addToSpec(path, spec, v, serializableReader)
|
|
||||||
v.head?.addToSpec(path, spec, v, serializableReader)
|
|
||||||
v.options?.addToSpec(path, spec, v, serializableReader)
|
|
||||||
v.post?.addToSpec(path, spec, v, serializableReader)
|
|
||||||
v.put?.addToSpec(path, spec, v, serializableReader)
|
|
||||||
v.patch?.addToSpec(path, spec, v, serializableReader)
|
|
||||||
|
|
||||||
val location = k.getLocationFromClass()
|
val location = k.getLocationFromClass()
|
||||||
|
val path = spec.paths[location] ?: Path()
|
||||||
|
path.parameters = path.parameters?.plus(v.parameters) ?: v.parameters
|
||||||
|
v.get?.addToSpec(path, spec, v, serializableReader, location)
|
||||||
|
v.delete?.addToSpec(path, spec, v, serializableReader, location)
|
||||||
|
v.head?.addToSpec(path, spec, v, serializableReader, location)
|
||||||
|
v.options?.addToSpec(path, spec, v, serializableReader, location)
|
||||||
|
v.post?.addToSpec(path, spec, v, serializableReader, location)
|
||||||
|
v.put?.addToSpec(path, spec, v, serializableReader, location)
|
||||||
|
v.patch?.addToSpec(path, spec, v, serializableReader, location)
|
||||||
|
|
||||||
spec.paths[location] = path
|
spec.paths[location] = path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,5 +43,5 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||||
|
|
||||||
implementation("joda-time:joda-time:2.12.0")
|
implementation("joda-time:joda-time:2.12.1")
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ dependencies {
|
|||||||
// IMPLEMENTATION
|
// IMPLEMENTATION
|
||||||
|
|
||||||
implementation(projects.kompendiumCore)
|
implementation(projects.kompendiumCore)
|
||||||
implementation("io.ktor:ktor-server-core:2.1.2")
|
implementation("io.ktor:ktor-server-core:2.1.3")
|
||||||
implementation("io.ktor:ktor-server-resources:2.1.2")
|
implementation("io.ktor:ktor-server-resources:2.1.3")
|
||||||
|
|
||||||
// TESTING
|
// TESTING
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package io.bkbn.kompendium.resources
|
||||||
|
|
||||||
|
import io.ktor.resources.Resource
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.full.hasAnnotation
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
|
||||||
|
fun KClass<*>.getResourcePathFromClass(): String {
|
||||||
|
val resource = findAnnotation<Resource>()
|
||||||
|
?: error("Cannot notarize a resource without annotating with @Resource")
|
||||||
|
|
||||||
|
val path = resource.path
|
||||||
|
val parent = memberProperties.map { it.returnType.classifier as KClass<*> }.find { it.hasAnnotation<Resource>() }
|
||||||
|
|
||||||
|
return if (parent == null) {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
parent.getResourcePathFromClass() + path
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package io.bkbn.kompendium.resources
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute.addToSpec
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute.calculateRoutePath
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute.collectAuthMethods
|
||||||
|
import io.ktor.server.application.ApplicationCallPipeline
|
||||||
|
import io.ktor.server.application.Hook
|
||||||
|
import io.ktor.server.application.createRouteScopedPlugin
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
|
||||||
|
object NotarizedResource {
|
||||||
|
object InstallHook : Hook<(ApplicationCallPipeline) -> Unit> {
|
||||||
|
override fun install(pipeline: ApplicationCallPipeline, handler: (ApplicationCallPipeline) -> Unit) {
|
||||||
|
handler(pipeline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline operator fun <reified T> invoke() = createRouteScopedPlugin(
|
||||||
|
name = "NotarizedResource<${T::class.qualifiedName}>",
|
||||||
|
createConfiguration = NotarizedRoute::Config
|
||||||
|
) {
|
||||||
|
on(InstallHook) {
|
||||||
|
val route = it as? Route ?: return@on
|
||||||
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
|
val routePath = route.calculateRoutePath()
|
||||||
|
val authMethods = route.collectAuthMethods()
|
||||||
|
val resourcePath = T::class.getResourcePathFromClass()
|
||||||
|
val fullPath = "$routePath$resourcePath"
|
||||||
|
|
||||||
|
addToSpec(spec, fullPath, authMethods)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,12 +12,8 @@ import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
|||||||
import io.bkbn.kompendium.core.util.SpecConfig
|
import io.bkbn.kompendium.core.util.SpecConfig
|
||||||
import io.bkbn.kompendium.oas.path.Path
|
import io.bkbn.kompendium.oas.path.Path
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.ktor.resources.Resource
|
|
||||||
import io.ktor.server.application.createApplicationPlugin
|
import io.ktor.server.application.createApplicationPlugin
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.full.findAnnotation
|
|
||||||
import kotlin.reflect.full.hasAnnotation
|
|
||||||
import kotlin.reflect.full.memberProperties
|
|
||||||
|
|
||||||
object NotarizedResources {
|
object NotarizedResources {
|
||||||
|
|
||||||
@ -45,34 +41,18 @@ object NotarizedResources {
|
|||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
||||||
pluginConfig.resources.forEach { (k, v) ->
|
pluginConfig.resources.forEach { (k, v) ->
|
||||||
val path = Path()
|
val resource = k.getResourcePathFromClass()
|
||||||
path.parameters = v.parameters
|
val path = spec.paths[resource] ?: Path()
|
||||||
v.get?.addToSpec(path, spec, v, serializableReader)
|
path.parameters = path.parameters?.plus(v.parameters) ?: v.parameters
|
||||||
v.delete?.addToSpec(path, spec, v, serializableReader)
|
v.get?.addToSpec(path, spec, v, serializableReader, resource)
|
||||||
v.head?.addToSpec(path, spec, v, serializableReader)
|
v.delete?.addToSpec(path, spec, v, serializableReader, resource)
|
||||||
v.options?.addToSpec(path, spec, v, serializableReader)
|
v.head?.addToSpec(path, spec, v, serializableReader, resource)
|
||||||
v.post?.addToSpec(path, spec, v, serializableReader)
|
v.options?.addToSpec(path, spec, v, serializableReader, resource)
|
||||||
v.put?.addToSpec(path, spec, v, serializableReader)
|
v.post?.addToSpec(path, spec, v, serializableReader, resource)
|
||||||
v.patch?.addToSpec(path, spec, v, serializableReader)
|
v.put?.addToSpec(path, spec, v, serializableReader, resource)
|
||||||
|
v.patch?.addToSpec(path, spec, v, serializableReader, resource)
|
||||||
|
|
||||||
val resource = k.getResourcesFromClass()
|
|
||||||
spec.paths[resource] = path
|
spec.paths[resource] = path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KClass<*>.getResourcesFromClass(): String {
|
|
||||||
// todo if parent
|
|
||||||
|
|
||||||
val resource = findAnnotation<Resource>()
|
|
||||||
?: error("Cannot notarize a resource without annotating with @Resource")
|
|
||||||
|
|
||||||
val path = resource.path
|
|
||||||
val parent = memberProperties.map { it.returnType.classifier as KClass<*> }.find { it.hasAnnotation<Resource>() }
|
|
||||||
|
|
||||||
return if (parent == null) {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
parent.getResourcesFromClass() + path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,11 @@ import io.ktor.server.application.install
|
|||||||
import io.ktor.server.resources.Resources
|
import io.ktor.server.resources.Resources
|
||||||
import io.ktor.server.resources.get
|
import io.ktor.server.resources.get
|
||||||
import io.ktor.server.response.respondText
|
import io.ktor.server.response.respondText
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
class KompendiumResourcesTest : DescribeSpec({
|
class KompendiumResourcesTest : DescribeSpec({
|
||||||
describe("Resource Tests") {
|
describe("NotarizedResources Tests") {
|
||||||
it("Can notarize a simple resource") {
|
it("Can notarize a simple resource") {
|
||||||
openApiTestAllSerializers(
|
openApiTestAllSerializers(
|
||||||
snapshotName = "T0001__simple_resource.json",
|
snapshotName = "T0001__simple_resource.json",
|
||||||
@ -117,4 +119,72 @@ class KompendiumResourcesTest : DescribeSpec({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
describe("NotarizedResource Tests") {
|
||||||
|
it("Can notarize resources in route") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0003__resources_in_route.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Resources)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
route("/api") {
|
||||||
|
typeEditDocumentation()
|
||||||
|
get<Type.Edit> { edit ->
|
||||||
|
call.respondText("Listing ${edit.parent.name}")
|
||||||
|
}
|
||||||
|
typeOtherDocumentation()
|
||||||
|
get<Type.Other> { other ->
|
||||||
|
call.respondText("Listing ${other.parent.name}, page ${other.page}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
private fun Route.typeOtherDocumentation() {
|
||||||
|
install(NotarizedResource<Type.Other>()) {
|
||||||
|
parameters = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "name",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
),
|
||||||
|
Parameter(
|
||||||
|
name = "page",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.INT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Other")
|
||||||
|
description("example resource")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("does great things")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.typeEditDocumentation() {
|
||||||
|
install(NotarizedResource<Type.Edit>()) {
|
||||||
|
parameters = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "name",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Edit")
|
||||||
|
description("example resource")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("does great things")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
124
resources/src/test/resources/T0003__resources_in_route.json
Normal file
124
resources/src/test/resources/T0003__resources_in_route.json
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/api/type/{name}/edit": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Edit",
|
||||||
|
"description": "example resource",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "does great things",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/api/type/{name}/other/{page}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Other",
|
||||||
|
"description": "example resource",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "does great things",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
Reference in New Issue
Block a user