feat: v3 locations (#292)

This commit is contained in:
Ryan Brink
2022-08-13 12:40:20 -07:00
committed by GitHub
parent 2007b1bb8c
commit 62080f3248
30 changed files with 616 additions and 1404 deletions

View File

@ -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("/\\(.+\\)"), "")
}

View File

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

View File

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

View File

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

View File

@ -1,84 +0,0 @@
package io.bkbn.kompendium.locations
//import io.bkbn.kompendium.annotations.Param
//import io.bkbn.kompendium.core.Kompendium
//import io.bkbn.kompendium.core.metadata.method.MethodInfo
//import io.bkbn.kompendium.core.parser.IMethodParser
//import io.bkbn.kompendium.oas.path.Path
//import io.bkbn.kompendium.oas.path.PathOperation
//import io.bkbn.kompendium.oas.payload.Parameter
//import io.ktor.application.feature
//import io.ktor.locations.KtorExperimentalLocationsAPI
//import io.ktor.locations.Location
//import io.ktor.routing.Route
//import io.ktor.routing.application
//import kotlin.reflect.KAnnotatedElement
//import kotlin.reflect.KClass
//import kotlin.reflect.KClassifier
//import kotlin.reflect.KType
//import kotlin.reflect.full.createType
//import kotlin.reflect.full.findAnnotation
//import kotlin.reflect.full.hasAnnotation
//import kotlin.reflect.full.memberProperties
//
//@OptIn(KtorExperimentalLocationsAPI::class)
//object LocationMethodParser : IMethodParser {
// override fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List<Parameter> {
// val clazzList = determineLocationParents(classifier!!)
// return clazzList.associateWith { it.memberProperties }
// .flatMap { (clazz, memberProperties) -> memberProperties.associateWith { clazz }.toList() }
// .filter { (prop, _) -> prop.hasAnnotation<Param>() }
// .map { (prop, clazz) -> prop.toParameter(info, clazz.createType(), clazz, feature) }
// }
//
// private fun determineLocationParents(classifier: KClassifier): List<KClass<*>> {
// var clazz: KClass<*>? = classifier as KClass<*>
// val clazzList = mutableListOf<KClass<*>>()
// while (clazz != null) {
// clazzList.add(clazz)
// clazz = getLocationParent(clazz)
// }
// return clazzList
// }
//
// private fun getLocationParent(clazz: KClass<*>): KClass<*>? {
// val parent = clazz.memberProperties
// .find { (it.returnType.classifier as KAnnotatedElement).hasAnnotation<Location>() }
// return parent?.returnType?.classifier as? KClass<*>
// }
//
// fun KClass<*>.calculateLocationPath(suffix: String = ""): String {
// val locationAnnotation = this.findAnnotation<Location>()
// require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
// val parent = this.java.declaringClass?.kotlin?.takeIf { it.hasAnnotation<Location>() }
// val newSuffix = locationAnnotation.path.plus(suffix)
// return when (parent) {
// null -> newSuffix
// else -> parent.calculateLocationPath(newSuffix)
// }
// }
//
// inline fun <reified TParam : Any> processBaseInfo(
// paramType: KType,
// requestType: KType,
// responseType: KType,
// info: MethodInfo<*, *>,
// route: Route
// ): LocationBaseInfo {
// val locationAnnotation = TParam::class.findAnnotation<Location>()
// require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
// val path = route.calculateRoutePath()
// val locationPath = TParam::class.calculateLocationPath()
// val pathWithLocation = path.plus(locationPath)
// val feature = route.application.feature(Kompendium)
// feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
// val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
// return LocationBaseInfo(baseInfo, feature, pathWithLocation)
// }
//
// data class LocationBaseInfo(
// val op: PathOperation,
// val feature: Kompendium,
// val path: String
// )
//}

View File

