More of the core functionality (#6)

This commit is contained in:
Ryan Brink
2021-04-13 13:35:55 -04:00
committed by GitHub
parent a7505483c4
commit fbf8c15694
24 changed files with 962 additions and 132 deletions

View File

@ -10,6 +10,7 @@ dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0")
testImplementation("io.ktor:ktor-server-test-host:1.5.3")
}
publishing {

View File

@ -3,7 +3,6 @@ package org.leafygreens.kompendium
import io.ktor.application.ApplicationCall
import io.ktor.http.HttpMethod
import io.ktor.routing.Route
import io.ktor.routing.createRouteFromPath
import io.ktor.routing.method
import io.ktor.util.pipeline.PipelineInterceptor
import java.lang.reflect.ParameterizedType
@ -13,83 +12,128 @@ import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
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.FormatSchema
import org.leafygreens.kompendium.models.oas.ObjectSchema
import org.leafygreens.kompendium.models.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponentSchema
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.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.meta.MethodInfo
import org.leafygreens.kompendium.util.Helpers.calculatePath
import org.leafygreens.kompendium.util.Helpers.putPairIfAbsent
object Kompendium {
val openApiSpec = OpenApiSpec(
const val COMPONENT_SLUG = "#/components/schemas"
var openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(),
servers = mutableListOf(),
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()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.get = OpenApiSpecPathItemOperation(
summary = info.summary,
description = info.description,
tags = info.tags
)
openApiSpec.paths[path]?.get = info.parseMethodInfo<Unit, TResp>()
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,
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route = generateComponentSchemas<TQ, TP, TR>(info, body) { i, b ->
): Route = generateComponentSchemas<TParam, TReq, TResp>() {
val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.post = OpenApiSpecPathItemOperation(
summary = i.summary,
description = i.description,
tags = i.tags
)
return method(HttpMethod.Post) { handle(b) }
openApiSpec.paths[path]?.post = info.parseMethodInfo<TReq, TResp>()
return method(HttpMethod.Post) { handle(body) }
}
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,
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
): Route = generateComponentSchemas<TQ, TP, TR>(info, body) { i, b ->
): Route = generateComponentSchemas<TParam, TReq, TResp>() {
val path = calculatePath()
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
openApiSpec.paths[path]?.put = OpenApiSpecPathItemOperation(
summary = i.summary,
description = i.description,
tags = i.tags
)
return method(HttpMethod.Put) { handle(b) }
openApiSpec.paths[path]?.put = info.parseMethodInfo<TReq, TResp>()
return method(HttpMethod.Put) { handle(body) }
}
@OptIn(KompendiumInternal::class)
inline fun <reified TQ : Any, reified TP : Any, reified TR : Any> generateComponentSchemas(
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
info: MethodInfo,
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
block: (MethodInfo, PipelineInterceptor<Unit, ApplicationCall>) -> Route
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): 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 {
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TQ::class))
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TR::class))
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TP::class))
return block.invoke(info, body)
if (TResp::class != Unit::class) openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TResp::class))
if (TReq::class != Unit::class) openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TReq::class))
// openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TParam::class))
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
fun objectSchemaPair(clazz: KClass<*>): Pair<String, ObjectSchema> {
val o = objectSchema(clazz)
return Pair(clazz.qualifiedName!!, o)
return Pair(clazz.simpleName!!, o)
}
private fun objectSchema(clazz: KClass<*>): ObjectSchema =
@ -115,7 +159,6 @@ object Kompendium {
return ArraySchema(fieldToSchema(listType))
}
@OptIn(KompendiumInternal::class)
private fun fieldToSchema(field: KClass<*>): OpenApiSpecComponentSchema = when (field) {
Int::class -> FormatSchema("int32", "integer")
Long::class -> FormatSchema("int64", "integer")
@ -125,4 +168,12 @@ object Kompendium {
Boolean::class -> SimpleSchema("boolean")
else -> objectSchema(field)
}
internal fun resetSchema() {
openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(),
servers = mutableListOf(),
paths = mutableMapOf()
)
}
}

View File

@ -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 = ""
)

View File

@ -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 = ""
)

View File

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

View File

@ -1,5 +0,0 @@
package org.leafygreens.kompendium.annotations
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class KompendiumModule

View File

@ -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"]
)

