More of the core functionality (#6)
This commit is contained in:
@ -1,3 +1,11 @@
|
|||||||
|
## [0.0.3] - April 13th, 2021
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Notarized Deletes
|
||||||
|
- Request and Response reflection abstractions
|
||||||
|
- Basic unit test coverage for each notarized operation
|
||||||
|
|
||||||
## [0.0.2] - April 12th, 2021
|
## [0.0.2] - April 12th, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
43
README.md
43
README.md
@ -10,7 +10,7 @@ Ktor native functions when implementing their API,
|
|||||||
and will supplement with Kompendium code in order
|
and will supplement with Kompendium code in order
|
||||||
to generate the appropriate spec.
|
to generate the appropriate spec.
|
||||||
|
|
||||||
## Modules
|
## In depth
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
@ -25,20 +25,23 @@ fun Application.mainModule() {
|
|||||||
routing {
|
routing {
|
||||||
route("/test") {
|
route("/test") {
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
notarizedGet(testIdGetInfo) {
|
notarizedGet<ExampleParams, ExampleResponse>(testIdGetInfo) {
|
||||||
call.respondText("get by id")
|
call.respondText("get by id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
route("/single") {
|
route("/single") {
|
||||||
notarizedGet(testSingleGetInfo) {
|
notarizedGet<ExampleRequest, ExampleResponse>(testSingleGetInfo) {
|
||||||
call.respondText("get single")
|
call.respondText("get single")
|
||||||
}
|
}
|
||||||
notarizedPost<A, B, C>(testSinglePostInfo) {
|
notarizedPost<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) {
|
||||||
call.respondText("test post")
|
call.respondText("test post")
|
||||||
}
|
}
|
||||||
notarizedPut<A, B, D>(testSinglePutInfo) {
|
notarizedPut<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) {
|
||||||
call.respondText { "hey" }
|
call.respondText { "hey" }
|
||||||
}
|
}
|
||||||
|
notarizedDelete<Unit, DeleteResponse>(testSingleDeleteInfo) {
|
||||||
|
call.respondText { "heya" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
route("/openapi.json") {
|
route("/openapi.json") {
|
||||||
@ -54,4 +57,34 @@ fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ancillary Data
|
||||||
|
data class ExampleParams(val a: String, val aa: Int)
|
||||||
|
|
||||||
|
data class ExampleNested(val nesty: String)
|
||||||
|
|
||||||
|
@KompendiumResponse(status = 204, "Entity was deleted successfully")
|
||||||
|
object DeleteResponse
|
||||||
|
|
||||||
|
@KompendiumRequest("Example Request")
|
||||||
|
data class ExampleRequest(
|
||||||
|
@KompendiumField(name = "field_name")
|
||||||
|
val fieldName: ExampleNested,
|
||||||
|
val b: Double,
|
||||||
|
val aaa: List<Long>
|
||||||
|
)
|
||||||
|
|
||||||
|
@KompendiumResponse(200, "A Successful Endeavor")
|
||||||
|
data class ExampleResponse(val c: String)
|
||||||
|
|
||||||
|
@KompendiumResponse(201, "Created Successfully")
|
||||||
|
data class ExampleCreatedResponse(val id: Int, val c: String)
|
||||||
|
|
||||||
|
object KompendiumTOC {
|
||||||
|
val testIdGetInfo = MethodInfo("Get Test", "Test for getting", tags = setOf("test", "example", "get"))
|
||||||
|
val testSingleGetInfo = MethodInfo("Another get test", "testing more")
|
||||||
|
val testSinglePostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
|
||||||
|
val testSinglePutInfo = MethodInfo("Test put endpoint", "Put your tests here!")
|
||||||
|
val testSingleDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes")
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Backbone
|
# Backbone
|
||||||
project.version=0.0.1
|
project.version=0.0.3
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
|
@ -10,6 +10,7 @@ dependencies {
|
|||||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||||
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0")
|
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0")
|
||||||
|
testImplementation("io.ktor:ktor-server-test-host:1.5.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@ -3,7 +3,6 @@ package org.leafygreens.kompendium
|
|||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.routing.createRouteFromPath
|
|
||||||
import io.ktor.routing.method
|
import io.ktor.routing.method
|
||||||
import io.ktor.util.pipeline.PipelineInterceptor
|
import io.ktor.util.pipeline.PipelineInterceptor
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
@ -13,83 +12,128 @@ import kotlin.reflect.full.findAnnotation
|
|||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
import kotlin.reflect.jvm.javaField
|
import kotlin.reflect.jvm.javaField
|
||||||
import org.leafygreens.kompendium.annotations.KompendiumField
|
import org.leafygreens.kompendium.annotations.KompendiumField
|
||||||
import org.leafygreens.kompendium.annotations.KompendiumInternal
|
import org.leafygreens.kompendium.annotations.KompendiumRequest
|
||||||
|
import org.leafygreens.kompendium.annotations.KompendiumResponse
|
||||||
|
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||||
import org.leafygreens.kompendium.models.oas.ArraySchema
|
import org.leafygreens.kompendium.models.oas.ArraySchema
|
||||||
import org.leafygreens.kompendium.models.oas.FormatSchema
|
import org.leafygreens.kompendium.models.oas.FormatSchema
|
||||||
import org.leafygreens.kompendium.models.oas.ObjectSchema
|
import org.leafygreens.kompendium.models.oas.ObjectSchema
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponentSchema
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponentSchema
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
|
||||||
import org.leafygreens.kompendium.models.oas.SimpleSchema
|
import org.leafygreens.kompendium.models.oas.SimpleSchema
|
||||||
import org.leafygreens.kompendium.models.meta.MethodInfo
|
|
||||||
import org.leafygreens.kompendium.util.Helpers.calculatePath
|
import org.leafygreens.kompendium.util.Helpers.calculatePath
|
||||||
import org.leafygreens.kompendium.util.Helpers.putPairIfAbsent
|
import org.leafygreens.kompendium.util.Helpers.putPairIfAbsent
|
||||||
|
|
||||||
object Kompendium {
|
object Kompendium {
|
||||||
val openApiSpec = OpenApiSpec(
|
|
||||||
|
const val COMPONENT_SLUG = "#/components/schemas"
|
||||||
|
|
||||||
|
var openApiSpec = OpenApiSpec(
|
||||||
info = OpenApiSpecInfo(),
|
info = OpenApiSpecInfo(),
|
||||||
servers = mutableListOf(),
|
servers = mutableListOf(),
|
||||||
paths = mutableMapOf()
|
paths = mutableMapOf()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Route.notarizedGet(info: MethodInfo, body: PipelineInterceptor<Unit, ApplicationCall>): Route {
|
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
|
||||||
|
info: MethodInfo,
|
||||||
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||||
|
): Route = generateComponentSchemas<TParam, Unit, TResp>() {
|
||||||
val path = calculatePath()
|
val path = calculatePath()
|
||||||
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||||
openApiSpec.paths[path]?.get = OpenApiSpecPathItemOperation(
|
openApiSpec.paths[path]?.get = info.parseMethodInfo<Unit, TResp>()
|
||||||
summary = info.summary,
|
|
||||||
description = info.description,
|
|
||||||
tags = info.tags
|
|
||||||
)
|
|
||||||
return method(HttpMethod.Get) { handle(body) }
|
return method(HttpMethod.Get) { handle(body) }
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified TQ : Any, reified TP : Any, reified TR : Any> Route.notarizedPost(
|
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost(
|
||||||
info: MethodInfo,
|
info: MethodInfo,
|
||||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||||
): Route = generateComponentSchemas<TQ, TP, TR>(info, body) { i, b ->
|
): Route = generateComponentSchemas<TParam, TReq, TResp>() {
|
||||||
val path = calculatePath()
|
val path = calculatePath()
|
||||||
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||||
openApiSpec.paths[path]?.post = OpenApiSpecPathItemOperation(
|
openApiSpec.paths[path]?.post = info.parseMethodInfo<TReq, TResp>()
|
||||||
summary = i.summary,
|
return method(HttpMethod.Post) { handle(body) }
|
||||||
description = i.description,
|
|
||||||
tags = i.tags
|
|
||||||
)
|
|
||||||
return method(HttpMethod.Post) { handle(b) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified TQ : Any, reified TP : Any, reified TR : Any> Route.notarizedPut(
|
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut(
|
||||||
info: MethodInfo,
|
info: MethodInfo,
|
||||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
|
||||||
): Route = generateComponentSchemas<TQ, TP, TR>(info, body) { i, b ->
|
): Route = generateComponentSchemas<TParam, TReq, TResp>() {
|
||||||
val path = calculatePath()
|
val path = calculatePath()
|
||||||
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||||
openApiSpec.paths[path]?.put = OpenApiSpecPathItemOperation(
|
openApiSpec.paths[path]?.put = info.parseMethodInfo<TReq, TResp>()
|
||||||
summary = i.summary,
|
return method(HttpMethod.Put) { handle(body) }
|
||||||
description = i.description,
|
|
||||||
tags = i.tags
|
|
||||||
)
|
|
||||||
return method(HttpMethod.Put) { handle(b) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(KompendiumInternal::class)
|
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
|
||||||
inline fun <reified TQ : Any, reified TP : Any, reified TR : Any> generateComponentSchemas(
|
|
||||||
info: MethodInfo,
|
info: MethodInfo,
|
||||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||||
block: (MethodInfo, PipelineInterceptor<Unit, ApplicationCall>) -> Route
|
): Route = generateComponentSchemas<TParam, Unit, TResp> {
|
||||||
|
val path = calculatePath()
|
||||||
|
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||||
|
openApiSpec.paths[path]?.delete = info.parseMethodInfo<Unit, TResp>()
|
||||||
|
return method(HttpMethod.Delete) { handle(body) }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified TReq, reified TResp> MethodInfo.parseMethodInfo() = OpenApiSpecPathItemOperation(
|
||||||
|
summary = this.summary,
|
||||||
|
description = this.description,
|
||||||
|
tags = this.tags,
|
||||||
|
deprecated = this.deprecated,
|
||||||
|
responses = parseResponseAnnotation<TResp>()?.let { mapOf(it) },
|
||||||
|
requestBody = parseRequestAnnotation<TReq>()
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> generateComponentSchemas(
|
||||||
|
block: () -> Route
|
||||||
): Route {
|
): Route {
|
||||||
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TQ::class))
|
if (TResp::class != Unit::class) openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TResp::class))
|
||||||
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TR::class))
|
if (TReq::class != Unit::class) openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TReq::class))
|
||||||
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TP::class))
|
// openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TParam::class))
|
||||||
return block.invoke(info, body)
|
return block.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified TReq> parseRequestAnnotation(): OpenApiSpecRequest? = when (TReq::class) {
|
||||||
|
Unit::class -> null
|
||||||
|
else -> {
|
||||||
|
val anny = TReq::class.findAnnotation<KompendiumRequest>() ?: error("My way or the highway bub")
|
||||||
|
OpenApiSpecRequest(
|
||||||
|
description = anny.description,
|
||||||
|
content = anny.mediaTypes.associate {
|
||||||
|
val ref = OpenApiSpecReferenceObject("$COMPONENT_SLUG/${TReq::class.simpleName}")
|
||||||
|
val mediaType = OpenApiSpecMediaType.Referenced(ref)
|
||||||
|
Pair(it, mediaType)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified TResp> parseResponseAnnotation(): Pair<Int, OpenApiSpecResponse>? = when (TResp::class) {
|
||||||
|
Unit::class -> null
|
||||||
|
else -> {
|
||||||
|
val anny = TResp::class.findAnnotation<KompendiumResponse>() ?: error("My way or the highway bub")
|
||||||
|
val specResponse = OpenApiSpecResponse(
|
||||||
|
description = anny.description,
|
||||||
|
content = anny.mediaTypes.associate {
|
||||||
|
val ref = OpenApiSpecReferenceObject("$COMPONENT_SLUG/${TResp::class.simpleName}")
|
||||||
|
val mediaType = OpenApiSpecMediaType.Referenced(ref)
|
||||||
|
Pair(it, mediaType)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Pair(anny.status, specResponse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@KompendiumInternal
|
|
||||||
// TODO Investigate a caching mechanism to reduce overhead... then just reference once created
|
// TODO Investigate a caching mechanism to reduce overhead... then just reference once created
|
||||||
fun objectSchemaPair(clazz: KClass<*>): Pair<String, ObjectSchema> {
|
fun objectSchemaPair(clazz: KClass<*>): Pair<String, ObjectSchema> {
|
||||||
val o = objectSchema(clazz)
|
val o = objectSchema(clazz)
|
||||||
return Pair(clazz.qualifiedName!!, o)
|
return Pair(clazz.simpleName!!, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun objectSchema(clazz: KClass<*>): ObjectSchema =
|
private fun objectSchema(clazz: KClass<*>): ObjectSchema =
|
||||||
@ -115,7 +159,6 @@ object Kompendium {
|
|||||||
return ArraySchema(fieldToSchema(listType))
|
return ArraySchema(fieldToSchema(listType))
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(KompendiumInternal::class)
|
|
||||||
private fun fieldToSchema(field: KClass<*>): OpenApiSpecComponentSchema = when (field) {
|
private fun fieldToSchema(field: KClass<*>): OpenApiSpecComponentSchema = when (field) {
|
||||||
Int::class -> FormatSchema("int32", "integer")
|
Int::class -> FormatSchema("int32", "integer")
|
||||||
Long::class -> FormatSchema("int64", "integer")
|
Long::class -> FormatSchema("int64", "integer")
|
||||||
@ -125,4 +168,12 @@ object Kompendium {
|
|||||||
Boolean::class -> SimpleSchema("boolean")
|
Boolean::class -> SimpleSchema("boolean")
|
||||||
else -> objectSchema(field)
|
else -> objectSchema(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun resetSchema() {
|
||||||
|
openApiSpec = OpenApiSpec(
|
||||||
|
info = OpenApiSpecInfo(),
|
||||||
|
servers = mutableListOf(),
|
||||||
|
paths = mutableMapOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package org.leafygreens.kompendium.annotations
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
|
||||||
annotation class KompendiumContact(
|
|
||||||
val name: String,
|
|
||||||
val url: String = "",
|
|
||||||
val email: String = ""
|
|
||||||
)
|
|
@ -1,10 +0,0 @@
|
|||||||
package org.leafygreens.kompendium.annotations
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
|
||||||
annotation class KompendiumInfo(
|
|
||||||
val title: String,
|
|
||||||
val version: String,
|
|
||||||
val description: String = "",
|
|
||||||
val termsOfService: String = ""
|
|
||||||
)
|
|
@ -1,18 +0,0 @@
|
|||||||
package org.leafygreens.kompendium.annotations
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
@RequiresOptIn(
|
|
||||||
level = RequiresOptIn.Level.WARNING,
|
|
||||||
message = "This API internal to Kompendium and should not be used. It could be removed or changed without notice."
|
|
||||||
)
|
|
||||||
@Experimental(level = Experimental.Level.WARNING)
|
|
||||||
@Target(
|
|
||||||
AnnotationTarget.CLASS,
|
|
||||||
AnnotationTarget.TYPEALIAS,
|
|
||||||
AnnotationTarget.FUNCTION,
|
|
||||||
AnnotationTarget.PROPERTY,
|
|
||||||
AnnotationTarget.FIELD,
|
|
||||||
AnnotationTarget.CONSTRUCTOR,
|
|
||||||
AnnotationTarget.PROPERTY_SETTER
|
|
||||||
)
|
|
||||||
annotation class KompendiumInternal
|
|
@ -1,5 +0,0 @@
|
|||||||
package org.leafygreens.kompendium.annotations
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
|
||||||
annotation class KompendiumModule
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.leafygreens.kompendium.annotations
|
||||||
|
|
||||||
|
annotation class KompendiumRequest(
|
||||||
|
val description: String,
|
||||||
|
val required: Boolean = true,
|
||||||
|
val mediaTypes: Array<String> = ["application/json"]
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.leafygreens.kompendium.annotations
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class KompendiumResponse(
|
||||||
|
val status: Int,
|
||||||
|
val description: String,
|
||||||
|
val mediaTypes: Array<String> = ["application/json"]
|
||||||
|
)
|
@ -1,7 +0,0 @@
|
|||||||
package org.leafygreens.kompendium.annotations
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
|
||||||
annotation class KompendiumServers(
|
|
||||||
val urls: Array<String>
|
|
||||||
)
|
|
@ -1,3 +1,8 @@
|
|||||||
package org.leafygreens.kompendium.models.meta
|
package org.leafygreens.kompendium.models.meta
|
||||||
|
|
||||||
data class MethodInfo(val summary: String, val description: String? = null, val tags: Set<String> = emptySet())
|
data class MethodInfo(
|
||||||
|
val summary: String,
|
||||||
|
val description: String? = null,
|
||||||
|
val tags: Set<String> = emptySet(),
|
||||||
|
val deprecated: Boolean = false
|
||||||
|
)
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
package org.leafygreens.kompendium.models.oas
|
package org.leafygreens.kompendium.models.oas
|
||||||
|
|
||||||
// TODO Oof -> https://swagger.io/specification/#media-type-object
|
// TODO Oof -> https://swagger.io/specification/#media-type-object
|
||||||
data class OpenApiSpecMediaType(
|
sealed class OpenApiSpecMediaType {
|
||||||
|
data class Explicit(
|
||||||
val schema: OpenApiSpecSchema, // TODO sheesh -> https://swagger.io/specification/#schema-object
|
val schema: OpenApiSpecSchema, // TODO sheesh -> https://swagger.io/specification/#schema-object
|
||||||
val example: String? = null, // TODO Enforce type? then serialize?
|
val example: String? = null, // TODO Enforce type? then serialize?
|
||||||
val examples: Map<String, String>? = null, // needs to be mutually exclusive with example
|
val examples: Map<String, String>? = null, // needs to be mutually exclusive with example
|
||||||
val encoding: Map<String, String>? = null // todo encoding object -> https://swagger.io/specification/#encoding-object
|
val encoding: Map<String, String>? = null // todo encoding object -> https://swagger.io/specification/#encoding-object
|
||||||
)
|
) : OpenApiSpecMediaType()
|
||||||
|
|
||||||
|
data class Referenced(
|
||||||
|
val schema: OpenApiSpecReferenceObject
|
||||||
|
) : OpenApiSpecMediaType()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ data class OpenApiSpecPathItemOperation(
|
|||||||
var parameters: List<OpenApiSpecReferencable>? = null,
|
var parameters: List<OpenApiSpecReferencable>? = null,
|
||||||
var requestBody: OpenApiSpecReferencable? = null,
|
var requestBody: OpenApiSpecReferencable? = null,
|
||||||
// TODO How to enforce `default` requirement 🧐
|
// TODO How to enforce `default` requirement 🧐
|
||||||
var responses: Map<String, OpenApiSpecReferencable>? = null,
|
var responses: Map<Int, OpenApiSpecReferencable>? = null,
|
||||||
var callbacks: Map<String, OpenApiSpecReferencable>? = null,
|
var callbacks: Map<String, OpenApiSpecReferencable>? = null,
|
||||||
var deprecated: Boolean = false,
|
var deprecated: Boolean = false,
|
||||||
// todo big yikes... also needs to reference objects in the security scheme 🤔
|
// todo big yikes... also needs to reference objects in the security scheme 🤔
|
||||||
|
@ -1,13 +1,311 @@
|
|||||||
package org.leafygreens.kompendium
|
package org.leafygreens.kompendium
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
import io.ktor.application.Application
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.features.ContentNegotiation
|
||||||
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.jackson.jackson
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.response.respondText
|
||||||
|
import io.ktor.routing.get
|
||||||
|
import io.ktor.routing.route
|
||||||
|
import io.ktor.routing.routing
|
||||||
|
import io.ktor.server.testing.handleRequest
|
||||||
|
import io.ktor.server.testing.withTestApplication
|
||||||
|
import java.net.URI
|
||||||
|
import kotlin.test.AfterTest
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import org.leafygreens.kompendium.Kompendium.notarizedDelete
|
||||||
|
import org.leafygreens.kompendium.Kompendium.notarizedGet
|
||||||
|
import org.leafygreens.kompendium.Kompendium.notarizedPost
|
||||||
|
import org.leafygreens.kompendium.Kompendium.notarizedPut
|
||||||
|
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
|
||||||
|
import org.leafygreens.kompendium.util.TestCreatedResponse
|
||||||
|
import org.leafygreens.kompendium.util.TestData
|
||||||
|
import org.leafygreens.kompendium.util.TestDeleteResponse
|
||||||
|
import org.leafygreens.kompendium.util.TestParams
|
||||||
|
import org.leafygreens.kompendium.util.TestRequest
|
||||||
|
import org.leafygreens.kompendium.util.TestResponse
|
||||||
|
|
||||||
internal class KompendiumTest {
|
internal class KompendiumTest {
|
||||||
|
|
||||||
|
@AfterTest
|
||||||
|
fun `reset kompendium`() {
|
||||||
|
Kompendium.resetSchema()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Kompendium can be instantiated with no details`() {
|
fun `Kompendium can be instantiated with no details`() {
|
||||||
assertEquals(Kompendium.openApiSpec.openapi, "3.0.3", "Kompendium has a default spec version of 3.0.3")
|
assertEquals(Kompendium.openApiSpec.openapi, "3.0.3", "Kompendium has a default spec version of 3.0.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Notarized Get records all expected information`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
notarizedGetModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = TestData.getFileSnapshot("notarized_get.json").trim()
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Notarized Get does not interrupt the pipeline`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
notarizedGetModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Get, "/test").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = "hey dude ‼️ congratz on the get request"
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Notarized Post records all expected information`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
notarizedPostModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = TestData.getFileSnapshot("notarized_post.json").trim()
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Notarized post does not interrupt the pipeline`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
notarizedPostModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Post, "/test").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = "hey dude ✌️ congratz on the post request"
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Notarized Put records all expected information`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
notarizedPutModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = TestData.getFileSnapshot("notarized_put.json").trim()
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Notarized put does not interrupt the pipeline`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
notarizedPutModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Put, "/test").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = "hey pal 🌝 whatcha doin' here?"
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Notarized delete records all expected information`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
notarizedDeleteModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = TestData.getFileSnapshot("notarized_delete.json").trim()
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Notarized delete does not interrupt the pipeline`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
notarizedDeleteModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val status = handleRequest(HttpMethod.Delete, "/test").response.status()
|
||||||
|
|
||||||
|
// expect
|
||||||
|
assertEquals(HttpStatusCode.NoContent, status, "No content status should be received")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Path parser stores the expected path`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
pathParsingTestModule()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = TestData.getFileSnapshot("path_parser.json").trim()
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val testGetInfo = MethodInfo("Another get test", "testing more")
|
||||||
|
val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
|
||||||
|
val testPutInfo = MethodInfo("Test put endpoint", "Put your tests here!")
|
||||||
|
val testDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.configModule() {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
jackson {
|
||||||
|
enable(SerializationFeature.INDENT_OUTPUT)
|
||||||
|
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.notarizedGetModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedGet<TestParams, TestResponse>(testGetInfo) {
|
||||||
|
call.respondText { "hey dude ‼️ congratz on the get request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.notarizedPostModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedPost<TestParams, TestRequest, TestCreatedResponse>(testPostInfo) {
|
||||||
|
call.respondText { "hey dude ✌️ congratz on the post request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.notarizedDeleteModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedDelete<TestParams, TestDeleteResponse>(testDeleteInfo) {
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.notarizedPutModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedPut<TestParams, TestRequest, TestCreatedResponse>(testPutInfo) {
|
||||||
|
call.respondText { "hey pal 🌝 whatcha doin' here?" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.pathParsingTestModule() {
|
||||||
|
routing {
|
||||||
|
route("/this") {
|
||||||
|
route("/is") {
|
||||||
|
route("/a") {
|
||||||
|
route("/complex") {
|
||||||
|
route("path") {
|
||||||
|
route("with/an/{id}") {
|
||||||
|
notarizedGet<TestParams, TestResponse>(testGetInfo) {
|
||||||
|
call.respondText { "Aww you followed this whole route 🥺" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.openApiModule() {
|
||||||
|
routing {
|
||||||
|
route("/openapi.json") {
|
||||||
|
get {
|
||||||
|
call.respond(
|
||||||
|
Kompendium.openApiSpec.copy(
|
||||||
|
info = OpenApiSpecInfo(
|
||||||
|
title = "Test API",
|
||||||
|
version = "1.33.7",
|
||||||
|
description = "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
termsOfService = URI("https://example.com"),
|
||||||
|
contact = OpenApiSpecInfoContact(
|
||||||
|
name = "Homer Simpson",
|
||||||
|
email = "chunkylover53@aol.com",
|
||||||
|
url = URI("https://gph.is/1NPUDiM")
|
||||||
|
),
|
||||||
|
license = OpenApiSpecInfoLicense(
|
||||||
|
name = "MIT",
|
||||||
|
url = URI("https://github.com/lg-backbone/kompendium/blob/main/LICENSE")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
servers = mutableListOf(
|
||||||
|
OpenApiSpecServer(
|
||||||
|
url = URI("https://myawesomeapi.com"),
|
||||||
|
description = "Production instance of my API"
|
||||||
|
),
|
||||||
|
OpenApiSpecServer(
|
||||||
|
url = URI("https://staging.myawesomeapi.com"),
|
||||||
|
description = "Where the fun stuff happens"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import org.leafygreens.kompendium.models.oas.OpenApiSpecOAuthFlows
|
|||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaArray
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaArray
|
||||||
@ -92,25 +93,25 @@ object TestData {
|
|||||||
requestBody = OpenApiSpecRequest(
|
requestBody = OpenApiSpecRequest(
|
||||||
description = "Pet object that needs to be added to the store",
|
description = "Pet object that needs to be added to the store",
|
||||||
content = mapOf(
|
content = mapOf(
|
||||||
"application/json" to OpenApiSpecMediaType(
|
"application/json" to OpenApiSpecMediaType.Explicit(
|
||||||
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
|
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
|
||||||
),
|
),
|
||||||
"application/xml" to OpenApiSpecMediaType(
|
"application/xml" to OpenApiSpecMediaType.Explicit(
|
||||||
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
|
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
required = true
|
required = true
|
||||||
),
|
),
|
||||||
responses = mapOf(
|
responses = mapOf(
|
||||||
"400" to OpenApiSpecResponse(
|
400 to OpenApiSpecResponse(
|
||||||
description = "Invalid ID supplied",
|
description = "Invalid ID supplied",
|
||||||
content = emptyMap()
|
content = emptyMap()
|
||||||
),
|
),
|
||||||
"404" to OpenApiSpecResponse(
|
404 to OpenApiSpecResponse(
|
||||||
description = "Pet not found",
|
description = "Pet not found",
|
||||||
content = emptyMap()
|
content = emptyMap()
|
||||||
),
|
),
|
||||||
"405" to OpenApiSpecResponse(
|
405 to OpenApiSpecResponse(
|
||||||
description = "Validation exception",
|
description = "Validation exception",
|
||||||
content = emptyMap()
|
content = emptyMap()
|
||||||
)
|
)
|
||||||
@ -129,16 +130,16 @@ object TestData {
|
|||||||
requestBody = OpenApiSpecRequest(
|
requestBody = OpenApiSpecRequest(
|
||||||
description = "Pet object that needs to be added to the store",
|
description = "Pet object that needs to be added to the store",
|
||||||
content = mapOf(
|
content = mapOf(
|
||||||
"application/json" to OpenApiSpecMediaType(
|
"application/json" to OpenApiSpecMediaType.Referenced(
|
||||||
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
|
schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet")
|
||||||
),
|
),
|
||||||
"application/xml" to OpenApiSpecMediaType(
|
"application/xml" to OpenApiSpecMediaType.Referenced(
|
||||||
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
|
schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
responses = mapOf(
|
responses = mapOf(
|
||||||
"405" to OpenApiSpecResponse(
|
405 to OpenApiSpecResponse(
|
||||||
description = "Invalid Input",
|
description = "Invalid Input",
|
||||||
content = emptyMap()
|
content = emptyMap()
|
||||||
)
|
)
|
||||||
@ -174,22 +175,22 @@ object TestData {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
responses = mapOf(
|
responses = mapOf(
|
||||||
"200" to OpenApiSpecResponse(
|
200 to OpenApiSpecResponse(
|
||||||
description = "successful operation",
|
description = "successful operation",
|
||||||
content = mapOf(
|
content = mapOf(
|
||||||
"application/xml" to OpenApiSpecMediaType(
|
"application/xml" to OpenApiSpecMediaType.Explicit(
|
||||||
schema = OpenApiSpecSchemaArray(
|
schema = OpenApiSpecSchemaArray(
|
||||||
items = OpenApiSpecSchemaRef("#/components/schemas/Pet")
|
items = OpenApiSpecSchemaRef("#/components/schemas/Pet")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"application/json" to OpenApiSpecMediaType(
|
"application/json" to OpenApiSpecMediaType.Explicit(
|
||||||
schema = OpenApiSpecSchemaArray(
|
schema = OpenApiSpecSchemaArray(
|
||||||
items = OpenApiSpecSchemaRef("#/components/schemas/Pet")
|
items = OpenApiSpecSchemaRef("#/components/schemas/Pet")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"400" to OpenApiSpecResponse(
|
400 to OpenApiSpecResponse(
|
||||||
description = "Invalid status value",
|
description = "Invalid status value",
|
||||||
content = mapOf()
|
content = mapOf()
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package org.leafygreens.kompendium.util
|
||||||
|
|
||||||
|
import org.leafygreens.kompendium.annotations.KompendiumField
|
||||||
|
import org.leafygreens.kompendium.annotations.KompendiumRequest
|
||||||
|
import org.leafygreens.kompendium.annotations.KompendiumResponse
|
||||||
|
|
||||||
|
data class TestParams(val a: String, val aa: Int)
|
||||||
|
|
||||||
|
data class TestNested(val nesty: String)
|
||||||
|
|
||||||
|
@KompendiumRequest("Example Request")
|
||||||
|
data class TestRequest(
|
||||||
|
@KompendiumField(name = "field_name")
|
||||||
|
val fieldName: TestNested,
|
||||||
|
val b: Double,
|
||||||
|
val aaa: List<Long>
|
||||||
|
)
|
||||||
|
|
||||||
|
@KompendiumResponse(200, "A Successful Endeavor")
|
||||||
|
data class TestResponse(val c: String)
|
||||||
|
|
||||||
|
@KompendiumResponse(201, "Created Successfully")
|
||||||
|
data class TestCreatedResponse(val id: Int, val c: String)
|
||||||
|
|
||||||
|
@KompendiumResponse(status = 204, "Entity was deleted successfully")
|
||||||
|
object TestDeleteResponse
|
58
kompendium-core/src/test/resources/notarized_delete.json
Normal file
58
kompendium-core/src/test/resources/notarized_delete.json
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"openapi" : "3.0.3",
|
||||||
|
"info" : {
|
||||||
|
"title" : "Test API",
|
||||||
|
"version" : "1.33.7",
|
||||||
|
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
|
||||||
|
"termsOfService" : "https://example.com",
|
||||||
|
"contact" : {
|
||||||
|
"name" : "Homer Simpson",
|
||||||
|
"url" : "https://gph.is/1NPUDiM",
|
||||||
|
"email" : "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license" : {
|
||||||
|
"name" : "MIT",
|
||||||
|
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers" : [ {
|
||||||
|
"url" : "https://myawesomeapi.com",
|
||||||
|
"description" : "Production instance of my API"
|
||||||
|
}, {
|
||||||
|
"url" : "https://staging.myawesomeapi.com",
|
||||||
|
"description" : "Where the fun stuff happens"
|
||||||
|
} ],
|
||||||
|
"paths" : {
|
||||||
|
"/test" : {
|
||||||
|
"delete" : {
|
||||||
|
"tags" : [ ],
|
||||||
|
"summary" : "Test delete endpoint",
|
||||||
|
"description" : "testing my deletes",
|
||||||
|
"responses" : {
|
||||||
|
"204" : {
|
||||||
|
"description" : "Entity was deleted successfully",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/TestDeleteResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated" : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components" : {
|
||||||
|
"schemas" : {
|
||||||
|
"TestDeleteResponse" : {
|
||||||
|
"properties" : { },
|
||||||
|
"type" : "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes" : { }
|
||||||
|
},
|
||||||
|
"security" : [ ],
|
||||||
|
"tags" : [ ]
|
||||||
|
}
|
62
kompendium-core/src/test/resources/notarized_get.json
Normal file
62
kompendium-core/src/test/resources/notarized_get.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"openapi" : "3.0.3",
|
||||||
|
"info" : {
|
||||||
|
"title" : "Test API",
|
||||||
|
"version" : "1.33.7",
|
||||||
|
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
|
||||||
|
"termsOfService" : "https://example.com",
|
||||||
|
"contact" : {
|
||||||
|
"name" : "Homer Simpson",
|
||||||
|
"url" : "https://gph.is/1NPUDiM",
|
||||||
|
"email" : "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license" : {
|
||||||
|
"name" : "MIT",
|
||||||
|
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers" : [ {
|
||||||
|
"url" : "https://myawesomeapi.com",
|
||||||
|
"description" : "Production instance of my API"
|
||||||
|
}, {
|
||||||
|
"url" : "https://staging.myawesomeapi.com",
|
||||||
|
"description" : "Where the fun stuff happens"
|
||||||
|
} ],
|
||||||
|
"paths" : {
|
||||||
|
"/test" : {
|
||||||
|
"get" : {
|
||||||
|
"tags" : [ ],
|
||||||
|
"summary" : "Another get test",
|
||||||
|
"description" : "testing more",
|
||||||
|
"responses" : {
|
||||||
|
"200" : {
|
||||||
|
"description" : "A Successful Endeavor",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated" : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components" : {
|
||||||
|
"schemas" : {
|
||||||
|
"TestResponse" : {
|
||||||
|
"properties" : {
|
||||||
|
"c" : {
|
||||||
|
"type" : "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes" : { }
|
||||||
|
},
|
||||||
|
"security" : [ ],
|
||||||
|
"tags" : [ ]
|
||||||
|
}
|
101
kompendium-core/src/test/resources/notarized_post.json
Normal file
101
kompendium-core/src/test/resources/notarized_post.json
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"openapi" : "3.0.3",
|
||||||
|
"info" : {
|
||||||
|
"title" : "Test API",
|
||||||
|
"version" : "1.33.7",
|
||||||
|
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
|
||||||
|
"termsOfService" : "https://example.com",
|
||||||
|
"contact" : {
|
||||||
|
"name" : "Homer Simpson",
|
||||||
|
"url" : "https://gph.is/1NPUDiM",
|
||||||
|
"email" : "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license" : {
|
||||||
|
"name" : "MIT",
|
||||||
|
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers" : [ {
|
||||||
|
"url" : "https://myawesomeapi.com",
|
||||||
|
"description" : "Production instance of my API"
|
||||||
|
}, {
|
||||||
|
"url" : "https://staging.myawesomeapi.com",
|
||||||
|
"description" : "Where the fun stuff happens"
|
||||||
|
} ],
|
||||||
|
"paths" : {
|
||||||
|
"/test" : {
|
||||||
|
"post" : {
|
||||||
|
"tags" : [ ],
|
||||||
|
"summary" : "Test post endpoint",
|
||||||
|
"description" : "Post your tests here!",
|
||||||
|
"requestBody" : {
|
||||||
|
"description" : "Example Request",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/TestRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required" : false
|
||||||
|
},
|
||||||
|
"responses" : {
|
||||||
|
"201" : {
|
||||||
|
"description" : "Created Successfully",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/TestCreatedResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated" : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components" : {
|
||||||
|
"schemas" : {
|
||||||
|
"TestCreatedResponse" : {
|
||||||
|
"properties" : {
|
||||||
|
"c" : {
|
||||||
|
"type" : "string"
|
||||||
|
},
|
||||||
|
"id" : {
|
||||||
|
"format" : "int32",
|
||||||
|
"type" : "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
},
|
||||||
|
"TestRequest" : {
|
||||||
|
"properties" : {
|
||||||
|
"aaa" : {
|
||||||
|
"items" : {
|
||||||
|
"format" : "int64",
|
||||||
|
"type" : "integer"
|
||||||
|
},
|
||||||
|
"type" : "array"
|
||||||
|
},
|
||||||
|
"b" : {
|
||||||
|
"format" : "double",
|
||||||
|
"type" : "number"
|
||||||
|
},
|
||||||
|
"field_name" : {
|
||||||
|
"properties" : {
|
||||||
|
"nesty" : {
|
||||||
|
"type" : "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes" : { }
|
||||||
|
},
|
||||||
|
"security" : [ ],
|
||||||
|
"tags" : [ ]
|
||||||
|
}
|
101
kompendium-core/src/test/resources/notarized_put.json
Normal file
101
kompendium-core/src/test/resources/notarized_put.json
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"openapi" : "3.0.3",
|
||||||
|
"info" : {
|
||||||
|
"title" : "Test API",
|
||||||
|
"version" : "1.33.7",
|
||||||
|
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
|
||||||
|
"termsOfService" : "https://example.com",
|
||||||
|
"contact" : {
|
||||||
|
"name" : "Homer Simpson",
|
||||||
|
"url" : "https://gph.is/1NPUDiM",
|
||||||
|
"email" : "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license" : {
|
||||||
|
"name" : "MIT",
|
||||||
|
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers" : [ {
|
||||||
|
"url" : "https://myawesomeapi.com",
|
||||||
|
"description" : "Production instance of my API"
|
||||||
|
}, {
|
||||||
|
"url" : "https://staging.myawesomeapi.com",
|
||||||
|
"description" : "Where the fun stuff happens"
|
||||||
|
} ],
|
||||||
|
"paths" : {
|
||||||
|
"/test" : {
|
||||||
|
"put" : {
|
||||||
|
"tags" : [ ],
|
||||||
|
"summary" : "Test put endpoint",
|
||||||
|
"description" : "Put your tests here!",
|
||||||
|
"requestBody" : {
|
||||||
|
"description" : "Example Request",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/TestRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required" : false
|
||||||
|
},
|
||||||
|
"responses" : {
|
||||||
|
"201" : {
|
||||||
|
"description" : "Created Successfully",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/TestCreatedResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated" : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components" : {
|
||||||
|
"schemas" : {
|
||||||
|
"TestCreatedResponse" : {
|
||||||
|
"properties" : {
|
||||||
|
"c" : {
|
||||||
|
"type" : "string"
|
||||||
|
},
|
||||||
|
"id" : {
|
||||||
|
"format" : "int32",
|
||||||
|
"type" : "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
},
|
||||||
|
"TestRequest" : {
|
||||||
|
"properties" : {
|
||||||
|
"aaa" : {
|
||||||
|
"items" : {
|
||||||
|
"format" : "int64",
|
||||||
|
"type" : "integer"
|
||||||
|
},
|
||||||
|
"type" : "array"
|
||||||
|
},
|
||||||
|
"b" : {
|
||||||
|
"format" : "double",
|
||||||
|
"type" : "number"
|
||||||
|
},
|
||||||
|
"field_name" : {
|
||||||
|
"properties" : {
|
||||||
|
"nesty" : {
|
||||||
|
"type" : "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes" : { }
|
||||||
|
},
|
||||||
|
"security" : [ ],
|
||||||
|
"tags" : [ ]
|
||||||
|
}
|
62
kompendium-core/src/test/resources/path_parser.json
Normal file
62
kompendium-core/src/test/resources/path_parser.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"openapi" : "3.0.3",
|
||||||
|
"info" : {
|
||||||
|
"title" : "Test API",
|
||||||
|
"version" : "1.33.7",
|
||||||
|
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
|
||||||
|
"termsOfService" : "https://example.com",
|
||||||
|
"contact" : {
|
||||||
|
"name" : "Homer Simpson",
|
||||||
|
"url" : "https://gph.is/1NPUDiM",
|
||||||
|
"email" : "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license" : {
|
||||||
|
"name" : "MIT",
|
||||||
|
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers" : [ {
|
||||||
|
"url" : "https://myawesomeapi.com",
|
||||||
|
"description" : "Production instance of my API"
|
||||||
|
}, {
|
||||||
|
"url" : "https://staging.myawesomeapi.com",
|
||||||
|
"description" : "Where the fun stuff happens"
|
||||||
|
} ],
|
||||||
|
"paths" : {
|
||||||
|
"/this/is/a/complex/path/with/an/{id}" : {
|
||||||
|
"get" : {
|
||||||
|
"tags" : [ ],
|
||||||
|
"summary" : "Another get test",
|
||||||
|
"description" : "testing more",
|
||||||
|
"responses" : {
|
||||||
|
"200" : {
|
||||||
|
"description" : "A Successful Endeavor",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated" : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components" : {
|
||||||
|
"schemas" : {
|
||||||
|
"TestResponse" : {
|
||||||
|
"properties" : {
|
||||||
|
"c" : {
|
||||||
|
"type" : "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes" : { }
|
||||||
|
},
|
||||||
|
"security" : [ ],
|
||||||
|
"tags" : [ ]
|
||||||
|
}
|
@ -4,6 +4,7 @@ import io.ktor.application.Application
|
|||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
import io.ktor.features.ContentNegotiation
|
import io.ktor.features.ContentNegotiation
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.jackson.jackson
|
import io.ktor.jackson.jackson
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.response.respondText
|
import io.ktor.response.respondText
|
||||||
@ -12,14 +13,22 @@ import io.ktor.routing.route
|
|||||||
import io.ktor.routing.routing
|
import io.ktor.routing.routing
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
|
import java.net.URI
|
||||||
|
import org.leafygreens.kompendium.Kompendium.notarizedDelete
|
||||||
import org.leafygreens.kompendium.Kompendium.notarizedGet
|
import org.leafygreens.kompendium.Kompendium.notarizedGet
|
||||||
import org.leafygreens.kompendium.Kompendium.notarizedPost
|
import org.leafygreens.kompendium.Kompendium.notarizedPost
|
||||||
import org.leafygreens.kompendium.Kompendium.notarizedPut
|
import org.leafygreens.kompendium.Kompendium.notarizedPut
|
||||||
import org.leafygreens.kompendium.Kompendium.openApiSpec
|
import org.leafygreens.kompendium.Kompendium.openApiSpec
|
||||||
import org.leafygreens.kompendium.annotations.KompendiumField
|
import org.leafygreens.kompendium.annotations.KompendiumField
|
||||||
|
import org.leafygreens.kompendium.annotations.KompendiumRequest
|
||||||
|
import org.leafygreens.kompendium.annotations.KompendiumResponse
|
||||||
import org.leafygreens.kompendium.models.meta.MethodInfo
|
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
|
||||||
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
|
||||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testIdGetInfo
|
import org.leafygreens.kompendium.playground.KompendiumTOC.testIdGetInfo
|
||||||
|
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleDeleteInfo
|
||||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo
|
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo
|
||||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePostInfo
|
import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePostInfo
|
||||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePutInfo
|
import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePutInfo
|
||||||
@ -32,21 +41,36 @@ fun main() {
|
|||||||
).start(wait = true)
|
).start(wait = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class A(val a: String, val aa: Int, val aaa: List<Long>)
|
data class ExampleParams(val a: String, val aa: Int)
|
||||||
data class B(
|
|
||||||
@KompendiumField(name = "AYY")
|
|
||||||
val a: A,
|
|
||||||
val b: Double,
|
|
||||||
)
|
|
||||||
data class C(val c: String)
|
|
||||||
|
|
||||||
data class D(val a: A, val b: B, val c: C)
|
data class ExampleNested(val nesty: String)
|
||||||
|
|
||||||
|
@KompendiumResponse(status = 204, "Entity was deleted successfully")
|
||||||
|
object DeleteResponse
|
||||||
|
|
||||||
|
@KompendiumRequest("Example Request")
|
||||||
|
data class ExampleRequest(
|
||||||
|
@KompendiumField(name = "field_name")
|
||||||
|
val fieldName: ExampleNested,
|
||||||
|
val b: Double,
|
||||||
|
val aaa: List<Long>
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val HTTP_OK = 200
|
||||||
|
private const val HTTP_CREATED = 201
|
||||||
|
|
||||||
|
@KompendiumResponse(HTTP_OK, "A Successful Endeavor")
|
||||||
|
data class ExampleResponse(val c: String)
|
||||||
|
|
||||||
|
@KompendiumResponse(HTTP_CREATED, "Created Successfully")
|
||||||
|
data class ExampleCreatedResponse(val id: Int, val c: String)
|
||||||
|
|
||||||
object KompendiumTOC {
|
object KompendiumTOC {
|
||||||
val testIdGetInfo = MethodInfo("Get Test", "Test for getting", tags = setOf("test", "example", "get"))
|
val testIdGetInfo = MethodInfo("Get Test", "Test for getting", tags = setOf("test", "example", "get"))
|
||||||
val testSingleGetInfo = MethodInfo("Another get test", "testing more")
|
val testSingleGetInfo = MethodInfo("Another get test", "testing more")
|
||||||
val testSinglePostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
|
val testSinglePostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
|
||||||
val testSinglePutInfo = MethodInfo("Test put endpoint", "Put your tests here!")
|
val testSinglePutInfo = MethodInfo("Test put endpoint", "Put your tests here!")
|
||||||
|
val testSingleDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Application.mainModule() {
|
fun Application.mainModule() {
|
||||||
@ -56,31 +80,56 @@ fun Application.mainModule() {
|
|||||||
routing {
|
routing {
|
||||||
route("/test") {
|
route("/test") {
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
notarizedGet(testIdGetInfo) {
|
notarizedGet<ExampleParams, ExampleResponse>(testIdGetInfo) {
|
||||||
call.respondText("get by id")
|
call.respondText("get by id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
route("/single") {
|
route("/single") {
|
||||||
notarizedGet(testSingleGetInfo) {
|
notarizedGet<ExampleRequest, ExampleResponse>(testSingleGetInfo) {
|
||||||
call.respondText("get single")
|
call.respondText("get single")
|
||||||
}
|
}
|
||||||
notarizedPost<A, B, C>(testSinglePostInfo) {
|
notarizedPost<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePostInfo) {
|
||||||
call.respondText("test post")
|
call.respondText("test post")
|
||||||
}
|
}
|
||||||
notarizedPut<A, B, D>(testSinglePutInfo) {
|
notarizedPut<ExampleParams, ExampleRequest, ExampleCreatedResponse>(testSinglePutInfo) {
|
||||||
call.respondText { "hey" }
|
call.respondText { "hey" }
|
||||||
}
|
}
|
||||||
|
notarizedDelete<Unit, DeleteResponse>(testSingleDeleteInfo) {
|
||||||
|
call.respondText { "heya" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
route("/openapi.json") {
|
route("/openapi.json") {
|
||||||
get {
|
get {
|
||||||
call.respond(openApiSpec.copy(
|
call.respond(
|
||||||
|
openApiSpec.copy(
|
||||||
info = OpenApiSpecInfo(
|
info = OpenApiSpecInfo(
|
||||||
title = "Test API",
|
title = "Test API",
|
||||||
version = "1.3.3.7",
|
version = "1.33.7",
|
||||||
description = "An amazing, fully-ish 😉 generated API spec"
|
description = "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
termsOfService = URI("https://example.com"),
|
||||||
|
contact = OpenApiSpecInfoContact(
|
||||||
|
name = "Homer Simpson",
|
||||||
|
email = "chunkylover53@aol.com",
|
||||||
|
url = URI("https://gph.is/1NPUDiM")
|
||||||
|
),
|
||||||
|
license = OpenApiSpecInfoLicense(
|
||||||
|
name = "MIT",
|
||||||
|
url = URI("https://github.com/lg-backbone/kompendium/blob/main/LICENSE")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
servers = mutableListOf(
|
||||||
|
OpenApiSpecServer(
|
||||||
|
url = URI("https://myawesomeapi.com"),
|
||||||
|
description = "Production instance of my API"
|
||||||
|
),
|
||||||
|
OpenApiSpecServer(
|
||||||
|
url = URI("https://staging.myawesomeapi.com"),
|
||||||
|
description = "Where the fun stuff happens"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user