Merge branch 'main' of github.com:bkbnio/kompendium
This commit is contained in:
20
CHANGELOG.md
20
CHANGELOG.md
@ -12,6 +12,26 @@
|
|||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
|
## [4.0.0-alpha] - September 3rd, 2023
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for `type` on sealed interfaces
|
||||||
|
- Ability to provide custom serializers
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Exception thrown when inheriting variable
|
||||||
|
- Notarized routes not discarded on test completion
|
||||||
|
- Data classes with property members breaks schema generation
|
||||||
|
- Security cannot be applied to individual path operations
|
||||||
|
- Serialization fails on generic response
|
||||||
|
- Parameter example descriptions not being applied
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Out of the box support for Jackson and Gson (can still be implemented through custom schema configurators)
|
||||||
|
|
||||||
## [3.14.4] - June 5th, 2023
|
## [3.14.4] - June 5th, 2023
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -48,8 +48,6 @@ dependencies {
|
|||||||
testFixturesApi("io.ktor:ktor-server-core:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-core:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-server-test-host:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-test-host:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-serialization:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-serialization:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-serialization-jackson:$ktorVersion")
|
|
||||||
testFixturesApi("io.ktor:ktor-serialization-gson:$ktorVersion")
|
|
||||||
testFixturesApi("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-server-auth:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-auth:$ktorVersion")
|
||||||
|
@ -10,6 +10,7 @@ class DeleteInfo private constructor(
|
|||||||
override val summary: String,
|
override val summary: String,
|
||||||
override val description: String,
|
override val description: String,
|
||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>
|
||||||
@ -33,7 +34,8 @@ class DeleteInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ class GetInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?
|
||||||
) : MethodInfo {
|
) : MethodInfo {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -33,7 +34,8 @@ class GetInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ class HeadInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfo {
|
) : MethodInfo {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -33,7 +34,8 @@ class HeadInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,9 @@ sealed interface MethodInfo {
|
|||||||
val tags: Set<String>
|
val tags: Set<String>
|
||||||
val summary: String
|
val summary: String
|
||||||
val description: String
|
val description: String
|
||||||
|
|
||||||
|
val security: Map<String, List<String>>?
|
||||||
|
get() = null
|
||||||
val externalDocumentation: ExternalDocumentation?
|
val externalDocumentation: ExternalDocumentation?
|
||||||
get() = null
|
get() = null
|
||||||
val operationId: String?
|
val operationId: String?
|
||||||
@ -28,6 +31,7 @@ sealed interface MethodInfo {
|
|||||||
internal var tags: Set<String> = emptySet()
|
internal var tags: Set<String> = emptySet()
|
||||||
internal var parameters: List<Parameter> = emptyList()
|
internal var parameters: List<Parameter> = emptyList()
|
||||||
internal var errors: MutableList<ResponseInfo> = mutableListOf()
|
internal var errors: MutableList<ResponseInfo> = mutableListOf()
|
||||||
|
internal var security: Map<String, List<String>>? = null
|
||||||
|
|
||||||
fun response(init: ResponseInfo.Builder.() -> Unit) = apply {
|
fun response(init: ResponseInfo.Builder.() -> Unit) = apply {
|
||||||
val builder = ResponseInfo.Builder()
|
val builder = ResponseInfo.Builder()
|
||||||
@ -59,6 +63,8 @@ sealed interface MethodInfo {
|
|||||||
|
|
||||||
fun parameters(vararg parameters: Parameter) = apply { this.parameters = parameters.toList() }
|
fun parameters(vararg parameters: Parameter) = apply { this.parameters = parameters.toList() }
|
||||||
|
|
||||||
|
fun security(security: Map<String, List<String>>) = apply { this.security = security }
|
||||||
|
|
||||||
abstract fun build(): T
|
abstract fun build(): T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ class OptionsInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfo {
|
) : MethodInfo {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -33,7 +34,8 @@ class OptionsInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ class PatchInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfoWithRequest {
|
) : MethodInfoWithRequest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -35,7 +36,8 @@ class PatchInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ class PostInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfoWithRequest {
|
) : MethodInfoWithRequest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -35,7 +36,8 @@ class PostInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ class PutInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfoWithRequest {
|
) : MethodInfoWithRequest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -35,7 +36,8 @@ class PutInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,8 @@ class RequestInfo private constructor(
|
|||||||
|
|
||||||
fun description(s: String) = apply { this.description = s }
|
fun description(s: String) = apply { this.description = s }
|
||||||
|
|
||||||
fun examples(vararg e: Pair<String, Any>) = apply {
|
fun examples(vararg e: Pair<String, MediaType.Example>) = apply {
|
||||||
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
this.examples = e.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mediaTypes(vararg m: String) = apply {
|
fun mediaTypes(vararg m: String) = apply {
|
||||||
|
@ -57,8 +57,8 @@ class ResponseInfo private constructor(
|
|||||||
|
|
||||||
fun description(s: String) = apply { this.description = s }
|
fun description(s: String) = apply { this.description = s }
|
||||||
|
|
||||||
fun examples(vararg e: Pair<String, Any>) = apply {
|
fun examples(vararg e: Pair<String, MediaType.Example>) = apply {
|
||||||
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
this.examples = e.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mediaTypes(vararg m: String) = apply {
|
fun mediaTypes(vararg m: String) = apply {
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
package io.bkbn.kompendium.core.plugin
|
package io.bkbn.kompendium.core.plugin
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
||||||
|
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.application.createApplicationPlugin
|
import io.ktor.server.application.createApplicationPlugin
|
||||||
import io.ktor.server.response.respond
|
import io.ktor.server.response.respond
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.routing.application
|
|
||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
@ -19,25 +18,26 @@ import kotlin.reflect.KType
|
|||||||
object NotarizedApplication {
|
object NotarizedApplication {
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
lateinit var spec: OpenApiSpec
|
lateinit var spec: () -> OpenApiSpec
|
||||||
var openApiJson: Routing.() -> Unit = {
|
var specRoute: (OpenApiSpec, Routing) -> Unit = { spec, routing ->
|
||||||
route("/openapi.json") {
|
routing.route("/openapi.json") {
|
||||||
get {
|
get {
|
||||||
call.respond(HttpStatusCode.OK, this@route.application.attributes[KompendiumAttributes.openApiSpec])
|
call.respond(spec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var customTypes: Map<KType, JsonSchema> = emptyMap()
|
var customTypes: Map<KType, JsonSchema> = emptyMap()
|
||||||
var schemaConfigurator: SchemaConfigurator = SchemaConfigurator.Default()
|
var schemaConfigurator: SchemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke() = createApplicationPlugin(
|
operator fun invoke() = createApplicationPlugin(
|
||||||
name = "NotarizedApplication",
|
name = "NotarizedApplication",
|
||||||
createConfiguration = ::Config
|
createConfiguration = ::Config
|
||||||
) {
|
) {
|
||||||
val spec = pluginConfig.spec
|
val spec = pluginConfig.spec()
|
||||||
val routing = application.routing { }
|
val routing = application.routing {}
|
||||||
pluginConfig.openApiJson(routing)
|
this@createApplicationPlugin.pluginConfig.specRoute(spec, routing)
|
||||||
|
// pluginConfig.openApiJson(routing)
|
||||||
pluginConfig.customTypes.forEach { (type, schema) ->
|
pluginConfig.customTypes.forEach { (type, schema) ->
|
||||||
spec.components.schemas[type.getSimpleSlug()] = schema
|
spec.components.schemas[type.getSimpleSlug()] = schema
|
||||||
}
|
}
|
||||||
|
@ -118,10 +118,7 @@ object Helpers {
|
|||||||
operationId = this.operationId,
|
operationId = this.operationId,
|
||||||
deprecated = this.deprecated,
|
deprecated = this.deprecated,
|
||||||
parameters = this.parameters,
|
parameters = this.parameters,
|
||||||
security = config.security
|
security = this.createCombinedSecurityContext(config),
|
||||||
?.map { (k, v) -> k to v }
|
|
||||||
?.map { listOf(it).toMap() }
|
|
||||||
?.toMutableList(),
|
|
||||||
requestBody = when (this) {
|
requestBody = when (this) {
|
||||||
is MethodInfoWithRequest -> this.request?.let { reqInfo ->
|
is MethodInfoWithRequest -> this.request?.let { reqInfo ->
|
||||||
Request(
|
Request(
|
||||||
@ -150,6 +147,25 @@ object Helpers {
|
|||||||
).plus(this.errors.toResponseMap())
|
).plus(this.errors.toResponseMap())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun MethodInfo.createCombinedSecurityContext(config: SpecConfig): MutableList<Map<String, List<String>>>? {
|
||||||
|
val configSecurity = config.security
|
||||||
|
?.map { (k, v) -> k to v }
|
||||||
|
?.map { listOf(it).toMap() }
|
||||||
|
?.toMutableList()
|
||||||
|
|
||||||
|
val methodSecurity = this.security
|
||||||
|
?.map { (k, v) -> k to v }
|
||||||
|
?.map { listOf(it).toMap() }
|
||||||
|
?.toMutableList()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
configSecurity == null && methodSecurity == null -> null
|
||||||
|
configSecurity == null -> methodSecurity
|
||||||
|
methodSecurity == null -> configSecurity
|
||||||
|
else -> configSecurity.plus(methodSecurity).toMutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
@ -6,17 +6,20 @@ import io.bkbn.kompendium.core.util.arrayConstraints
|
|||||||
import io.bkbn.kompendium.core.util.complexRequest
|
import io.bkbn.kompendium.core.util.complexRequest
|
||||||
import io.bkbn.kompendium.core.util.customAuthConfig
|
import io.bkbn.kompendium.core.util.customAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.customFieldNameResponse
|
import io.bkbn.kompendium.core.util.customFieldNameResponse
|
||||||
|
import io.bkbn.kompendium.core.util.customScopesOnSiblingPathOperations
|
||||||
import io.bkbn.kompendium.core.util.dateTimeString
|
import io.bkbn.kompendium.core.util.dateTimeString
|
||||||
import io.bkbn.kompendium.core.util.defaultAuthConfig
|
import io.bkbn.kompendium.core.util.defaultAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.defaultField
|
import io.bkbn.kompendium.core.util.defaultField
|
||||||
import io.bkbn.kompendium.core.util.defaultParameter
|
import io.bkbn.kompendium.core.util.defaultParameter
|
||||||
import io.bkbn.kompendium.core.util.doubleConstraints
|
import io.bkbn.kompendium.core.util.doubleConstraints
|
||||||
import io.bkbn.kompendium.core.util.enrichedGenericResponse
|
|
||||||
import io.bkbn.kompendium.core.util.enrichedComplexGenericType
|
import io.bkbn.kompendium.core.util.enrichedComplexGenericType
|
||||||
|
import io.bkbn.kompendium.core.util.enrichedGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.enrichedNestedCollection
|
import io.bkbn.kompendium.core.util.enrichedNestedCollection
|
||||||
import io.bkbn.kompendium.core.util.enrichedSimpleRequest
|
import io.bkbn.kompendium.core.util.enrichedSimpleRequest
|
||||||
import io.bkbn.kompendium.core.util.enrichedSimpleResponse
|
import io.bkbn.kompendium.core.util.enrichedSimpleResponse
|
||||||
import io.bkbn.kompendium.core.util.exampleParams
|
import io.bkbn.kompendium.core.util.exampleParams
|
||||||
|
import io.bkbn.kompendium.core.util.exampleSummaryAndDescription
|
||||||
|
import io.bkbn.kompendium.core.util.fieldOutsideConstructor
|
||||||
import io.bkbn.kompendium.core.util.genericException
|
import io.bkbn.kompendium.core.util.genericException
|
||||||
import io.bkbn.kompendium.core.util.genericPolymorphicResponse
|
import io.bkbn.kompendium.core.util.genericPolymorphicResponse
|
||||||
import io.bkbn.kompendium.core.util.genericPolymorphicResponseMultipleImpls
|
import io.bkbn.kompendium.core.util.genericPolymorphicResponseMultipleImpls
|
||||||
@ -44,7 +47,10 @@ import io.bkbn.kompendium.core.util.nullableEnumField
|
|||||||
import io.bkbn.kompendium.core.util.nullableField
|
import io.bkbn.kompendium.core.util.nullableField
|
||||||
import io.bkbn.kompendium.core.util.nullableNestedObject
|
import io.bkbn.kompendium.core.util.nullableNestedObject
|
||||||
import io.bkbn.kompendium.core.util.nullableReference
|
import io.bkbn.kompendium.core.util.nullableReference
|
||||||
|
import io.bkbn.kompendium.core.util.optionalReqExample
|
||||||
import io.bkbn.kompendium.core.util.overrideMediaTypes
|
import io.bkbn.kompendium.core.util.overrideMediaTypes
|
||||||
|
import io.bkbn.kompendium.core.util.overrideSealedTypeIdentifier
|
||||||
|
import io.bkbn.kompendium.core.util.paramWrapper
|
||||||
import io.bkbn.kompendium.core.util.polymorphicCollectionResponse
|
import io.bkbn.kompendium.core.util.polymorphicCollectionResponse
|
||||||
import io.bkbn.kompendium.core.util.polymorphicException
|
import io.bkbn.kompendium.core.util.polymorphicException
|
||||||
import io.bkbn.kompendium.core.util.polymorphicMapResponse
|
import io.bkbn.kompendium.core.util.polymorphicMapResponse
|
||||||
@ -52,7 +58,6 @@ import io.bkbn.kompendium.core.util.polymorphicResponse
|
|||||||
import io.bkbn.kompendium.core.util.postNoReqBody
|
import io.bkbn.kompendium.core.util.postNoReqBody
|
||||||
import io.bkbn.kompendium.core.util.primitives
|
import io.bkbn.kompendium.core.util.primitives
|
||||||
import io.bkbn.kompendium.core.util.reqRespExamples
|
import io.bkbn.kompendium.core.util.reqRespExamples
|
||||||
import io.bkbn.kompendium.core.util.optionalReqExample
|
|
||||||
import io.bkbn.kompendium.core.util.requiredParams
|
import io.bkbn.kompendium.core.util.requiredParams
|
||||||
import io.bkbn.kompendium.core.util.responseHeaders
|
import io.bkbn.kompendium.core.util.responseHeaders
|
||||||
import io.bkbn.kompendium.core.util.returnsList
|
import io.bkbn.kompendium.core.util.returnsList
|
||||||
@ -66,9 +71,9 @@ import io.bkbn.kompendium.core.util.singleException
|
|||||||
import io.bkbn.kompendium.core.util.stringConstraints
|
import io.bkbn.kompendium.core.util.stringConstraints
|
||||||
import io.bkbn.kompendium.core.util.stringContentEncodingConstraints
|
import io.bkbn.kompendium.core.util.stringContentEncodingConstraints
|
||||||
import io.bkbn.kompendium.core.util.stringPatternConstraints
|
import io.bkbn.kompendium.core.util.stringPatternConstraints
|
||||||
|
import io.bkbn.kompendium.core.util.subtypeNotCompleteSetOfParentProperties
|
||||||
import io.bkbn.kompendium.core.util.topLevelNullable
|
import io.bkbn.kompendium.core.util.topLevelNullable
|
||||||
import io.bkbn.kompendium.core.util.trailingSlash
|
import io.bkbn.kompendium.core.util.trailingSlash
|
||||||
import io.bkbn.kompendium.core.util.paramWrapper
|
|
||||||
import io.bkbn.kompendium.core.util.unbackedFieldsResponse
|
import io.bkbn.kompendium.core.util.unbackedFieldsResponse
|
||||||
import io.bkbn.kompendium.core.util.withOperationId
|
import io.bkbn.kompendium.core.util.withOperationId
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
@ -78,6 +83,7 @@ import io.bkbn.kompendium.oas.security.ApiKeyAuth
|
|||||||
import io.bkbn.kompendium.oas.security.BasicAuth
|
import io.bkbn.kompendium.oas.security.BasicAuth
|
||||||
import io.bkbn.kompendium.oas.security.BearerAuth
|
import io.bkbn.kompendium.oas.security.BearerAuth
|
||||||
import io.bkbn.kompendium.oas.security.OAuth
|
import io.bkbn.kompendium.oas.security.OAuth
|
||||||
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
import io.kotest.assertions.throwables.shouldThrow
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
import io.kotest.core.spec.style.DescribeSpec
|
import io.kotest.core.spec.style.DescribeSpec
|
||||||
import io.kotest.matchers.should
|
import io.kotest.matchers.should
|
||||||
@ -85,6 +91,8 @@ import io.kotest.matchers.string.startWith
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.cio.CIO
|
import io.ktor.client.engine.cio.CIO
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.auth.Authentication
|
import io.ktor.server.auth.Authentication
|
||||||
import io.ktor.server.auth.OAuthServerSettings
|
import io.ktor.server.auth.OAuthServerSettings
|
||||||
@ -92,6 +100,11 @@ 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 io.ktor.server.response.respondText
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
@ -182,6 +195,9 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can generate example optional request body") {
|
it("Can generate example optional request body") {
|
||||||
openApiTestAllSerializers("T0069__example_optional_req.json") { optionalReqExample() }
|
openApiTestAllSerializers("T0069__example_optional_req.json") { optionalReqExample() }
|
||||||
}
|
}
|
||||||
|
it("Can generate example summary and description") {
|
||||||
|
openApiTestAllSerializers("T0075__example_summary_and_description.json") { exampleSummaryAndDescription() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Defaults") {
|
describe("Defaults") {
|
||||||
it("Can generate a default parameter value") {
|
it("Can generate a default parameter value") {
|
||||||
@ -235,97 +251,62 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can handle a really gnarly generic example") {
|
it("Can handle a really gnarly generic example") {
|
||||||
openApiTestAllSerializers("T0043__gnarly_generic_example.json") { gnarlyGenericResponse() }
|
openApiTestAllSerializers("T0043__gnarly_generic_example.json") { gnarlyGenericResponse() }
|
||||||
}
|
}
|
||||||
}
|
it("Can override the type name for a sealed interface implementation") {
|
||||||
describe("Custom Serializable Reader tests") {
|
openApiTestAllSerializers("T0070__sealed_interface_type_name_override.json") { overrideSealedTypeIdentifier() }
|
||||||
it("Can support ignoring fields") {
|
|
||||||
openApiTestAllSerializers("T0048__ignored_property.json") { ignoredFieldsResponse() }
|
|
||||||
}
|
}
|
||||||
it("Can support un-backed fields") {
|
it("Can serialize an object where the subtype is not a complete set of parent properties") {
|
||||||
openApiTestAllSerializers("T0049__unbacked_property.json") { unbackedFieldsResponse() }
|
openApiTestAllSerializers("T0071__subtype_not_complete_set_of_parent_properties.json") {
|
||||||
|
subtypeNotCompleteSetOfParentProperties()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
it("Can support custom named fields") {
|
describe("Custom Serializable Reader tests") {
|
||||||
openApiTestAllSerializers("T0050__custom_named_property.json") { customFieldNameResponse() }
|
it("Can support ignoring fields") {
|
||||||
|
openApiTestAllSerializers("T0048__ignored_property.json") { ignoredFieldsResponse() }
|
||||||
|
}
|
||||||
|
it("Can support un-backed fields") {
|
||||||
|
openApiTestAllSerializers("T0049__unbacked_property.json") { unbackedFieldsResponse() }
|
||||||
|
}
|
||||||
|
it("Can support custom named fields") {
|
||||||
|
openApiTestAllSerializers("T0050__custom_named_property.json") { customFieldNameResponse() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
describe("Miscellaneous") {
|
||||||
describe("Miscellaneous") {
|
xit("Can generate the necessary ReDoc home page") {
|
||||||
xit("Can generate the necessary ReDoc home page") {
|
// TODO apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
|
||||||
// TODO apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
|
}
|
||||||
}
|
it("Can add an operation id to a notarized route") {
|
||||||
it("Can add an operation id to a notarized route") {
|
openApiTestAllSerializers("T0034__notarized_get_with_operation_id.json") { withOperationId() }
|
||||||
openApiTestAllSerializers("T0034__notarized_get_with_operation_id.json") { withOperationId() }
|
}
|
||||||
}
|
xit("Can add an undeclared field") {
|
||||||
xit("Can add an undeclared field") {
|
// TODO openApiTestAllSerializers("undeclared_field.json") { undeclaredType() }
|
||||||
// TODO openApiTestAllSerializers("undeclared_field.json") { undeclaredType() }
|
}
|
||||||
}
|
it("Can add a custom header parameter with a name override") {
|
||||||
it("Can add a custom header parameter with a name override") {
|
openApiTestAllSerializers("T0035__override_parameter_name.json") { headerParameter() }
|
||||||
openApiTestAllSerializers("T0035__override_parameter_name.json") { headerParameter() }
|
}
|
||||||
}
|
xit("Can override field name") {
|
||||||
xit("Can override field name") {
|
// TODO Assess strategies here
|
||||||
// TODO Assess strategies here
|
}
|
||||||
}
|
it("Can serialize a recursive type") {
|
||||||
it("Can serialize a recursive type") {
|
openApiTestAllSerializers("T0042__simple_recursive.json") { simpleRecursive() }
|
||||||
openApiTestAllSerializers("T0042__simple_recursive.json") { simpleRecursive() }
|
}
|
||||||
}
|
it("Nullable fields do not lead to doom") {
|
||||||
it("Nullable fields do not lead to doom") {
|
openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() }
|
||||||
openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() }
|
}
|
||||||
}
|
it("Can have a nullable enum as a member field") {
|
||||||
it("Can have a nullable enum as a member field") {
|
openApiTestAllSerializers("T0037__nullable_enum_field.json") { nullableEnumField() }
|
||||||
openApiTestAllSerializers("T0037__nullable_enum_field.json") { nullableEnumField() }
|
}
|
||||||
}
|
it("Can have a nullable reference without impacting base type") {
|
||||||
it("Can have a nullable reference without impacting base type") {
|
openApiTestAllSerializers("T0041__nullable_reference.json") { nullableReference() }
|
||||||
openApiTestAllSerializers("T0041__nullable_reference.json") { nullableReference() }
|
}
|
||||||
}
|
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") {
|
||||||
it("Can handle top level nullable types") {
|
openApiTestAllSerializers("T0051__top_level_nullable.json") { topLevelNullable() }
|
||||||
openApiTestAllSerializers("T0051__top_level_nullable.json") { topLevelNullable() }
|
}
|
||||||
}
|
it("Can handle multiple registrations for different methods with the same path and different auth") {
|
||||||
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() }
|
|
||||||
}
|
|
||||||
it("Can generate paths without application root-path") {
|
|
||||||
openApiTestAllSerializers(
|
|
||||||
"T0054__app_with_rootpath.json",
|
|
||||||
applicationEnvironmentBuilder = {
|
|
||||||
rootPath = "/example"
|
|
||||||
},
|
|
||||||
specOverrides = {
|
|
||||||
copy(
|
|
||||||
servers = servers.map { it.copy(url = URI("${it.url}/example")) }.toMutableList()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { notarizedGet() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
describe("Error Handling") {
|
|
||||||
it("Throws a clear exception when an unidentified type is encountered") {
|
|
||||||
val exception = shouldThrow<UnknownSchemaException> { openApiTestAllSerializers("") { dateTimeString() } }
|
|
||||||
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(
|
openApiTestAllSerializers(
|
||||||
snapshotName = "",
|
"T0053__same_path_different_methods_and_auth.json",
|
||||||
applicationSetup = {
|
applicationSetup = {
|
||||||
install(Authentication) {
|
install(Authentication) {
|
||||||
basic("basic") {
|
basic("basic") {
|
||||||
@ -334,157 +315,277 @@ class KompendiumTest : DescribeSpec({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
specOverrides = {
|
||||||
samePathSameMethod()
|
this.copy(
|
||||||
}
|
components = Components(
|
||||||
}
|
securitySchemes = mutableMapOf(
|
||||||
exception.message should startWith("A route has already been registered for path: /test/{a} and method: GET")
|
"basic" to BasicAuth()
|
||||||
}
|
)
|
||||||
}
|
|
||||||
describe("Formats") {
|
|
||||||
it("Can set a format for a simple type schema") {
|
|
||||||
openApiTestAllSerializers(
|
|
||||||
snapshotName = "T0038__formatted_date_time_string.json",
|
|
||||||
customTypes = mapOf(typeOf<Instant>() to TypeDefinition(type = "string", format = "date"))
|
|
||||||
) { dateTimeString() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
describe("Free Form") {
|
|
||||||
// todo Assess strategies here
|
|
||||||
}
|
|
||||||
describe("Authentication") {
|
|
||||||
it("Can add a default auth config by default") {
|
|
||||||
openApiTestAllSerializers(
|
|
||||||
snapshotName = "T0045__default_auth_config.json",
|
|
||||||
applicationSetup = {
|
|
||||||
install(Authentication) {
|
|
||||||
basic("basic") {
|
|
||||||
realm = "Ktor Server"
|
|
||||||
validate { UserIdPrincipal("Placeholder") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
specOverrides = {
|
|
||||||
this.copy(
|
|
||||||
components = Components(
|
|
||||||
securitySchemes = mutableMapOf(
|
|
||||||
"basic" to BasicAuth()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
) { defaultAuthConfig() }
|
|
||||||
}
|
|
||||||
it("Can provide custom auth config with proper scopes") {
|
|
||||||
openApiTestAllSerializers(
|
|
||||||
snapshotName = "T0046__custom_auth_config.json",
|
|
||||||
applicationSetup = {
|
|
||||||
install(Authentication) {
|
|
||||||
oauth("auth-oauth-google") {
|
|
||||||
urlProvider = { "http://localhost:8080/callback" }
|
|
||||||
providerLookup = {
|
|
||||||
OAuthServerSettings.OAuth2ServerSettings(
|
|
||||||
name = "google",
|
|
||||||
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
|
|
||||||
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
|
|
||||||
requestMethod = HttpMethod.Post,
|
|
||||||
clientId = "DUMMY_VAL",
|
|
||||||
clientSecret = "DUMMY_VAL",
|
|
||||||
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
|
|
||||||
extraTokenParameters = listOf("access_type" to "offline")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
client = HttpClient(CIO)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
) { samePathDifferentMethodsAndAuth() }
|
||||||
specOverrides = {
|
}
|
||||||
this.copy(
|
it("Can generate paths without application root-path") {
|
||||||
components = Components(
|
openApiTestAllSerializers(
|
||||||
securitySchemes = mutableMapOf(
|
"T0054__app_with_rootpath.json",
|
||||||
"auth-oauth-google" to OAuth(
|
applicationEnvironmentBuilder = {
|
||||||
flows = OAuth.Flows(
|
rootPath = "/example"
|
||||||
implicit = OAuth.Flows.Implicit(
|
},
|
||||||
authorizationUrl = "https://accounts.google.com/o/oauth2/auth",
|
specOverrides = {
|
||||||
scopes = mapOf(
|
copy(
|
||||||
"write:pets" to "modify pets in your account",
|
servers = servers.map { it.copy(url = URI("${it.url}/example")) }.toMutableList()
|
||||||
"read:pets" to "read your pets"
|
)
|
||||||
|
}
|
||||||
|
) { notarizedGet() }
|
||||||
|
}
|
||||||
|
it("Can apply a custom serialization strategy to the openapi document") {
|
||||||
|
val customJsonEncoder = Json {
|
||||||
|
serializersModule = KompendiumSerializersModule.module
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
}
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0072__custom_serialization_strategy.json",
|
||||||
|
notarizedApplicationConfigOverrides = {
|
||||||
|
specRoute = { spec, routing ->
|
||||||
|
routing {
|
||||||
|
route("/openapi.json") {
|
||||||
|
get {
|
||||||
|
call.response.headers.append("Content-Type", "application/json")
|
||||||
|
call.respondText { customJsonEncoder.encodeToString(spec) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentNegotiation = {
|
||||||
|
json(
|
||||||
|
Json {
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { notarizedGet() }
|
||||||
|
}
|
||||||
|
it("Can serialize a data class with a field outside of the constructor") {
|
||||||
|
openApiTestAllSerializers("T0073__data_class_with_field_outside_constructor.json") { fieldOutsideConstructor() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Error Handling") {
|
||||||
|
it("Throws a clear exception when an unidentified type is encountered") {
|
||||||
|
val exception = shouldThrow<UnknownSchemaException> { openApiTestAllSerializers("") { dateTimeString() } }
|
||||||
|
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(
|
||||||
|
snapshotName = "",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
basic("basic") {
|
||||||
|
realm = "Ktor Server"
|
||||||
|
validate { UserIdPrincipal("Placeholder") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
samePathSameMethod()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exception.message should startWith("A route has already been registered for path: /test/{a} and method: GET")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Formats") {
|
||||||
|
it("Can set a format for a simple type schema") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0038__formatted_date_time_string.json",
|
||||||
|
customTypes = mapOf(typeOf<Instant>() to TypeDefinition(type = "string", format = "date"))
|
||||||
|
) { dateTimeString() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Free Form") {
|
||||||
|
// todo Assess strategies here
|
||||||
|
}
|
||||||
|
describe("Authentication") {
|
||||||
|
it("Can add a default auth config by default") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0045__default_auth_config.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
basic("basic") {
|
||||||
|
realm = "Ktor Server"
|
||||||
|
validate { UserIdPrincipal("Placeholder") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"basic" to BasicAuth()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { defaultAuthConfig() }
|
||||||
|
}
|
||||||
|
it("Can provide custom auth config with proper scopes") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0046__custom_auth_config.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
oauth("auth-oauth-google") {
|
||||||
|
urlProvider = { "http://localhost:8080/callback" }
|
||||||
|
providerLookup = {
|
||||||
|
OAuthServerSettings.OAuth2ServerSettings(
|
||||||
|
name = "google",
|
||||||
|
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
|
||||||
|
requestMethod = HttpMethod.Post,
|
||||||
|
clientId = "DUMMY_VAL",
|
||||||
|
clientSecret = "DUMMY_VAL",
|
||||||
|
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
|
||||||
|
extraTokenParameters = listOf("access_type" to "offline")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
client = HttpClient(CIO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"auth-oauth-google" to OAuth(
|
||||||
|
flows = OAuth.Flows(
|
||||||
|
implicit = OAuth.Flows.Implicit(
|
||||||
|
authorizationUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
scopes = mapOf(
|
||||||
|
"write:pets" to "modify pets in your account",
|
||||||
|
"read:pets" to "read your pets"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
) { customAuthConfig() }
|
||||||
) { customAuthConfig() }
|
}
|
||||||
}
|
it("Can provide multiple authentication strategies") {
|
||||||
it("Can provide multiple authentication strategies") {
|
openApiTestAllSerializers(
|
||||||
openApiTestAllSerializers(
|
snapshotName = "T0047__multiple_auth_strategies.json",
|
||||||
snapshotName = "T0047__multiple_auth_strategies.json",
|
applicationSetup = {
|
||||||
applicationSetup = {
|
install(Authentication) {
|
||||||
install(Authentication) {
|
apiKey("api-key") {
|
||||||
apiKey("api-key") {
|
headerName = "X-API-KEY"
|
||||||
headerName = "X-API-KEY"
|
validate {
|
||||||
validate {
|
UserIdPrincipal("Placeholder")
|
||||||
UserIdPrincipal("Placeholder")
|
}
|
||||||
|
}
|
||||||
|
jwt("jwt") {
|
||||||
|
realm = "Server"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jwt("jwt") {
|
},
|
||||||
realm = "Server"
|
specOverrides = {
|
||||||
}
|
this.copy(
|
||||||
}
|
components = Components(
|
||||||
},
|
securitySchemes = mutableMapOf(
|
||||||
specOverrides = {
|
"jwt" to BearerAuth("JWT"),
|
||||||
this.copy(
|
"api-key" to ApiKeyAuth(ApiKeyAuth.ApiKeyLocation.HEADER, "X-API-KEY")
|
||||||
components = Components(
|
)
|
||||||
securitySchemes = mutableMapOf(
|
|
||||||
"jwt" to BearerAuth("JWT"),
|
|
||||||
"api-key" to ApiKeyAuth(ApiKeyAuth.ApiKeyLocation.HEADER, "X-API-KEY")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
) { multipleAuthStrategies() }
|
||||||
) { multipleAuthStrategies() }
|
}
|
||||||
}
|
it("Can provide different scopes on path operations in the same route") {
|
||||||
}
|
openApiTestAllSerializers(
|
||||||
describe("Enrichment") {
|
snapshotName = "T0074__auth_on_specific_path_operation.json",
|
||||||
it("Can enrich a simple request") {
|
applicationSetup = {
|
||||||
openApiTestAllSerializers("T0055__enriched_simple_request.json") { enrichedSimpleRequest() }
|
install(Authentication) {
|
||||||
}
|
oauth("auth-oauth-google") {
|
||||||
it("Can enrich a simple response") {
|
urlProvider = { "http://localhost:8080/callback" }
|
||||||
openApiTestAllSerializers("T0058__enriched_simple_response.json") { enrichedSimpleResponse() }
|
providerLookup = {
|
||||||
}
|
OAuthServerSettings.OAuth2ServerSettings(
|
||||||
it("Can enrich a nested collection") {
|
name = "google",
|
||||||
openApiTestAllSerializers("T0056__enriched_nested_collection.json") { enrichedNestedCollection() }
|
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
}
|
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
|
||||||
it("Can enrich a complex generic type") {
|
requestMethod = HttpMethod.Post,
|
||||||
openApiTestAllSerializers("T0057__enriched_complex_generic_type.json") { enrichedComplexGenericType() }
|
clientId = "DUMMY_VAL",
|
||||||
}
|
clientSecret = "DUMMY_VAL",
|
||||||
it("Can enrich a generic object") {
|
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
|
||||||
openApiTestAllSerializers("T0067__enriched_generic_object.json") { enrichedGenericResponse() }
|
extraTokenParameters = listOf("access_type" to "offline")
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
describe("Constraints") {
|
client = HttpClient(CIO)
|
||||||
it("Can apply constraints to an int field") {
|
}
|
||||||
openApiTestAllSerializers("T0059__int_constraints.json") { intConstraints() }
|
}
|
||||||
}
|
},
|
||||||
it("Can apply constraints to a double field") {
|
specOverrides = {
|
||||||
openApiTestAllSerializers("T0060__double_constraints.json") { doubleConstraints() }
|
this.copy(
|
||||||
}
|
components = Components(
|
||||||
it("Can apply a min and max length to a string field") {
|
securitySchemes = mutableMapOf(
|
||||||
openApiTestAllSerializers("T0061__string_min_max_constraints.json") { stringConstraints() }
|
"auth-oauth-google" to OAuth(
|
||||||
}
|
flows = OAuth.Flows(
|
||||||
it("Can apply a pattern to a string field") {
|
implicit = OAuth.Flows.Implicit(
|
||||||
openApiTestAllSerializers("T0062__string_pattern_constraints.json") { stringPatternConstraints() }
|
authorizationUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
}
|
scopes = mapOf(
|
||||||
it("Can apply a content encoding and media type to a string field") {
|
"write:pets" to "modify pets in your account",
|
||||||
openApiTestAllSerializers("T0063__string_content_encoding_constraints.json") {
|
"read:pets" to "read your pets"
|
||||||
stringContentEncodingConstraints()
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { customScopesOnSiblingPathOperations() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it("Can apply constraints to an array field") {
|
describe("Enrichment") {
|
||||||
openApiTestAllSerializers("T0064__array_constraints.json") { arrayConstraints() }
|
it("Can enrich a simple request") {
|
||||||
|
openApiTestAllSerializers("T0055__enriched_simple_request.json") { enrichedSimpleRequest() }
|
||||||
|
}
|
||||||
|
it("Can enrich a simple response") {
|
||||||
|
openApiTestAllSerializers("T0058__enriched_simple_response.json") { enrichedSimpleResponse() }
|
||||||
|
}
|
||||||
|
it("Can enrich a nested collection") {
|
||||||
|
openApiTestAllSerializers("T0056__enriched_nested_collection.json") { enrichedNestedCollection() }
|
||||||
|
}
|
||||||
|
it("Can enrich a complex generic type") {
|
||||||
|
openApiTestAllSerializers("T0057__enriched_complex_generic_type.json") { enrichedComplexGenericType() }
|
||||||
|
}
|
||||||
|
it("Can enrich a generic object") {
|
||||||
|
openApiTestAllSerializers("T0067__enriched_generic_object.json") { enrichedGenericResponse() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Constraints") {
|
||||||
|
it("Can apply constraints to an int field") {
|
||||||
|
openApiTestAllSerializers("T0059__int_constraints.json") { intConstraints() }
|
||||||
|
}
|
||||||
|
it("Can apply constraints to a double field") {
|
||||||
|
openApiTestAllSerializers("T0060__double_constraints.json") { doubleConstraints() }
|
||||||
|
}
|
||||||
|
it("Can apply a min and max length to a string field") {
|
||||||
|
openApiTestAllSerializers("T0061__string_min_max_constraints.json") { stringConstraints() }
|
||||||
|
}
|
||||||
|
it("Can apply a pattern to a string field") {
|
||||||
|
openApiTestAllSerializers("T0062__string_pattern_constraints.json") { stringPatternConstraints() }
|
||||||
|
}
|
||||||
|
it("Can apply a content encoding and media type to a string field") {
|
||||||
|
openApiTestAllSerializers("T0063__string_content_encoding_constraints.json") {
|
||||||
|
stringContentEncodingConstraints()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it("Can apply constraints to an array field") {
|
||||||
|
openApiTestAllSerializers("T0064__array_constraints.json") { arrayConstraints() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ package io.bkbn.kompendium.core.util
|
|||||||
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestResponse
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
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.defaultPathDescription
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
@ -11,6 +12,7 @@ import io.ktor.http.HttpStatusCode
|
|||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.auth.authenticate
|
import io.ktor.server.auth.authenticate
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.post
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
fun Routing.defaultAuthConfig() {
|
fun Routing.defaultAuthConfig() {
|
||||||
@ -42,6 +44,43 @@ fun Routing.customAuthConfig() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Routing.customScopesOnSiblingPathOperations() {
|
||||||
|
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")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
request {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
requestType<TestResponse>()
|
||||||
|
}
|
||||||
|
security = mapOf(
|
||||||
|
"auth-oauth-google" to listOf("write:pets")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Routing.multipleAuthStrategies() {
|
fun Routing.multipleAuthStrategies() {
|
||||||
authenticate("jwt", "api-key") {
|
authenticate("jwt", "api-key") {
|
||||||
route(rootPath) {
|
route(rootPath) {
|
||||||
|
@ -11,6 +11,7 @@ import io.bkbn.kompendium.core.util.TestModules.defaultRequestDescription
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
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.MediaType
|
||||||
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.install
|
import io.ktor.server.application.install
|
||||||
@ -27,7 +28,7 @@ fun Routing.reqRespExamples() {
|
|||||||
description(defaultRequestDescription)
|
description(defaultRequestDescription)
|
||||||
requestType<TestRequest>()
|
requestType<TestRequest>()
|
||||||
examples(
|
examples(
|
||||||
"Testerina" to TestRequest(TestNested("asdf"), 1.5, emptyList())
|
"Testerina" to MediaType.Example(TestRequest(TestNested("asdf"), 1.5, emptyList()))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
response {
|
response {
|
||||||
@ -35,7 +36,7 @@ fun Routing.reqRespExamples() {
|
|||||||
responseCode(HttpStatusCode.OK)
|
responseCode(HttpStatusCode.OK)
|
||||||
responseType<TestResponse>()
|
responseType<TestResponse>()
|
||||||
examples(
|
examples(
|
||||||
"Testerino" to TestResponse("Heya")
|
"Testerino" to MediaType.Example(TestResponse("Heya"))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +51,7 @@ fun Routing.exampleParams() = basicGetGenerator<TestResponse>(
|
|||||||
`in` = Parameter.Location.path,
|
`in` = Parameter.Location.path,
|
||||||
schema = TypeDefinition.STRING,
|
schema = TypeDefinition.STRING,
|
||||||
examples = mapOf(
|
examples = mapOf(
|
||||||
"foo" to Parameter.Example("testing")
|
"foo" to MediaType.Example("testing")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -66,7 +67,7 @@ fun Routing.optionalReqExample() {
|
|||||||
description(defaultRequestDescription)
|
description(defaultRequestDescription)
|
||||||
requestType<TestRequest>()
|
requestType<TestRequest>()
|
||||||
examples(
|
examples(
|
||||||
"Testerina" to TestRequest(TestNested("asdf"), 1.5, emptyList())
|
"Testerina" to MediaType.Example(TestRequest(TestNested("asdf"), 1.5, emptyList()))
|
||||||
)
|
)
|
||||||
required(false)
|
required(false)
|
||||||
}
|
}
|
||||||
@ -75,7 +76,37 @@ fun Routing.optionalReqExample() {
|
|||||||
responseCode(HttpStatusCode.OK)
|
responseCode(HttpStatusCode.OK)
|
||||||
responseType<TestResponse>()
|
responseType<TestResponse>()
|
||||||
examples(
|
examples(
|
||||||
"Testerino" to TestResponse("Heya")
|
"Testerino" to MediaType.Example(TestResponse("Heya"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.exampleSummaryAndDescription() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary("This is a summary")
|
||||||
|
description("This is a description")
|
||||||
|
request {
|
||||||
|
description("This is a request description")
|
||||||
|
requestType<TestRequest>()
|
||||||
|
examples(
|
||||||
|
"Testerina" to MediaType.Example(
|
||||||
|
TestRequest(TestNested("asdf"), 1.5, emptyList()),
|
||||||
|
"summary",
|
||||||
|
"description"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
description("This is a response description")
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
examples(
|
||||||
|
"Testerino" to MediaType.Example(TestResponse("Heya"), "summary", "description")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.util
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.SomethingSimilar
|
||||||
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
||||||
import io.bkbn.kompendium.core.fixtures.TestRequest
|
import io.bkbn.kompendium.core.fixtures.TestRequest
|
||||||
import io.bkbn.kompendium.core.fixtures.TestResponse
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
@ -349,3 +350,23 @@ fun Routing.postNoReqBody() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Routing.fieldOutsideConstructor() {
|
||||||
|
route("/field_outside_constructor") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<SomethingSimilar>()
|
||||||
|
description("A cool request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("Cool response")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
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.Barzo
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ChillaxificationMaximization
|
||||||
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
import io.bkbn.kompendium.core.fixtures.Flibbity
|
import io.bkbn.kompendium.core.fixtures.Flibbity
|
||||||
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
||||||
import io.bkbn.kompendium.core.fixtures.Foosy
|
import io.bkbn.kompendium.core.fixtures.Foosy
|
||||||
import io.bkbn.kompendium.core.fixtures.Gibbity
|
import io.bkbn.kompendium.core.fixtures.Gibbity
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Gizmo
|
||||||
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
|
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
|
||||||
import io.bkbn.kompendium.core.fixtures.Page
|
import io.bkbn.kompendium.core.fixtures.Page
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
@ -20,3 +22,5 @@ fun Routing.genericPolymorphicResponse() = basicGetGenerator<Flibbity<Double>>()
|
|||||||
fun Routing.genericPolymorphicResponseMultipleImpls() = basicGetGenerator<Flibbity<FlibbityGibbit>>()
|
fun Routing.genericPolymorphicResponseMultipleImpls() = basicGetGenerator<Flibbity<FlibbityGibbit>>()
|
||||||
fun Routing.nestedGenericCollection() = basicGetGenerator<Page<Int>>()
|
fun Routing.nestedGenericCollection() = basicGetGenerator<Page<Int>>()
|
||||||
fun Routing.nestedGenericMultipleParamsCollection() = basicGetGenerator<MultiNestedGenerics<String, ComplexRequest>>()
|
fun Routing.nestedGenericMultipleParamsCollection() = basicGetGenerator<MultiNestedGenerics<String, ComplexRequest>>()
|
||||||
|
fun Routing.overrideSealedTypeIdentifier() = basicGetGenerator<ChillaxificationMaximization>()
|
||||||
|
fun Routing.subtypeNotCompleteSetOfParentProperties() = basicGetGenerator<Gizmo>()
|
||||||
|
@ -86,12 +86,19 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"c",
|
"c",
|
||||||
"z"
|
"z",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SimpleGibbit": {
|
"SimpleGibbit": {
|
||||||
@ -102,10 +109,17 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"FlibbityGibbit": {
|
"FlibbityGibbit": {
|
||||||
|
@ -82,11 +82,18 @@
|
|||||||
},
|
},
|
||||||
"f": {
|
"f": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Bibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"f"
|
"f",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Gibbity-String": {
|
"Gibbity-String": {
|
||||||
@ -94,10 +101,17 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"a": {
|
"a": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Gibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Flibbity-String": {
|
"Flibbity-String": {
|
||||||
|
@ -65,12 +65,19 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"c",
|
"c",
|
||||||
"z"
|
"z",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SimpleGibbit": {
|
"SimpleGibbit": {
|
||||||
@ -81,10 +88,17 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"FlibbityGibbit": {
|
"FlibbityGibbit": {
|
||||||
|
@ -65,12 +65,19 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"c",
|
"c",
|
||||||
"z"
|
"z",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SimpleGibbit": {
|
"SimpleGibbit": {
|
||||||
@ -81,10 +88,17 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"List-FlibbityGibbit": {
|
"List-FlibbityGibbit": {
|
||||||
|
@ -65,12 +65,19 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"c",
|
"c",
|
||||||
"z"
|
"z",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SimpleGibbit": {
|
"SimpleGibbit": {
|
||||||
@ -81,10 +88,17 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Map-String-FlibbityGibbit": {
|
"Map-String-FlibbityGibbit": {
|
||||||
|
@ -62,11 +62,18 @@
|
|||||||
"f": {
|
"f": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "double"
|
"format": "double"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Bibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"f"
|
"f",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Gibbity-Double": {
|
"Gibbity-Double": {
|
||||||
@ -75,10 +82,17 @@
|
|||||||
"a": {
|
"a": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "double"
|
"format": "double"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Gibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Flibbity-Double": {
|
"Flibbity-Double": {
|
||||||
|
@ -53,40 +53,6 @@
|
|||||||
"webhooks": {},
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"ComplexGibbit": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"b": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"c": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"z": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"b",
|
|
||||||
"c",
|
|
||||||
"z"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"SimpleGibbit": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"a": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"z": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"a"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Bibbity-FlibbityGibbit": {
|
"Bibbity-FlibbityGibbit": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -102,11 +68,66 @@
|
|||||||
"$ref": "#/components/schemas/SimpleGibbit"
|
"$ref": "#/components/schemas/SimpleGibbit"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Bibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"f"
|
"f",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ComplexGibbit": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"b": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"z": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"b",
|
||||||
|
"c",
|
||||||
|
"z",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SimpleGibbit": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"z": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Gibbity-FlibbityGibbit": {
|
"Gibbity-FlibbityGibbit": {
|
||||||
@ -121,10 +142,17 @@
|
|||||||
"$ref": "#/components/schemas/SimpleGibbit"
|
"$ref": "#/components/schemas/SimpleGibbit"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Gibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Flibbity-FlibbityGibbit": {
|
"Flibbity-FlibbityGibbit": {
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"$ref": "#/components/schemas/ChillaxificationMaximization"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Chillax": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"chillax"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ToDaMax": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"maximize"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"b",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ChillaxificationMaximization": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Chillax"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/ToDaMax"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"$ref": "#/components/schemas/Gizmo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Gizmo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/field_outside_constructor": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A cool request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SomethingSimilar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Cool response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SomethingSimilar": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"auth-oauth-google": [
|
||||||
|
"read:pets"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"auth-oauth-google": [
|
||||||
|
"write:pets"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
|
"auth-oauth-google": {
|
||||||
|
"flows": {
|
||||||
|
"implicit": {
|
||||||
|
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"scopes": {
|
||||||
|
"write:pets": "modify pets in your account",
|
||||||
|
"read:pets": "read your pets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "oauth2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "This is a summary",
|
||||||
|
"description": "This is a description",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "This is a request description",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestRequest"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Testerina": {
|
||||||
|
"value": {
|
||||||
|
"fieldName": {
|
||||||
|
"nesty": "asdf"
|
||||||
|
},
|
||||||
|
"b": 1.5,
|
||||||
|
"aaa": []
|
||||||
|
},
|
||||||
|
"summary": "summary",
|
||||||
|
"description": "description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "This is a response description",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Testerino": {
|
||||||
|
"value": {
|
||||||
|
"c": "Heya"
|
||||||
|
},
|
||||||
|
"summary": "summary",
|
||||||
|
"description": "description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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": []
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import com.google.gson.annotations.Expose
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KProperty1
|
|
||||||
import kotlin.reflect.full.memberProperties
|
|
||||||
import kotlin.reflect.jvm.javaField
|
|
||||||
|
|
||||||
/*
|
|
||||||
These are test implementation and may well be a good starting point for creating production ones.
|
|
||||||
Both Gson and Jackson are complex and can achieve this things is more than one way therefore
|
|
||||||
these will not always work hence why they are in the test package
|
|
||||||
*/
|
|
||||||
|
|
||||||
class GsonSchemaConfigurator: SchemaConfigurator {
|
|
||||||
|
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> {
|
|
||||||
// NOTE: This is test logic Expose is set at a global Gson level so configure to match your Gson set up
|
|
||||||
val hasAnyExpose = clazz.memberProperties.any { it.hasJavaAnnotation<Expose>() }
|
|
||||||
return if(hasAnyExpose) {
|
|
||||||
clazz.memberProperties
|
|
||||||
.filter { it.hasJavaAnnotation<Expose>() }
|
|
||||||
} else clazz.memberProperties
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String =
|
|
||||||
property.getJavaAnnotation<SerializedName>()?.value?: property.name
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class JacksonSchemaConfigurator: SchemaConfigurator {
|
|
||||||
|
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
|
||||||
clazz.memberProperties
|
|
||||||
.filterNot {
|
|
||||||
it.hasJavaAnnotation<JsonIgnore>()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String =
|
|
||||||
property.getJavaAnnotation<JsonProperty>()?.value?: property.name
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T: Annotation> KProperty1<*, *>.hasJavaAnnotation(): Boolean {
|
|
||||||
return javaField?.isAnnotationPresent(T::class.java)?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T: Annotation> KProperty1<*, *>.getJavaAnnotation(): T? {
|
|
||||||
return javaField?.getDeclaredAnnotation(T::class.java)
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
|
||||||
|
|
||||||
enum class SupportedSerializer {
|
|
||||||
KOTLINX,
|
|
||||||
GSON,
|
|
||||||
JACKSON
|
|
||||||
}
|
|
@ -1,7 +1,5 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
package io.bkbn.kompendium.core.fixtures
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec
|
import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
@ -16,19 +14,17 @@ import io.kotest.matchers.shouldNot
|
|||||||
import io.kotest.matchers.string.beBlank
|
import io.kotest.matchers.string.beBlank
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.serialization.gson.gson
|
|
||||||
import io.ktor.serialization.jackson.jackson
|
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.engine.ApplicationEngineEnvironmentBuilder
|
import io.ktor.server.engine.ApplicationEngineEnvironmentBuilder
|
||||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiationConfig
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.testing.ApplicationTestBuilder
|
import io.ktor.server.testing.ApplicationTestBuilder
|
||||||
import io.ktor.server.testing.testApplication
|
import io.ktor.server.testing.testApplication
|
||||||
import java.io.File
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.File
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
object TestHelpers {
|
object TestHelpers {
|
||||||
@ -55,7 +51,7 @@ object TestHelpers {
|
|||||||
/**
|
/**
|
||||||
* This will take a provided JSON snapshot file, retrieve it from the resource folder,
|
* This will take a provided JSON snapshot file, retrieve it from the resource folder,
|
||||||
* and build a test ktor server to compare the expected output with the output found in the default
|
* and build a test ktor server to compare the expected output with the output found in the default
|
||||||
* OpenAPI json endpoint. By default, this will run the same test with Gson, Kotlinx, and Jackson serializers
|
* OpenAPI json endpoint.
|
||||||
* @param snapshotName The snapshot file to retrieve from the resources folder
|
* @param snapshotName The snapshot file to retrieve from the resources folder
|
||||||
*/
|
*/
|
||||||
fun openApiTestAllSerializers(
|
fun openApiTestAllSerializers(
|
||||||
@ -64,70 +60,47 @@ object TestHelpers {
|
|||||||
applicationSetup: Application.() -> Unit = { },
|
applicationSetup: Application.() -> Unit = { },
|
||||||
specOverrides: OpenApiSpec.() -> OpenApiSpec = { this },
|
specOverrides: OpenApiSpec.() -> OpenApiSpec = { this },
|
||||||
applicationEnvironmentBuilder: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
applicationEnvironmentBuilder: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||||
|
notarizedApplicationConfigOverrides: NotarizedApplication.Config.() -> Unit = {},
|
||||||
|
contentNegotiation: ContentNegotiationConfig.() -> Unit = {
|
||||||
|
json(Json {
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
serializersModule = KompendiumSerializersModule.module
|
||||||
|
})
|
||||||
|
},
|
||||||
routeUnderTest: Routing.() -> Unit
|
routeUnderTest: Routing.() -> Unit
|
||||||
) {
|
) {
|
||||||
openApiTest(
|
openApiTest(
|
||||||
snapshotName,
|
snapshotName,
|
||||||
SupportedSerializer.KOTLINX,
|
|
||||||
routeUnderTest,
|
|
||||||
applicationSetup,
|
|
||||||
specOverrides,
|
|
||||||
customTypes,
|
|
||||||
applicationEnvironmentBuilder
|
|
||||||
)
|
|
||||||
openApiTest(
|
|
||||||
snapshotName,
|
|
||||||
SupportedSerializer.JACKSON,
|
|
||||||
routeUnderTest,
|
|
||||||
applicationSetup,
|
|
||||||
specOverrides,
|
|
||||||
customTypes,
|
|
||||||
applicationEnvironmentBuilder
|
|
||||||
)
|
|
||||||
openApiTest(
|
|
||||||
snapshotName,
|
|
||||||
SupportedSerializer.GSON,
|
|
||||||
routeUnderTest,
|
routeUnderTest,
|
||||||
applicationSetup,
|
applicationSetup,
|
||||||
specOverrides,
|
specOverrides,
|
||||||
customTypes,
|
customTypes,
|
||||||
|
notarizedApplicationConfigOverrides,
|
||||||
|
contentNegotiation,
|
||||||
applicationEnvironmentBuilder
|
applicationEnvironmentBuilder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openApiTest(
|
private fun openApiTest(
|
||||||
snapshotName: String,
|
snapshotName: String,
|
||||||
serializer: SupportedSerializer,
|
|
||||||
routeUnderTest: Routing.() -> Unit,
|
routeUnderTest: Routing.() -> Unit,
|
||||||
applicationSetup: Application.() -> Unit,
|
applicationSetup: Application.() -> Unit,
|
||||||
specOverrides: OpenApiSpec.() -> OpenApiSpec,
|
specOverrides: OpenApiSpec.() -> OpenApiSpec,
|
||||||
typeOverrides: Map<KType, JsonSchema> = emptyMap(),
|
typeOverrides: Map<KType, JsonSchema> = emptyMap(),
|
||||||
applicationBuilder: ApplicationEngineEnvironmentBuilder.() -> Unit = {}
|
notarizedApplicationConfigOverrides: NotarizedApplication.Config.() -> Unit,
|
||||||
|
contentNegotiation: ContentNegotiationConfig.() -> Unit,
|
||||||
|
applicationBuilder: ApplicationEngineEnvironmentBuilder.() -> Unit
|
||||||
) = testApplication {
|
) = testApplication {
|
||||||
environment(applicationBuilder)
|
environment(applicationBuilder)
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
customTypes = typeOverrides
|
customTypes = typeOverrides
|
||||||
spec = defaultSpec().specOverrides()
|
spec = { specOverrides(defaultSpec()) }
|
||||||
schemaConfigurator = when (serializer) {
|
schemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
SupportedSerializer.KOTLINX -> KotlinXSchemaConfigurator()
|
notarizedApplicationConfigOverrides()
|
||||||
SupportedSerializer.GSON -> GsonSchemaConfigurator()
|
|
||||||
SupportedSerializer.JACKSON -> JacksonSchemaConfigurator()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
when (serializer) {
|
contentNegotiation()
|
||||||
SupportedSerializer.KOTLINX -> json(Json {
|
|
||||||
encodeDefaults = true
|
|
||||||
explicitNulls = false
|
|
||||||
serializersModule = KompendiumSerializersModule.module
|
|
||||||
})
|
|
||||||
|
|
||||||
SupportedSerializer.GSON -> gson()
|
|
||||||
SupportedSerializer.JACKSON -> jackson(ContentType.Application.Json) {
|
|
||||||
enable(SerializationFeature.INDENT_OUTPUT)
|
|
||||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
application(applicationSetup)
|
application(applicationSetup)
|
||||||
routing {
|
routing {
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
package io.bkbn.kompendium.core.fixtures
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import com.google.gson.annotations.Expose
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -91,6 +89,25 @@ data class AnothaJamma(val b: Float) : SlammaJamma
|
|||||||
|
|
||||||
data class InsaneJamma(val c: SlammaJamma) : SlammaJamma
|
data class InsaneJamma(val c: SlammaJamma) : SlammaJamma
|
||||||
|
|
||||||
|
sealed interface ChillaxificationMaximization
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("chillax")
|
||||||
|
data class Chillax(val a: String) : ChillaxificationMaximization
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("maximize")
|
||||||
|
data class ToDaMax(val b: Int) : ChillaxificationMaximization
|
||||||
|
|
||||||
|
sealed class Gadget(
|
||||||
|
open val title: String,
|
||||||
|
open val description: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class Gizmo(
|
||||||
|
override val title: String,
|
||||||
|
) : Gadget(title, "Just a gizmo")
|
||||||
|
|
||||||
sealed interface Flibbity<T>
|
sealed interface Flibbity<T>
|
||||||
|
|
||||||
data class Gibbity<T>(val a: T) : Flibbity<T>
|
data class Gibbity<T>(val a: T) : Flibbity<T>
|
||||||
@ -158,9 +175,7 @@ object Nested {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TransientObject(
|
data class TransientObject(
|
||||||
@field:Expose
|
|
||||||
val nonTransient: String,
|
val nonTransient: String,
|
||||||
@field:JsonIgnore
|
|
||||||
@Transient
|
@Transient
|
||||||
val transient: String = "transient"
|
val transient: String = "transient"
|
||||||
)
|
)
|
||||||
@ -174,8 +189,6 @@ data class UnbackedObject(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SerialNameObject(
|
data class SerialNameObject(
|
||||||
@field:JsonProperty("snake_case_name")
|
|
||||||
@field:SerializedName("snake_case_name")
|
|
||||||
@SerialName("snake_case_name")
|
@SerialName("snake_case_name")
|
||||||
val camelCaseName: String
|
val camelCaseName: String
|
||||||
)
|
)
|
||||||
@ -194,3 +207,8 @@ enum class Color {
|
|||||||
data class ObjectWithEnum(
|
data class ObjectWithEnum(
|
||||||
val color: Color
|
val color: Color
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SomethingSimilar(val a: String) {
|
||||||
|
val b = "something else"
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=3.14.4
|
project.version=4.0.0-alpha
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
|
@ -1,20 +1,51 @@
|
|||||||
package io.bkbn.kompendium.json.schema
|
package io.bkbn.kompendium.json.schema
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
import kotlin.reflect.full.hasAnnotation
|
import kotlin.reflect.full.hasAnnotation
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
|
||||||
class KotlinXSchemaConfigurator : SchemaConfigurator {
|
class KotlinXSchemaConfigurator : SchemaConfigurator {
|
||||||
|
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> {
|
||||||
clazz.memberProperties
|
return clazz.memberProperties
|
||||||
.filterNot { it.hasAnnotation<Transient>() }
|
.filterNot { it.hasAnnotation<Transient>() }
|
||||||
|
.filter { clazz.primaryConstructor?.parameters?.map { it.name }?.contains(it.name) ?: true }
|
||||||
|
}
|
||||||
|
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String =
|
override fun serializableName(property: KProperty1<out Any, *>): String =
|
||||||
property.annotations
|
property.annotations
|
||||||
.filterIsInstance<SerialName>()
|
.filterIsInstance<SerialName>()
|
||||||
.firstOrNull()?.value ?: property.name
|
.firstOrNull()?.value ?: property.name
|
||||||
|
|
||||||
|
override fun sealedTypeEnrichment(
|
||||||
|
implementationType: KType,
|
||||||
|
implementationSchema: JsonSchema,
|
||||||
|
): JsonSchema {
|
||||||
|
return if (implementationSchema is TypeDefinition && implementationSchema.type == "object") {
|
||||||
|
implementationSchema.copy(
|
||||||
|
required = implementationSchema.required?.plus("type"),
|
||||||
|
properties = implementationSchema.properties?.plus(
|
||||||
|
mapOf(
|
||||||
|
"type" to EnumDefinition("string", enum = setOf(determineTypeQualifier(implementationType)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
implementationSchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineTypeQualifier(type: KType): String {
|
||||||
|
val nameOverrideAnnotation = (type.classifier as KClass<*>).findAnnotation<SerialName>()
|
||||||
|
return nameOverrideAnnotation?.value ?: (type.classifier as KClass<*>).qualifiedName!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
package io.bkbn.kompendium.json.schema
|
package io.bkbn.kompendium.json.schema
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
interface SchemaConfigurator {
|
interface SchemaConfigurator {
|
||||||
fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>>
|
fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>>
|
||||||
fun serializableName(property: KProperty1<out Any, *>): String
|
fun serializableName(property: KProperty1<out Any, *>): String
|
||||||
|
|
||||||
open class Default : SchemaConfigurator {
|
fun sealedTypeEnrichment(
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
implementationType: KType,
|
||||||
clazz.memberProperties
|
implementationSchema: JsonSchema
|
||||||
|
): JsonSchema
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String = property.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -25,15 +25,18 @@ object SealedObjectHandler {
|
|||||||
val subclasses = clazz.sealedSubclasses
|
val subclasses = clazz.sealedSubclasses
|
||||||
.map { it.createType(type.arguments) }
|
.map { it.createType(type.arguments) }
|
||||||
.map { t ->
|
.map { t ->
|
||||||
SchemaGenerator.fromTypeToSchema(t, cache, schemaConfigurator, enrichment).let { js ->
|
SchemaGenerator.fromTypeToSchema(t, cache, schemaConfigurator, enrichment)
|
||||||
if (js is TypeDefinition && js.type == "object") {
|
.let {
|
||||||
val slug = t.getSlug(enrichment)
|
schemaConfigurator.sealedTypeEnrichment(t, it)
|
||||||
cache[slug] = js
|
}.let { js ->
|
||||||
ReferenceDefinition(t.getReferenceSlug(enrichment))
|
if (js is TypeDefinition && js.type == "object") {
|
||||||
} else {
|
val slug = t.getSlug(enrichment)
|
||||||
js
|
cache[slug] = js
|
||||||
|
ReferenceDefinition(t.getReferenceSlug(enrichment))
|
||||||
|
} else {
|
||||||
|
js
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.toSet()
|
.toSet()
|
||||||
return AnyOfDefinition(subclasses)
|
return AnyOfDefinition(subclasses)
|
||||||
|
@ -13,7 +13,7 @@ object Helpers {
|
|||||||
fun KType.getSlug(enrichment: Enrichment? = null) = when (enrichment) {
|
fun KType.getSlug(enrichment: Enrichment? = null) = when (enrichment) {
|
||||||
is TypeEnrichment<*> -> getEnrichedSlug(enrichment)
|
is TypeEnrichment<*> -> getEnrichedSlug(enrichment)
|
||||||
is PropertyEnrichment -> error("Slugs should not be generated for field enrichments")
|
is PropertyEnrichment -> error("Slugs should not be generated for field enrichments")
|
||||||
null -> getSimpleSlug()
|
else -> getSimpleSlug()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KType.getSimpleSlug(): String = when {
|
fun KType.getSimpleSlug(): String = when {
|
||||||
@ -26,7 +26,7 @@ object Helpers {
|
|||||||
fun KType.getReferenceSlug(enrichment: Enrichment? = null): String = when (enrichment) {
|
fun KType.getReferenceSlug(enrichment: Enrichment? = null): String = when (enrichment) {
|
||||||
is TypeEnrichment<*> -> getSimpleReferenceSlug() + "-${enrichment.id}"
|
is TypeEnrichment<*> -> getSimpleReferenceSlug() + "-${enrichment.id}"
|
||||||
is PropertyEnrichment -> error("Reference slugs should never be generated for field enrichments")
|
is PropertyEnrichment -> error("Reference slugs should never be generated for field enrichments")
|
||||||
null -> getSimpleReferenceSlug()
|
else -> getSimpleReferenceSlug()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KType.getSimpleReferenceSlug() = when {
|
private fun KType.getSimpleReferenceSlug() = when {
|
||||||
|
@ -20,5 +20,5 @@ data class MediaType(
|
|||||||
val encoding: Map<String, Encoding>? = null,
|
val encoding: Map<String, Encoding>? = null,
|
||||||
) {
|
) {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Example(@Contextual val value: Any)
|
data class Example(@Contextual val value: Any, val summary: String? = null, val description: String? = null)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package io.bkbn.kompendium.oas.payload
|
package io.bkbn.kompendium.oas.payload
|
||||||
|
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import kotlinx.serialization.Contextual
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,12 +25,9 @@ data class Parameter(
|
|||||||
val required: Boolean = true,
|
val required: Boolean = true,
|
||||||
val deprecated: Boolean = false,
|
val deprecated: Boolean = false,
|
||||||
val allowEmptyValue: Boolean? = null,
|
val allowEmptyValue: Boolean? = null,
|
||||||
val examples: Map<String, Example>? = null,
|
val examples: Map<String, MediaType.Example>? = null,
|
||||||
// todo support styling https://spec.openapis.org/oas/v3.1.0#style-values
|
// todo support styling https://spec.openapis.org/oas/v3.1.0#style-values
|
||||||
) {
|
) {
|
||||||
@Serializable
|
|
||||||
data class Example(@Contextual val value: Any)
|
|
||||||
|
|
||||||
@Suppress("EnumNaming")
|
@Suppress("EnumNaming")
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class Location {
|
enum class Location {
|
||||||
|
@ -60,13 +60,15 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec.copy(
|
spec = {
|
||||||
components = Components(
|
baseSpec.copy(
|
||||||
securitySchemes = mutableMapOf(
|
components = Components(
|
||||||
"basic" to BasicAuth()
|
securitySchemes = mutableMapOf(
|
||||||
|
"basic" to BasicAuth()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
swagger(pageTitle = "Simple API Docs")
|
swagger(pageTitle = "Simple API Docs")
|
||||||
|
@ -44,9 +44,7 @@ private fun Application.mainModule() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = { baseSpec }
|
||||||
// Adds support for @Transient and @SerialName
|
|
||||||
// If you are not using them this is not required.
|
|
||||||
schemaConfigurator = KotlinXSchemaConfigurator()
|
schemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
|
@ -43,9 +43,7 @@ private fun Application.mainModule() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = { baseSpec }
|
||||||
// Adds support for @Transient and @SerialName
|
|
||||||
// If you are not using them this is not required.
|
|
||||||
schemaConfigurator = KotlinXSchemaConfigurator()
|
schemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package io.bkbn.kompendium.playground
|
package io.bkbn.kompendium.playground
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.core.routes.swagger
|
import io.bkbn.kompendium.core.routes.swagger
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.component.Components
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.bkbn.kompendium.oas.security.BasicAuth
|
||||||
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
import io.bkbn.kompendium.playground.util.ExampleResponse
|
import io.bkbn.kompendium.playground.util.ExampleResponse
|
||||||
import io.bkbn.kompendium.playground.util.Util.baseSpec
|
import io.bkbn.kompendium.playground.util.Util.baseSpec
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.serialization.gson.gson
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
@ -21,14 +21,13 @@ import io.ktor.server.cio.CIO
|
|||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
import io.ktor.server.response.respond
|
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.get
|
import io.ktor.server.routing.get
|
||||||
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 kotlin.reflect.KClass
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlin.reflect.KProperty1
|
import kotlinx.serialization.json.Json
|
||||||
import kotlin.reflect.full.memberProperties
|
|
||||||
import kotlin.reflect.jvm.javaField
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(
|
embeddedServer(
|
||||||
@ -38,20 +37,44 @@ fun main() {
|
|||||||
).start(wait = true)
|
).start(wait = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val CustomJsonEncoder = Json {
|
||||||
|
serializersModule = KompendiumSerializersModule.module
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
}
|
||||||
|
|
||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
gson {
|
json(Json {
|
||||||
setPrettyPrinting()
|
serializersModule = KompendiumSerializersModule.module
|
||||||
}
|
encodeDefaults = true
|
||||||
|
explicitNulls = true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = {
|
||||||
schemaConfigurator = GsonSchemaConfigurator()
|
baseSpec.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"basic" to BasicAuth()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
specRoute = { spec, routing ->
|
||||||
|
routing {
|
||||||
|
route("/openapi.json") {
|
||||||
|
get {
|
||||||
|
call.response.headers.append("Content-Type", "application/json")
|
||||||
|
call.respondText { CustomJsonEncoder.encodeToString(spec) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
swagger(pageTitle = "Simple API Docs")
|
swagger(pageTitle = "Simple API Docs")
|
||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
locationDocumentation()
|
locationDocumentation()
|
||||||
get {
|
get {
|
||||||
@ -73,6 +96,9 @@ private fun Route.locationDocumentation() {
|
|||||||
get = GetInfo.builder {
|
get = GetInfo.builder {
|
||||||
summary("Get user by id")
|
summary("Get user by id")
|
||||||
description("A very neat endpoint!")
|
description("A very neat endpoint!")
|
||||||
|
security = mapOf(
|
||||||
|
"basic" to emptyList()
|
||||||
|
)
|
||||||
response {
|
response {
|
||||||
responseCode(HttpStatusCode.OK)
|
responseCode(HttpStatusCode.OK)
|
||||||
responseType<ExampleResponse>()
|
responseType<ExampleResponse>()
|
||||||
@ -81,27 +107,3 @@ private fun Route.locationDocumentation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds support for Expose and SerializedName annotations,
|
|
||||||
// if you are not using them this is not required
|
|
||||||
class GsonSchemaConfigurator(
|
|
||||||
private val excludeFieldsWithoutExposeAnnotation: Boolean = false
|
|
||||||
): SchemaConfigurator.Default() {
|
|
||||||
|
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> {
|
|
||||||
return if(excludeFieldsWithoutExposeAnnotation) clazz.memberProperties
|
|
||||||
.filter { it.hasJavaAnnotation<Expose>() }
|
|
||||||
else clazz.memberProperties
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String =
|
|
||||||
property.getJavaAnnotation<SerializedName>()?.value?: property.name
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T: Annotation> KProperty1<*, *>.hasJavaAnnotation(): Boolean {
|
|
||||||
return javaField?.isAnnotationPresent(T::class.java)?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T: Annotation> KProperty1<*, *>.getJavaAnnotation(): T? {
|
|
||||||
return javaField?.getDeclaredAnnotation(T::class.java)
|
|
||||||
}
|
|
@ -46,7 +46,7 @@ private fun Application.mainModule() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = { baseSpec }
|
||||||
customTypes = mapOf(
|
customTypes = mapOf(
|
||||||
typeOf<Instant>() to TypeDefinition(type = "string", format = "date-time")
|
typeOf<Instant>() to TypeDefinition(type = "string", format = "date-time")
|
||||||
)
|
)
|
||||||
|
@ -45,7 +45,7 @@ private fun Application.mainModule() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = { baseSpec }
|
||||||
// Adds support for @Transient and @SerialName
|
// Adds support for @Transient and @SerialName
|
||||||
// If you are not using them this is not required.
|
// If you are not using them this is not required.
|
||||||
schemaConfigurator = KotlinXSchemaConfigurator()
|
schemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
package io.bkbn.kompendium.playground
|
package io.bkbn.kompendium.playground
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
|
||||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.core.routes.swagger
|
import io.bkbn.kompendium.core.routes.swagger
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.MediaType
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
import io.bkbn.kompendium.playground.util.ExampleResponse
|
import io.bkbn.kompendium.playground.util.ExampleResponse
|
||||||
|
import io.bkbn.kompendium.playground.util.ExceptionResponse
|
||||||
import io.bkbn.kompendium.playground.util.Util.baseSpec
|
import io.bkbn.kompendium.playground.util.Util.baseSpec
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.serialization.jackson.jackson
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
@ -27,10 +26,7 @@ import io.ktor.server.routing.Route
|
|||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
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 kotlin.reflect.KClass
|
import kotlinx.serialization.json.Json
|
||||||
import kotlin.reflect.KProperty1
|
|
||||||
import kotlin.reflect.full.memberProperties
|
|
||||||
import kotlin.reflect.jvm.javaField
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(
|
embeddedServer(
|
||||||
@ -42,29 +38,35 @@ fun main() {
|
|||||||
|
|
||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
jackson {
|
json(Json {
|
||||||
enable(SerializationFeature.INDENT_OUTPUT)
|
serializersModule = KompendiumSerializersModule.module
|
||||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
encodeDefaults = true
|
||||||
}
|
explicitNulls = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = { baseSpec }
|
||||||
schemaConfigurator = JacksonSchemaConfigurator()
|
schemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
swagger(pageTitle = "Simple API Docs")
|
swagger(pageTitle = "Simple API Docs")
|
||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
locationDocumentation()
|
idDocumentation()
|
||||||
get {
|
get {
|
||||||
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
||||||
}
|
}
|
||||||
|
route("/profile") {
|
||||||
|
profileDocumentation()
|
||||||
|
get {
|
||||||
|
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.locationDocumentation() {
|
private fun Route.idDocumentation() {
|
||||||
install(NotarizedRoute()) {
|
install(NotarizedRoute()) {
|
||||||
parameters = listOf(
|
parameters = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
@ -80,31 +82,42 @@ private fun Route.locationDocumentation() {
|
|||||||
responseCode(HttpStatusCode.OK)
|
responseCode(HttpStatusCode.OK)
|
||||||
responseType<ExampleResponse>()
|
responseType<ExampleResponse>()
|
||||||
description("Will return whether or not the user is real 😱")
|
description("Will return whether or not the user is real 😱")
|
||||||
|
examples(
|
||||||
|
"example1" to MediaType.Example(ExampleResponse(true), "ahaha", "bhbh"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
canRespond {
|
||||||
|
responseType<ExceptionResponse>()
|
||||||
|
responseCode(HttpStatusCode.NotFound)
|
||||||
|
description("Indicates that a user with this id does not exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds support for JsonIgnore and JsonProperty annotations,
|
private fun Route.profileDocumentation() {
|
||||||
// if you are not using them this is not required
|
install(NotarizedRoute()) {
|
||||||
// This also does not support class level configuration
|
parameters = listOf(
|
||||||
private class JacksonSchemaConfigurator: SchemaConfigurator.Default() {
|
Parameter(
|
||||||
|
name = "id",
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
`in` = Parameter.Location.path,
|
||||||
clazz.memberProperties
|
schema = TypeDefinition.STRING
|
||||||
.filterNot {
|
)
|
||||||
it.hasJavaAnnotation<JsonIgnore>()
|
)
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get a users profile")
|
||||||
|
description("A cool endpoint!")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<ExampleResponse>()
|
||||||
|
description("Returns user profile information")
|
||||||
}
|
}
|
||||||
|
canRespond {
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String =
|
responseType<ExceptionResponse>()
|
||||||
property.getJavaAnnotation<JsonProperty>()?.value?: property.name
|
responseCode(HttpStatusCode.NotFound)
|
||||||
|
description("Indicates that a user with this id does not exist")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private inline fun <reified T: Annotation> KProperty1<*, *>.hasJavaAnnotation(): Boolean {
|
}
|
||||||
return javaField?.isAnnotationPresent(T::class.java)?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T: Annotation> KProperty1<*, *>.getJavaAnnotation(): T? {
|
|
||||||
return javaField?.getDeclaredAnnotation(T::class.java)
|
|
||||||
}
|
}
|
@ -43,7 +43,7 @@ private fun Application.mainModule() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = { baseSpec }
|
||||||
}
|
}
|
||||||
install(StatusPages) {
|
install(StatusPages) {
|
||||||
exception<Throwable> { call, _ ->
|
exception<Throwable> { call, _ ->
|
||||||
|
@ -62,18 +62,22 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec.copy(
|
spec = {
|
||||||
components = Components(
|
baseSpec.copy(
|
||||||
securitySchemes = mutableMapOf(
|
components = Components(
|
||||||
"basic" to BasicAuth()
|
securitySchemes = mutableMapOf(
|
||||||
|
"basic" to BasicAuth()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
openApiJson = {
|
specRoute = { spec, routing ->
|
||||||
authenticate("basic") {
|
routing {
|
||||||
route("/openapi.json") {
|
authenticate("basic") {
|
||||||
get {
|
route("/openapi.json") {
|
||||||
call.respond(HttpStatusCode.OK, this@route.application.attributes[KompendiumAttributes.openApiSpec])
|
get {
|
||||||
|
call.respond(HttpStatusCode.OK, spec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ private fun Application.mainModule() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = { baseSpec }
|
||||||
}
|
}
|
||||||
install(NotarizedLocations()) {
|
install(NotarizedLocations()) {
|
||||||
locations = mapOf(
|
locations = mapOf(
|
||||||
|
@ -44,7 +44,7 @@ private fun Application.mainModule() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = { baseSpec }
|
||||||
}
|
}
|
||||||
install(NotarizedResources()) {
|
install(NotarizedResources()) {
|
||||||
resources = mapOf(
|
resources = mapOf(
|
||||||
|
Reference in New Issue
Block a user