@ -1,110 +0,0 @@
package io.bkbn.kompendium.locations
//import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight
//import io.bkbn.kompendium.core.metadata.method.DeleteInfo
//import io.bkbn.kompendium.core.metadata.method.GetInfo
//import io.bkbn.kompendium.core.metadata.method.PostInfo
//import io.bkbn.kompendium.core.metadata.method.PutInfo
//import io.bkbn.kompendium.oas.path.PathOperation
//import io.ktor.application.ApplicationCall
//import io.ktor.http.HttpMethod
//import io.ktor.locations.KtorExperimentalLocationsAPI
//import io.ktor.locations.handle
//import io.ktor.locations.location
//import io.ktor.routing.Route
//import io.ktor.routing.method
//import io.ktor.util.pipeline.PipelineContext
//
///**
// * This version of notarized routes leverages the Ktor [io.ktor.locations.Locations] plugin to provide type safe access
// * to all path and query parameters.
// */
//@KtorExperimentalLocationsAPI
//object NotarizedLocation {
//
// /**
// * Notarization for an HTTP GET request leveraging the Ktor [io.ktor.locations.Locations] plugin
// * @param TParam The class containing all parameter fields.
// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param].
// * Additionally, the class must be annotated with @[io.ktor.locations.Location].
// * @param TResp Class detailing the expected API response
// * @param info Route metadata
// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
// */
// inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
// info: GetInfo<TParam, TResp>,
// postProcess: (PathOperation) -> PathOperation = { p -> p },
// noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
// ): Route = methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
// val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
// lbi.feature.config.spec.paths[lbi.path]?.get = postProcess(lbi.op)
// return location(TParam::class) {
// method(HttpMethod.Get) { handle(body) }
// }
// }
//
// /**
// * Notarization for an HTTP POST request leveraging the Ktor [io.ktor.locations.Locations] plugin
// * @param TParam The class containing all parameter fields.
// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param]
// * Additionally, the class must be annotated with @[io.ktor.locations.Location].
// * @param TReq Class detailing the expected API request body
// * @param TResp Class detailing the expected API response
// * @param info Route metadata
// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
// */
// inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost(
// info: PostInfo<TParam, TReq, TResp>,
// postProcess: (PathOperation) -> PathOperation = { p -> p },
// noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
// ): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
// val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
// lbi.feature.config.spec.paths[lbi.path]?.post = postProcess(lbi.op)
// return location(TParam::class) {
// method(HttpMethod.Post) { handle(body) }
// }
// }
//
// /**
// * Notarization for an HTTP Delete request leveraging the Ktor [io.ktor.locations.Locations] plugin
// * @param TParam The class containing all parameter fields.
// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param]
// * Additionally, the class must be annotated with @[io.ktor.locations.Location].
// * @param TReq Class detailing the expected API request body
// * @param TResp Class detailing the expected API response
// * @param info Route metadata
// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
// */
// inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut(
// info: PutInfo<TParam, TReq, TResp>,
// postProcess: (PathOperation) -> PathOperation = { p -> p },
// noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
// ): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
// val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
// lbi.feature.config.spec.paths[lbi.path]?.put = postProcess(lbi.op)
// return location(TParam::class) {
// method(HttpMethod.Put) { handle(body) }
// }
// }
//
// /**
// * Notarization for an HTTP POST request leveraging the Ktor [io.ktor.locations.Locations] plugin
// * @param TParam The class containing all parameter fields.
// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param]
// * Additionally, the class must be annotated with @[io.ktor.locations.Location].
// * @param TResp Class detailing the expected API response
// * @param info Route metadata
// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
// */
// inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
// info: DeleteInfo<TParam, TResp>,
// postProcess: (PathOperation) -> PathOperation = { p -> p },
// noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
// ): Route = methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
// val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
// lbi.feature.config.spec.paths[lbi.path]?.delete = postProcess(lbi.op)
// return location(TParam::class) {
// method(HttpMethod.Delete) { handle(body) }
// }
// }
//}

View File

