feat: v3 locations (#292)
This commit is contained in:
@ -11,8 +11,10 @@ import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
||||
import io.bkbn.kompendium.core.util.Helpers.getReferenceSlug
|
||||
import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug
|
||||
import io.bkbn.kompendium.core.util.SpecConfig
|
||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
@ -31,17 +33,17 @@ import kotlin.reflect.KType
|
||||
|
||||
object NotarizedRoute {
|
||||
|
||||
class Config {
|
||||
var tags: Set<String> = emptySet()
|
||||
var parameters: List<Parameter> = emptyList()
|
||||
var get: GetInfo? = null
|
||||
var post: PostInfo? = null
|
||||
var put: PutInfo? = null
|
||||
var delete: DeleteInfo? = null
|
||||
var patch: PatchInfo? = null
|
||||
var head: HeadInfo? = null
|
||||
var options: OptionsInfo? = null
|
||||
var security: Map<String, List<String>>? = null
|
||||
class Config : SpecConfig {
|
||||
override var tags: Set<String> = emptySet()
|
||||
override var parameters: List<Parameter> = emptyList()
|
||||
override var get: GetInfo? = null
|
||||
override var post: PostInfo? = null
|
||||
override var put: PutInfo? = null
|
||||
override var delete: DeleteInfo? = null
|
||||
override var patch: PatchInfo? = null
|
||||
override var head: HeadInfo? = null
|
||||
override var options: OptionsInfo? = null
|
||||
override var security: Map<String, List<String>>? = null
|
||||
internal var path: Path? = null
|
||||
}
|
||||
|
||||
@ -86,86 +88,5 @@ object NotarizedRoute {
|
||||
pluginConfig.path = path
|
||||
}
|
||||
|
||||
private fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: Config) {
|
||||
SchemaGenerator.fromTypeOrUnit(this.response.responseType, spec.components.schemas)?.let { schema ->
|
||||
spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema
|
||||
}
|
||||
|
||||
errors.forEach { error ->
|
||||
SchemaGenerator.fromTypeOrUnit(error.responseType, spec.components.schemas)?.let { schema ->
|
||||
spec.components.schemas[error.responseType.getSimpleSlug()] = schema
|
||||
}
|
||||
}
|
||||
|
||||
when (this) {
|
||||
is MethodInfoWithRequest -> {
|
||||
SchemaGenerator.fromTypeOrUnit(this.request.requestType, spec.components.schemas)?.let { schema ->
|
||||
spec.components.schemas[this.request.requestType.getSimpleSlug()] = schema
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
val operations = this.toPathOperation(config)
|
||||
|
||||
when (this) {
|
||||
is DeleteInfo -> path.delete = operations
|
||||
is GetInfo -> path.get = operations
|
||||
is HeadInfo -> path.head = operations
|
||||
is PatchInfo -> path.patch = operations
|
||||
is PostInfo -> path.post = operations
|
||||
is PutInfo -> path.put = operations
|
||||
is OptionsInfo -> path.options = operations
|
||||
}
|
||||
}
|
||||
|
||||
private fun MethodInfo.toPathOperation(config: Config) = PathOperation(
|
||||
tags = config.tags.plus(this.tags),
|
||||
summary = this.summary,
|
||||
description = this.description,
|
||||
externalDocs = this.externalDocumentation,
|
||||
operationId = this.operationId,
|
||||
deprecated = this.deprecated,
|
||||
parameters = this.parameters,
|
||||
security = config.security
|
||||
?.map { (k, v) -> k to v }
|
||||
?.map { listOf(it).toMap() }
|
||||
?.toList(),
|
||||
requestBody = when (this) {
|
||||
is MethodInfoWithRequest -> Request(
|
||||
description = this.request.description,
|
||||
content = this.request.requestType.toReferenceContent(this.request.examples),
|
||||
required = true
|
||||
)
|
||||
|
||||
else -> null
|
||||
},
|
||||
responses = mapOf(
|
||||
this.response.responseCode.value to Response(
|
||||
description = this.response.description,
|
||||
content = this.response.responseType.toReferenceContent(this.response.examples)
|
||||
)
|
||||
).plus(this.errors.toResponseMap())
|
||||
)
|
||||
|
||||
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
||||
error.responseCode.value to Response(
|
||||
description = error.description,
|
||||
content = error.responseType.toReferenceContent(error.examples)
|
||||
)
|
||||
}
|
||||
|
||||
private fun KType.toReferenceContent(examples: Map<String, MediaType.Example>?): Map<String, MediaType>? =
|
||||
when (this.classifier as KClass<*>) {
|
||||
Unit::class -> null
|
||||
else -> mapOf(
|
||||
"application/json" to MediaType(
|
||||
schema = ReferenceDefinition(this.getReferenceSlug()),
|
||||
examples = examples
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
||||
}
|
||||
|
@ -1,13 +1,26 @@
|
||||
package io.bkbn.kompendium.core.util
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||
import io.bkbn.kompendium.core.metadata.HeadInfo
|
||||
import io.bkbn.kompendium.core.metadata.MethodInfo
|
||||
import io.bkbn.kompendium.core.metadata.MethodInfoWithRequest
|
||||
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
||||
import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.path.Path
|
||||
import io.bkbn.kompendium.oas.path.PathOperation
|
||||
import io.bkbn.kompendium.oas.payload.MediaType
|
||||
import io.bkbn.kompendium.oas.payload.Request
|
||||
import io.bkbn.kompendium.oas.payload.Response
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.createType
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.util.Locale
|
||||
|
||||
object Helpers {
|
||||
|
||||
@ -32,4 +45,85 @@ object Helpers {
|
||||
.map { it.simpleName }
|
||||
return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-")
|
||||
}
|
||||
|
||||
fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: SpecConfig) {
|
||||
SchemaGenerator.fromTypeOrUnit(this.response.responseType, spec.components.schemas)?.let { schema ->
|
||||
spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema
|
||||
}
|
||||
|
||||
errors.forEach { error ->
|
||||
SchemaGenerator.fromTypeOrUnit(error.responseType, spec.components.schemas)?.let { schema ->
|
||||
spec.components.schemas[error.responseType.getSimpleSlug()] = schema
|
||||
}
|
||||
}
|
||||
|
||||
when (this) {
|
||||
is MethodInfoWithRequest -> {
|
||||
SchemaGenerator.fromTypeOrUnit(this.request.requestType, spec.components.schemas)?.let { schema ->
|
||||
spec.components.schemas[this.request.requestType.getSimpleSlug()] = schema
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
val operations = this.toPathOperation(config)
|
||||
|
||||
when (this) {
|
||||
is DeleteInfo -> path.delete = operations
|
||||
is GetInfo -> path.get = operations
|
||||
is HeadInfo -> path.head = operations
|
||||
is PatchInfo -> path.patch = operations
|
||||
is PostInfo -> path.post = operations
|
||||
is PutInfo -> path.put = operations
|
||||
is OptionsInfo -> path.options = operations
|
||||
}
|
||||
}
|
||||
|
||||
private fun MethodInfo.toPathOperation(config: SpecConfig) = PathOperation(
|
||||
tags = config.tags.plus(this.tags),
|
||||
summary = this.summary,
|
||||
description = this.description,
|
||||
externalDocs = this.externalDocumentation,
|
||||
operationId = this.operationId,
|
||||
deprecated = this.deprecated,
|
||||
parameters = this.parameters,
|
||||
security = config.security
|
||||
?.map { (k, v) -> k to v }
|
||||
?.map { listOf(it).toMap() }
|
||||
?.toList(),
|
||||
requestBody = when (this) {
|
||||
is MethodInfoWithRequest -> Request(
|
||||
description = this.request.description,
|
||||
content = this.request.requestType.toReferenceContent(this.request.examples),
|
||||
required = true
|
||||
)
|
||||
|
||||
else -> null
|
||||
},
|
||||
responses = mapOf(
|
||||
this.response.responseCode.value to Response(
|
||||
description = this.response.description,
|
||||
content = this.response.responseType.toReferenceContent(this.response.examples)
|
||||
)
|
||||
).plus(this.errors.toResponseMap())
|
||||
)
|
||||
|
||||
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
||||
error.responseCode.value to Response(
|
||||
description = error.description,
|
||||
content = error.responseType.toReferenceContent(error.examples)
|
||||
)
|
||||
}
|
||||
|
||||
private fun KType.toReferenceContent(examples: Map<String, MediaType.Example>?): Map<String, MediaType>? =
|
||||
when (this.classifier as KClass<*>) {
|
||||
Unit::class -> null
|
||||
else -> mapOf(
|
||||
"application/json" to MediaType(
|
||||
schema = ReferenceDefinition(this.getReferenceSlug()),
|
||||
examples = examples
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package io.bkbn.kompendium.core.util
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||
import io.bkbn.kompendium.core.metadata.HeadInfo
|
||||
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
||||
import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||
import io.bkbn.kompendium.oas.payload.Parameter
|
||||
|
||||
interface SpecConfig {
|
||||
var tags: Set<String>
|
||||
var parameters: List<Parameter>
|
||||
var get: GetInfo?
|
||||
var post: PostInfo?
|
||||
var put: PutInfo?
|
||||
var delete: DeleteInfo?
|
||||
var patch: PatchInfo?
|
||||
var head: HeadInfo?
|
||||
var options: OptionsInfo?
|
||||
var security: Map<String, List<String>>?
|
||||
}
|
@ -23,6 +23,7 @@ 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.server.application.Application
|
||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.server.routing.Routing
|
||||
import io.ktor.server.testing.ApplicationTestBuilder
|
||||
@ -63,17 +64,19 @@ object TestHelpers {
|
||||
fun openApiTestAllSerializers(
|
||||
snapshotName: String,
|
||||
customTypes: Map<KType, JsonSchema> = emptyMap(),
|
||||
applicationSetup: Application.() -> Unit = { },
|
||||
routeUnderTest: Routing.() -> Unit
|
||||
) {
|
||||
openApiTest(snapshotName, SupportedSerializer.KOTLINX, routeUnderTest, customTypes)
|
||||
openApiTest(snapshotName, SupportedSerializer.JACKSON, routeUnderTest, customTypes)
|
||||
openApiTest(snapshotName, SupportedSerializer.GSON, routeUnderTest, customTypes)
|
||||
openApiTest(snapshotName, SupportedSerializer.KOTLINX, routeUnderTest, applicationSetup, customTypes)
|
||||
openApiTest(snapshotName, SupportedSerializer.JACKSON, routeUnderTest, applicationSetup, customTypes)
|
||||
openApiTest(snapshotName, SupportedSerializer.GSON, routeUnderTest, applicationSetup, customTypes)
|
||||
}
|
||||
|
||||
private fun openApiTest(
|
||||
snapshotName: String,
|
||||
serializer: SupportedSerializer,
|
||||
routeUnderTest: Routing.() -> Unit,
|
||||
applicationSetup: Application.() -> Unit,
|
||||
typeOverrides: Map<KType, JsonSchema> = emptyMap()
|
||||
) = testApplication {
|
||||
install(NotarizedApplication()) {
|
||||
@ -95,6 +98,7 @@ object TestHelpers {
|
||||
}
|
||||
}
|
||||
}
|
||||
application(applicationSetup)
|
||||
routing {
|
||||
redoc()
|
||||
routeUnderTest()
|
||||
|
Reference in New Issue
Block a user