View File

@ -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"]
)

View File

@ -1,7 +0,0 @@
package org.leafygreens.kompendium.annotations
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
annotation class KompendiumServers(
val urls: Array<String>
)

View File

@ -1,3 +1,8 @@
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
)

View File

@ -1,9 +1,16 @@
package org.leafygreens.kompendium.models.oas
// TODO Oof -> https://swagger.io/specification/#media-type-object
data class OpenApiSpecMediaType(
val schema: OpenApiSpecSchema, // TODO sheesh -> https://swagger.io/specification/#schema-object
val example: String? = null, // TODO Enforce type? then serialize?
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
)
sealed class OpenApiSpecMediaType {
data class Explicit(
val schema: OpenApiSpecSchema, // TODO sheesh -> https://swagger.io/specification/#schema-object
val example: String? = null, // TODO Enforce type? then serialize?
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
) : OpenApiSpecMediaType()
data class Referenced(
val schema: OpenApiSpecReferenceObject
) : OpenApiSpecMediaType()
}

View File

@ -9,7 +9,7 @@ data class OpenApiSpecPathItemOperation(
var parameters: List<OpenApiSpecReferencable>? = null,
var requestBody: OpenApiSpecReferencable? = null,
// 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 deprecated: Boolean = false,
// todo big yikes... also needs to reference objects in the security scheme 🤔

View File

@ -1,13 +1,311 @@
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.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 {
@AfterTest
fun `reset kompendium`() {
Kompendium.resetSchema()
}
@Test
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")
}
@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"
)
)
)
)
}
}
}
}
}

View File

@ -14,6 +14,7 @@ import org.leafygreens.kompendium.models.oas.OpenApiSpecOAuthFlows
import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
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.OpenApiSpecSchemaArray
@ -92,25 +93,25 @@ object TestData {
requestBody = OpenApiSpecRequest(
description = "Pet object that needs to be added to the store",
content = mapOf(
"application/json" to OpenApiSpecMediaType(
"application/json" to OpenApiSpecMediaType.Explicit(
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
),
"application/xml" to OpenApiSpecMediaType(
"application/xml" to OpenApiSpecMediaType.Explicit(
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
)
),
required = true
),
responses = mapOf(
"400" to OpenApiSpecResponse(
400 to OpenApiSpecResponse(
description = "Invalid ID supplied",
content = emptyMap()
),
"404" to OpenApiSpecResponse(
404 to OpenApiSpecResponse(
description = "Pet not found",
content = emptyMap()
),
"405" to OpenApiSpecResponse(
405 to OpenApiSpecResponse(
description = "Validation exception",
content = emptyMap()
)
@ -129,16 +130,16 @@ object TestData {
requestBody = OpenApiSpecRequest(
description = "Pet object that needs to be added to the store",
content = mapOf(
"application/json" to OpenApiSpecMediaType(
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
"application/json" to OpenApiSpecMediaType.Referenced(
schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet")
),
"application/xml" to OpenApiSpecMediaType(
schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet")
"application/xml" to OpenApiSpecMediaType.Referenced(
schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet")
)
)
),
responses = mapOf(
"405" to OpenApiSpecResponse(
405 to OpenApiSpecResponse(
description = "Invalid Input",
content = emptyMap()
)
@ -174,22 +175,22 @@ object TestData {
)
),
responses = mapOf(
"200" to OpenApiSpecResponse(
200 to OpenApiSpecResponse(
description = "successful operation",
content = mapOf(
"application/xml" to OpenApiSpecMediaType(
"application/xml" to OpenApiSpecMediaType.Explicit(
schema = OpenApiSpecSchemaArray(
items = OpenApiSpecSchemaRef("#/components/schemas/Pet")
)
),
"application/json" to OpenApiSpecMediaType(
"application/json" to OpenApiSpecMediaType.Explicit(
schema = OpenApiSpecSchemaArray(
items = OpenApiSpecSchemaRef("#/components/schemas/Pet")
)
)
)
),
"400" to OpenApiSpecResponse(
400 to OpenApiSpecResponse(
description = "Invalid status value",
content = mapOf()
)

View File

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

View 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" : [ ]
}

View 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" : [ ]
}

View 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" : [ ]
}

View 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" : [ ]
}

View 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" : [ ]
}