@ -0,0 +1,79 @@
package io.bkbn.kompendium.locations
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
import io.bkbn.kompendium.core.metadata.DeleteInfo
import io.bkbn.kompendium.core.metadata.GetInfo
import io.bkbn.kompendium.core.metadata.HeadInfo
import io.bkbn.kompendium.core.metadata.OptionsInfo
import io.bkbn.kompendium.core.metadata.PatchInfo
import io.bkbn.kompendium.core.metadata.PostInfo
import io.bkbn.kompendium.core.metadata.PutInfo
import io.bkbn.kompendium.core.util.Helpers.addToSpec
import io.bkbn.kompendium.core.util.SpecConfig
import io.bkbn.kompendium.oas.path.Path
import io.bkbn.kompendium.oas.payload.Parameter
import io.ktor.server.application.createApplicationPlugin
import io.ktor.server.locations.KtorExperimentalLocationsAPI
import io.ktor.server.locations.Location
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.memberProperties
object NotarizedLocations {
data class LocationMetadata(
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,
) : SpecConfig
class Config {
lateinit var locations: Map<KClass<*>, LocationMetadata>
}
operator fun invoke() = createApplicationPlugin(
name = "NotarizedLocations",
createConfiguration = ::Config
) {
val spec = application.attributes[KompendiumAttributes.openApiSpec]
pluginConfig.locations.forEach { (k, v) ->
val path = Path()
path.parameters = v.parameters
v.get?.addToSpec(path, spec, v)
v.delete?.addToSpec(path, spec, v)
v.head?.addToSpec(path, spec, v)
v.options?.addToSpec(path, spec, v)
v.post?.addToSpec(path, spec, v)
v.put?.addToSpec(path, spec, v)
v.patch?.addToSpec(path, spec, v)
val location = k.getLocationFromClass()
spec.paths[location] = path
}
}
@OptIn(KtorExperimentalLocationsAPI::class)
private fun KClass<*>.getLocationFromClass(): String {
// todo if parent
val location = findAnnotation<Location>()
?: error("Cannot notarize a location without annotating with @Location")
val path = location.path
val parent = memberProperties.map { it.returnType.classifier as KClass<*> }.find { it.hasAnnotation<Location>() }
return if (parent == null) {
path
} else {
parent.getLocationFromClass() + path
}
}
}

View File

@ -1,82 +1,119 @@
package io.bkbn.kompendium.locations
//import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
//import io.bkbn.kompendium.locations.util.locationsConfig
//import io.bkbn.kompendium.locations.util.notarizedDeleteNestedLocation
//import io.bkbn.kompendium.locations.util.notarizedDeleteSimpleLocation
//import io.bkbn.kompendium.locations.util.notarizedGetNestedLocation
//import io.bkbn.kompendium.locations.util.notarizedGetNestedLocationFromNonLocationClass
//import io.bkbn.kompendium.locations.util.notarizedGetSimpleLocation
//import io.bkbn.kompendium.locations.util.notarizedPostNestedLocation
//import io.bkbn.kompendium.locations.util.notarizedPostSimpleLocation
//import io.bkbn.kompendium.locations.util.notarizedPutNestedLocation
//import io.bkbn.kompendium.locations.util.notarizedPutSimpleLocation
//import io.kotest.core.spec.style.DescribeSpec
//
//class KompendiumLocationsTest : DescribeSpec({
// describe("Locations") {
// it("Can notarize a get request with a simple location") {
// // act
// openApiTestAllSerializers("notarized_get_simple_location.json") {
// locationsConfig()
// notarizedGetSimpleLocation()
// }
// }
// it("Can notarize a get request with a nested location") {
// // act
// openApiTestAllSerializers("notarized_get_nested_location.json") {
// locationsConfig()
// notarizedGetNestedLocation()
// }
// }
// it("Can notarize a post with a simple location") {
// // act
// openApiTestAllSerializers("notarized_post_simple_location.json") {
// locationsConfig()
// notarizedPostSimpleLocation()
// }
// }
// it("Can notarize a post with a nested location") {
// // act
// openApiTestAllSerializers("notarized_post_nested_location.json") {
// locationsConfig()
// notarizedPostNestedLocation()
// }
// }
// it("Can notarize a put with a simple location") {
// // act
// openApiTestAllSerializers("notarized_put_simple_location.json") {
// locationsConfig()
// notarizedPutSimpleLocation()
// }
// }
// it("Can notarize a put with a nested location") {
// // act
// openApiTestAllSerializers("notarized_put_nested_location.json") {
// locationsConfig()
// notarizedPutNestedLocation()
// }
// }
// it("Can notarize a delete with a simple location") {
// // act
// openApiTestAllSerializers("notarized_delete_simple_location.json") {
// locationsConfig()
// notarizedDeleteSimpleLocation()
// }
// }
// it("Can notarize a delete with a nested location") {
// // act
// openApiTestAllSerializers("notarized_delete_nested_location.json") {
// locationsConfig()
// notarizedDeleteNestedLocation()
// }
// }
// it("Can notarize a get with a nested location nested in a non-location class") {
// // act
// openApiTestAllSerializers("notarized_get_nested_location_from_non_location_class.json") {
// locationsConfig()
// notarizedGetNestedLocationFromNonLocationClass()
// }
// }
// }
//})
import Listing
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
import io.bkbn.kompendium.core.fixtures.TestResponse
import io.bkbn.kompendium.core.metadata.GetInfo
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
import io.bkbn.kompendium.oas.payload.Parameter
import io.kotest.core.spec.style.DescribeSpec
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.locations.Locations
import io.ktor.server.locations.get
import io.ktor.server.response.respondText
class KompendiumLocationsTest : DescribeSpec({
describe("Location Tests") {
it("Can notarize a simple location") {
openApiTestAllSerializers(
snapshotName = "T0001__simple_location.json",
applicationSetup = {
install(Locations)
install(NotarizedLocations()) {
locations = mapOf(
Listing::class to NotarizedLocations.LocationMetadata(
parameters = listOf(
Parameter(
name = "name",
`in` = Parameter.Location.path,
schema = TypeDefinition.STRING
),
Parameter(
name = "page",
`in` = Parameter.Location.path,
schema = TypeDefinition.INT
)
),
get = GetInfo.builder {
summary("Location")
description("example location")
response {
responseCode(HttpStatusCode.OK)
responseType<TestResponse>()
description("does great things")
}
}
),
)
}
}
) {
get<Listing> { listing ->
call.respondText("Listing ${listing.name}, page ${listing.page}")
}
}
}
it("Can notarize nested locations") {
openApiTestAllSerializers(
snapshotName = "T0002__nested_locations.json",
applicationSetup = {
install(Locations)
install(NotarizedLocations()) {
locations = mapOf(
Type.Edit::class to NotarizedLocations.LocationMetadata(
parameters = listOf(
Parameter(
name = "name",
`in` = Parameter.Location.path,
schema = TypeDefinition.STRING
)
),
get = GetInfo.builder {
summary("Edit")
description("example location")
response {
responseCode(HttpStatusCode.OK)
responseType<TestResponse>()
description("does great things")
}
}
),
Type.Other::class to NotarizedLocations.LocationMetadata(
parameters = listOf(
Parameter(
name = "name",
`in` = Parameter.Location.path,
schema = TypeDefinition.STRING
),
Parameter(
name = "page",
`in` = Parameter.Location.path,
schema = TypeDefinition.INT
)
),
get = GetInfo.builder {
summary("Other")
description("example location")
response {
responseCode(HttpStatusCode.OK)
responseType<TestResponse>()
description("does great things")
}
}
),
)
}
}
) {
get<Type.Edit> { edit ->
call.respondText("Listing ${edit.parent.name}")
}
get<Type.Other> { other ->
call.respondText("Listing ${other.parent.name}, page ${other.page}")
}
}
}
}
})

View File

@ -1,22 +1,12 @@
package io.bkbn.kompendium.locations.util
import io.ktor.server.locations.Location
//import io.bkbn.kompendium.annotations.Param
//import io.bkbn.kompendium.annotations.ParamType
//import io.ktor.locations.Location
//
//@Location("/test/{name}")
//data class SimpleLoc(@Param(ParamType.PATH) val name: String) {
// @Location("/nesty")
// data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc)
//}
//
//object NonLocationObject {
// @Location("/test/{name}")
// data class SimpleLoc(@Param(ParamType.PATH) val name: String) {
// @Location("/nesty")
// data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc)
// }
//}
//
//data class SimpleResponse(val result: Boolean)
//data class SimpleRequest(val input: String)
@Location("/list/{name}/page/{page}")
data class Listing(val name: String, val page: Int)
@Location("/type/{name}")
data class Type(val name: String) {
@Location("/edit")
data class Edit(val parent: Type)
@Location("/other/{page}")
data class Other(val parent: Type, val page: Int)
}

View File

@ -1,107 +0,0 @@
package io.bkbn.kompendium.locations.util
//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedDelete
//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedGet
//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedPost
//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedPut
//import io.ktor.application.Application
//import io.ktor.application.call
//import io.ktor.application.install
//import io.ktor.locations.Locations
//import io.ktor.response.respondText
//import io.ktor.routing.route
//import io.ktor.routing.routing
//
//fun Application.locationsConfig() {
// install(Locations)
//}
//
//fun Application.notarizedGetSimpleLocation() {
// routing {
// route("/test") {
// notarizedGet(TestResponseInfo.testGetSimpleLocation) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}
//
//fun Application.notarizedGetNestedLocation() {
// routing {
// route("/test") {
// notarizedGet(TestResponseInfo.testGetNestedLocation) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}
//
//fun Application.notarizedPostSimpleLocation() {
// routing {
// route("/test") {
// notarizedPost(TestResponseInfo.testPostSimpleLocation) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}
//
//fun Application.notarizedPostNestedLocation() {
// routing {
// route("/test") {
// notarizedPost(TestResponseInfo.testPostNestedLocation) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}
//
//fun Application.notarizedPutSimpleLocation() {
// routing {
// route("/test") {
// notarizedPut(TestResponseInfo.testPutSimpleLocation) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}
//
//fun Application.notarizedPutNestedLocation() {
// routing {
// route("/test") {
// notarizedPut(TestResponseInfo.testPutNestedLocation) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}
//
//fun Application.notarizedDeleteSimpleLocation() {
// routing {
// route("/test") {
// notarizedDelete(TestResponseInfo.testDeleteSimpleLocation) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}
//
//fun Application.notarizedDeleteNestedLocation() {
// routing {
// route("/test") {
// notarizedDelete(TestResponseInfo.testDeleteNestedLocation) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}
//
//fun Application.notarizedGetNestedLocationFromNonLocationClass() {
// routing {
// route("/test") {
// notarizedGet(TestResponseInfo.testGetNestedLocationFromNonLocationClass) {
// call.respondText { "hey dude ‼️ congratz on the get request" }
// }
// }
// }
//}

View File

@ -1,97 +0,0 @@
package io.bkbn.kompendium.locations.util
//import io.bkbn.kompendium.core.legacy.metadata.RequestInfo
//import io.bkbn.kompendium.core.legacy.metadata.ResponseInfo
//import io.bkbn.kompendium.core.legacy.metadata.method.DeleteInfo
//import io.bkbn.kompendium.core.legacy.metadata.method.GetInfo
//import io.bkbn.kompendium.core.legacy.metadata.method.PostInfo
//import io.bkbn.kompendium.core.legacy.metadata.method.PutInfo
//import io.ktor.http.HttpStatusCode
//
//object TestResponseInfo {
// val testGetSimpleLocation = GetInfo<SimpleLoc, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
// val testPostSimpleLocation = PostInfo<SimpleLoc, SimpleRequest, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// requestInfo = RequestInfo(
// description = "Cool stuff"
// ),
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
// val testPutSimpleLocation = PutInfo<SimpleLoc, SimpleRequest, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// requestInfo = RequestInfo(
// description = "Cool stuff"
// ),
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
// val testDeleteSimpleLocation = DeleteInfo<SimpleLoc, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
// val testGetNestedLocation = GetInfo<SimpleLoc.NestedLoc, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
// val testPostNestedLocation = PostInfo<SimpleLoc.NestedLoc, SimpleRequest, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// requestInfo = RequestInfo(
// description = "Cool stuff"
// ),
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
// val testPutNestedLocation = PutInfo<SimpleLoc.NestedLoc, SimpleRequest, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// requestInfo = RequestInfo(
// description = "Cool stuff"
// ),
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
// val testDeleteNestedLocation = DeleteInfo<SimpleLoc.NestedLoc, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
//
// val testGetNestedLocationFromNonLocationClass = GetInfo<NonLocationObject.SimpleLoc.NestedLoc, SimpleResponse>(
// summary = "Location Test",
// description = "A cool test",
// responseInfo = ResponseInfo(
// status = HttpStatusCode.OK,
// description = "A successful endeavor"
// )
// )
//}

View File

@ -1,5 +1,6 @@
{
"openapi": "3.0.3",
"openapi": "3.1.0",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "Test API",
"version": "1.33.7",
@ -26,59 +27,62 @@
}
],
"paths": {
"/test/test/{name}/nesty": {
"/list/{name}/page/{page}": {
"get": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "isCool",
"in": "query",
"schema": {
"type": "boolean"
},
"required": true,
"deprecated": false
},
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"summary": "Location",
"description": "example location",
"parameters": [],
"responses": {
"200": {
"description": "A successful endeavor",
"description": "does great things",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
"$ref": "#/components/schemas/TestResponse"
}
}
}
}
},
"deprecated": false
}
},
"parameters": [
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
},
{
"name": "page",
"in": "path",
"schema": {
"type": "number",
"format": "int32"
},
"required": true,
"deprecated": false
}
]
}
},
"webhooks": {},
"components": {
"schemas": {
"SimpleResponse": {
"TestResponse": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
"c": {
"type": "string"
}
},
"required": [
"result"
],
"type": "object"
"c"
]
}
},
"securitySchemes": {}

View File

@ -0,0 +1,124 @@
{
"openapi": "3.1.0",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "Test API",
"version": "1.33.7",
"description": "An amazing, fully-ish 😉 generated API spec",
"termsOfService": "https://example.com",
"contact": {
"name": "Homer Simpson",
"url": "https://gph.is/1NPUDiM",
"email": "chunkylover53@aol.com"
},
"license": {
"name": "MIT",
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers": [
{
"url": "https://myawesomeapi.com",
"description": "Production instance of my API"
},
{
"url": "https://staging.myawesomeapi.com",
"description": "Where the fun stuff happens"
}
],
"paths": {
"/type/{name}/edit": {
"get": {
"tags": [],
"summary": "Edit",
"description": "example location",
"parameters": [],
"responses": {
"200": {
"description": "does great things",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestResponse"
}
}
}
}
},
"deprecated": false
},
"parameters": [
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
]
},
"/type/{name}/other/{page}": {
"get": {
"tags": [],
"summary": "Other",
"description": "example location",
"parameters": [],
"responses": {
"200": {
"description": "does great things",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestResponse"
}
}
}
}
},
"deprecated": false
},
"parameters": [
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
},
{
"name": "page",
"in": "path",
"schema": {
"type": "number",
"format": "int32"
},
"required": true,
"deprecated": false
}
]
}
},
"webhooks": {},
"components": {
"schemas": {
"TestResponse": {
"type": "object",
"properties": {
"c": {
"type": "string"
}
},
"required": [
"c"
]
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,88 +0,0 @@
{
"openapi": "3.0.3",
"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/test/{name}/nesty": {
"delete": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "isCool",
"in": "query",
"schema": {
"type": "boolean"
},
"required": true,
"deprecated": false
},
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,79 +0,0 @@
{
"openapi": "3.0.3",
"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/test/{name}": {
"delete": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,88 +0,0 @@
{
"openapi": "3.0.3",
"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/test/{name}/nesty": {
"get": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "isCool",
"in": "query",
"schema": {
"type": "boolean"
},
"required": true,
"deprecated": false
},
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,79 +0,0 @@
{
"openapi": "3.0.3",
"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/test/{name}": {
"get": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,110 +0,0 @@
{
"openapi": "3.0.3",
"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/test/{name}/nesty": {
"post": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "isCool",
"in": "query",
"schema": {
"type": "boolean"
},
"required": true,
"deprecated": false
},
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"requestBody": {
"description": "Cool stuff",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleRequest": {
"properties": {
"input": {
"type": "string"
}
},
"required": [
"input"
],
"type": "object"
},
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,101 +0,0 @@
{
"openapi": "3.0.3",
"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/test/{name}": {
"post": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"requestBody": {
"description": "Cool stuff",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleRequest": {
"properties": {
"input": {
"type": "string"
}
},
"required": [
"input"
],
"type": "object"
},
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,110 +0,0 @@
{
"openapi": "3.0.3",
"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/test/{name}/nesty": {
"put": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "isCool",
"in": "query",
"schema": {
"type": "boolean"
},
"required": true,
"deprecated": false
},
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"requestBody": {
"description": "Cool stuff",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleRequest": {
"properties": {
"input": {
"type": "string"
}
},
"required": [
"input"
],
"type": "object"
},
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,101 +0,0 @@
{
"openapi": "3.0.3",
"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/test/{name}": {
"put": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"requestBody": {
"description": "Cool stuff",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleRequest": {
"properties": {
"input": {
"type": "string"
}
},
"required": [
"input"
],
"type": "object"
},
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -71,7 +71,7 @@ private fun Application.mainModule() {
redoc(pageTitle = "Simple API Docs")
authenticate("basic") {
route("/{id}") {
idDocumentation()
locationDocumentation()
get {
call.respond(HttpStatusCode.OK, ExampleResponse(true))
}
@ -80,7 +80,7 @@ private fun Application.mainModule() {
}
}
private fun Route.idDocumentation() {
private fun Route.locationDocumentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(

View File

@ -47,7 +47,7 @@ private fun Application.mainModule() {
redoc(pageTitle = "Simple API Docs")
route("/{id}") {
idDocumentation()
locationDocumentation()
get {
call.respond(HttpStatusCode.OK, ExampleResponse(true))
}
@ -55,7 +55,7 @@ private fun Application.mainModule() {
}
}
private fun Route.idDocumentation() {
private fun Route.locationDocumentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(

View File

@ -54,7 +54,7 @@ private fun Application.mainModule() {
redoc(pageTitle = "Simple API Docs")
route("/{id}") {
idDocumentation()
locationDocumentation()
get {
call.respond(
HttpStatusCode.OK,
@ -68,7 +68,7 @@ private fun Application.mainModule() {
}
}
private fun Route.idDocumentation() {
private fun Route.locationDocumentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(

View File

@ -13,7 +13,6 @@ import io.bkbn.kompendium.playground.util.Util.baseSpec
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
@ -55,7 +54,7 @@ private fun Application.mainModule() {
redoc(pageTitle = "Simple API Docs")
route("/{id}") {
idDocumentation()
locationDocumentation()
get {
throw RuntimeException("This wasn't your fault I promise <3")
}
@ -63,7 +62,7 @@ private fun Application.mainModule() {
}
}
private fun Route.idDocumentation() {
private fun Route.locationDocumentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(

View File

@ -43,7 +43,7 @@ private fun Application.mainModule() {
redoc(pageTitle = "Simple API Docs")
route("/{id}") {
idDocumentation()
locationDocumentation()
get {
call.respond(HttpStatusCode.OK, ExampleResponse(true))
}
@ -51,7 +51,7 @@ private fun Application.mainModule() {
}
}
private fun Route.idDocumentation() {
private fun Route.locationDocumentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(

View File

@ -82,7 +82,7 @@ private fun Application.mainModule() {
authenticate("basic") {
redoc(pageTitle = "Simple API Docs")
route("/{id}") {
idDocumentation()
locationDocumentation()
get {
call.respond(HttpStatusCode.OK, ExampleResponse(true))
}
@ -91,7 +91,7 @@ private fun Application.mainModule() {
}
}
private fun Route.idDocumentation() {
private fun Route.locationDocumentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(

View File

@ -46,7 +46,7 @@ private fun Application.mainModule() {
redoc(pageTitle = "Simple API Docs")
route("/{id}") {
idDocumentation()
locationDocumentation()
get {
call.respond(HttpStatusCode.OK, ExampleResponse(true))
}
@ -54,7 +54,7 @@ private fun Application.mainModule() {
}
}
private fun Route.idDocumentation() {
private fun Route.locationDocumentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(

View File

@ -0,0 +1,80 @@
package io.bkbn.kompendium.playground
import io.bkbn.kompendium.core.metadata.GetInfo
import io.bkbn.kompendium.core.plugin.NotarizedApplication
import io.bkbn.kompendium.core.routes.redoc
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
import io.bkbn.kompendium.locations.NotarizedLocations
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.Listing
import io.bkbn.kompendium.playground.util.Util.baseSpec
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.engine.embeddedServer
import io.ktor.server.locations.Locations
import io.ktor.server.locations.get
import io.ktor.server.netty.Netty
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respondText
import io.ktor.server.routing.routing
import kotlinx.serialization.json.Json
fun main() {
embeddedServer(
Netty,
port = 8081,
module = Application::mainModule
).start(wait = true)
}
private fun Application.mainModule() {
install(Locations)
install(ContentNegotiation) {
json(Json {
serializersModule = KompendiumSerializersModule.module
encodeDefaults = true
explicitNulls = false
})
}
install(NotarizedApplication()) {
spec = baseSpec
}
install(NotarizedLocations()) {
locations = mapOf(
Listing::class to NotarizedLocations.LocationMetadata(
parameters = listOf(
Parameter(
name = "name",
`in` = Parameter.Location.path,
schema = TypeDefinition.STRING
),
Parameter(
name = "page",
`in` = Parameter.Location.path,
schema = TypeDefinition.INT
)
),
get = GetInfo.builder {
summary("Get user by id")
description("A very neat endpoint!")
response {
responseCode(HttpStatusCode.OK)
responseType<ExampleResponse>()
description("Will return whether or not the user is real 😱")
}
}
),
)
}
routing {
redoc(pageTitle = "Simple API Docs")
get<Listing> { listing ->
call.respondText("Listing ${listing.name}, page ${listing.page}")
}
}
}

View File

@ -1,5 +1,7 @@
package io.bkbn.kompendium.playground.util
import io.ktor.server.locations.KtorExperimentalLocationsAPI
import io.ktor.server.locations.Location
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
@ -14,3 +16,12 @@ data class CustomTypeResponse(
@Serializable
data class ExceptionResponse(val message: String)
@Location("/list/{name}/page/{page}")
data class Listing(val name: String, val page: Int)
@Location("/type/{name}") data class Type(val name: String) {
// In these classes we have to include the `name` property matching the parent.
@Location("/edit") data class Edit(val parent: Type)
@Location("/other/{page}") data class Other(val parent: Type, val page: Int)
}