From 81e24f96dc0efb64de48e7b9db58ae61a62ae82b Mon Sep 17 00:00:00 2001 From: Ryan Brink <5607577+rgbrizzlehizzle@users.noreply.github.com> Date: Sat, 17 Apr 2021 08:32:43 -0400 Subject: [PATCH] document top level collectiom (#24) --- CHANGELOG.md | 7 ++ README.md | 72 ++++-------- gradle.properties | 2 +- .../org/leafygreens/kompendium/Kompendium.kt | 79 +++++++------ .../org/leafygreens/kompendium/Kontent.kt | 2 +- .../annotations/KompendiumRequest.kt | 7 -- .../annotations/KompendiumResponse.kt | 9 -- .../kompendium/models/meta/MethodInfo.kt | 3 + .../kompendium/models/meta/RequestInfo.kt | 7 ++ .../kompendium/models/meta/ResponseInfo.kt | 7 ++ .../leafygreens/kompendium/util/Helpers.kt | 5 + .../leafygreens/kompendium/KompendiumTest.kt | 41 ++++++- .../leafygreens/kompendium/util/TestModels.kt | 7 -- .../src/test/resources/complex_type.json | 4 +- .../src/test/resources/notarized_delete.json | 2 +- .../src/test/resources/notarized_post.json | 4 +- .../test/resources/notarized_primitives.json | 23 ++++ .../src/test/resources/notarized_put.json | 4 +- .../src/test/resources/response_list.json | 71 +++++++++++ .../leafygreens/kompendium/playground/Main.kt | 110 ++++++++++++------ 20 files changed, 308 insertions(+), 158 deletions(-) delete mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumRequest.kt delete mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumResponse.kt create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/RequestInfo.kt create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/ResponseInfo.kt create mode 100644 kompendium-core/src/test/resources/response_list.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0d52cc4..b30c6760d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.3.0] - April 17th, 2021 + +### Changed + +- Removed response and request annotations in favor of MethodInfo extension. +- Modified notarization to add the correct reference slug regardless of type + ## [0.2.0] - April 16th, 2021 ### Changed diff --git a/README.md b/README.md index e73583487..135e09382 100644 --- a/README.md +++ b/README.md @@ -68,26 +68,39 @@ meaning that swapping in a default Ktor route and a Kompendium `notarized` route ### Supplemental Annotations In general, Kompendium tries to limit the number of annotations that developers need to use in order to get an app -integrated. However, there are a couple areas that it made sense, at least for an MVP. +integrated. -Currently, there are three Kompendium annotations +Currently, there is only a single Kompendium annotation -- `KompendiumRequest` -- `KompendiumResponse` - `KompendiumField` -These are aimed at offering modifications at the request, response, and field level respectively, and offer things such -as response status codes, field name overrides, and OpenApi metadata such as `description`. +The intended purpose is to offer field level overrides such as naming conventions (ie snake instead of camel). ## Examples +The full source code can be found in the `kompendium-playground` module. Here we show just the adjustments +needed to a standard Ktor server to get up and running in Kompendium. + ```kotlin // Minimal API Example +fun main() { + embeddedServer( + Netty, + port = 8081, + module = Application::mainModule + ).start(wait = true) +} + fun Application.mainModule() { install(ContentNegotiation) { - jackson() + jackson { + enable(SerializationFeature.INDENT_OUTPUT) + setSerializationInclusion(JsonInclude.Include.NON_NULL) + } } routing { + openApi() + redoc() route("/test") { route("/{id}") { notarizedGet(testIdGetInfo) { @@ -109,52 +122,13 @@ fun Application.mainModule() { } } } - route("/openapi.json") { - get { - call.respond(openApiSpec.copy( - info = OpenApiSpecInfo( - title = "Test API", - version = "1.3.3.7", - description = "An amazing, fully-ish 😉 generated API spec" - ) - )) - } - } } } - -// Ancillary Data -data class ExampleParams(val a: String, val aa: Int) - -data class ExampleNested(val nesty: String) - -@KompendiumResponse(status = KompendiumHttpCodes.NO_CONTENT, "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 -) - -@KompendiumResponse(KompendiumHttpCodes.OK, "A Successful Endeavor") -data class ExampleResponse(val c: String) - -@KompendiumResponse(KompendiumHttpCodes.CREATED, "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") -} ``` -This example would output the following json spec https://gist.github.com/rgbrizzlehizzle/b9544922f2e99a2815177f8bdbf80668 +When run in the playground, this would output the following at `/openapi.json` + +https://gist.github.com/rgbrizzlehizzle/b9544922f2e99a2815177f8bdbf80668 ## Limitations diff --git a/gradle.properties b/gradle.properties index 83b6aa9f3..4676f08e1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Kompendium -project.version=0.2.0 +project.version=0.3.0 # Kotlin kotlin.code.style=official # Gradle diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt index 7701d92f2..3251865ef 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt @@ -5,11 +5,12 @@ import io.ktor.http.HttpMethod import io.ktor.routing.Route import io.ktor.routing.method import io.ktor.util.pipeline.PipelineInterceptor -import kotlin.reflect.full.findAnnotation +import kotlin.reflect.KType +import kotlin.reflect.typeOf import org.leafygreens.kompendium.Kontent.generateKontent -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.RequestInfo +import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.oas.OpenApiSpec import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType @@ -18,8 +19,8 @@ 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.util.Helpers.COMPONENT_SLUG import org.leafygreens.kompendium.util.Helpers.calculatePath +import org.leafygreens.kompendium.util.Helpers.getReferenceSlug object Kompendium { @@ -29,94 +30,102 @@ object Kompendium { paths = mutableMapOf() ) + @OptIn(ExperimentalStdlibApi::class) inline fun Route.notarizedGet( info: MethodInfo, noinline body: PipelineInterceptor - ): Route = generateComponentSchemas() { + ): Route = notarizationPreFlight() { requestType, responseType -> val path = calculatePath() openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } - openApiSpec.paths[path]?.get = info.parseMethodInfo() + openApiSpec.paths[path]?.get = info.parseMethodInfo(HttpMethod.Get, requestType, responseType) return method(HttpMethod.Get) { handle(body) } } inline fun Route.notarizedPost( info: MethodInfo, noinline body: PipelineInterceptor - ): Route = generateComponentSchemas() { + ): Route = notarizationPreFlight() { requestType, responseType -> val path = calculatePath() openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } - openApiSpec.paths[path]?.post = info.parseMethodInfo() + openApiSpec.paths[path]?.post = info.parseMethodInfo(HttpMethod.Post, requestType, responseType) return method(HttpMethod.Post) { handle(body) } } inline fun Route.notarizedPut( info: MethodInfo, noinline body: PipelineInterceptor, - ): Route = generateComponentSchemas() { + ): Route = notarizationPreFlight() { requestType, responseType -> val path = calculatePath() openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } - openApiSpec.paths[path]?.put = info.parseMethodInfo() + openApiSpec.paths[path]?.put = info.parseMethodInfo(HttpMethod.Put, requestType, responseType) return method(HttpMethod.Put) { handle(body) } } inline fun Route.notarizedDelete( info: MethodInfo, noinline body: PipelineInterceptor - ): Route = generateComponentSchemas { + ): Route = notarizationPreFlight { requestType, responseType -> val path = calculatePath() openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() } - openApiSpec.paths[path]?.delete = info.parseMethodInfo() + openApiSpec.paths[path]?.delete = info.parseMethodInfo(HttpMethod.Delete, requestType, responseType) return method(HttpMethod.Delete) { handle(body) } } - inline fun MethodInfo.parseMethodInfo() = OpenApiSpecPathItemOperation( + // TODO here down is a mess, needs refactor once core functionality is in place + fun MethodInfo.parseMethodInfo( + method: HttpMethod, + requestType: KType, + responseType: KType + ) = OpenApiSpecPathItemOperation( summary = this.summary, description = this.description, tags = this.tags, deprecated = this.deprecated, - responses = parseResponseAnnotation()?.let { mapOf(it) }, - requestBody = parseRequestAnnotation() + responses = responseType.toSpec(responseInfo)?.let { mapOf(it) }, + requestBody = if (method != HttpMethod.Get) requestType.toSpec(requestInfo) else null ) - inline fun generateComponentSchemas( - block: () -> Route + @OptIn(ExperimentalStdlibApi::class) + inline fun notarizationPreFlight( + block: (KType, KType) -> Route ): Route { val responseKontent = generateKontent() val requestKontent = generateKontent() openApiSpec.components.schemas.putAll(responseKontent) openApiSpec.components.schemas.putAll(requestKontent) - return block.invoke() + val requestType = typeOf() + val responseType = typeOf() + return block.invoke(requestType, responseType) } - inline fun parseRequestAnnotation(): OpenApiSpecRequest? = when (TReq::class) { + // TODO These two lookin' real similar 👀 Combine? + private fun KType.toSpec(requestInfo: RequestInfo?): OpenApiSpecRequest? = when (this) { Unit::class -> null - else -> when (val anny = TReq::class.findAnnotation()) { + else -> when (requestInfo) { null -> null else -> OpenApiSpecRequest( - description = anny.description, - content = anny.mediaTypes.associate { - val ref = OpenApiSpecReferenceObject("$COMPONENT_SLUG/${TReq::class.simpleName}") - val mediaType = OpenApiSpecMediaType.Referenced(ref) - Pair(it, mediaType) + description = requestInfo.description, + content = requestInfo.mediaTypes.associateWith { + val ref = getReferenceSlug() + OpenApiSpecMediaType.Referenced(OpenApiSpecReferenceObject(ref)) } ) } } - inline fun parseResponseAnnotation(): Pair? = when (TResp::class) { - Unit::class -> null - else -> when (val anny = TResp::class.findAnnotation()) { - null -> null + private fun KType.toSpec(responseInfo: ResponseInfo?): Pair? = when (this) { + Unit::class -> null // TODO Maybe not though? could be unit but 200 🤔 + else -> when (responseInfo) { + null -> null // TODO again probably revisit this else -> { 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) + description = responseInfo.description, + content = responseInfo.mediaTypes.associateWith { + val ref = getReferenceSlug() + OpenApiSpecMediaType.Referenced(OpenApiSpecReferenceObject(ref)) } ) - Pair(anny.status, specResponse) + Pair(responseInfo.status, specResponse) } } } diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kontent.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kontent.kt index 1c50d67a5..b1883baa0 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kontent.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kontent.kt @@ -99,7 +99,7 @@ object Kontent { val referenceName = genericNameAdapter(type, clazz) val valueReference = ReferencedSchema("$COMPONENT_SLUG/$valClassName") val schema = DictionarySchema(additionalProperties = valueReference) - val updatedCache = generateKTypeKontent(valType!!, cache) + val updatedCache = generateKTypeKontent(valType, cache) return updatedCache.plus(referenceName to schema) } diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumRequest.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumRequest.kt deleted file mode 100644 index 01756a512..000000000 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.leafygreens.kompendium.annotations - -annotation class KompendiumRequest( - val description: String, - val required: Boolean = true, - val mediaTypes: Array = ["application/json"] -) diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumResponse.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumResponse.kt deleted file mode 100644 index adc61ee19..000000000 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumResponse.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.leafygreens.kompendium.annotations - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.CLASS) -annotation class KompendiumResponse( - val status: Int, - val description: String, - val mediaTypes: Array = ["application/json"] -) diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/MethodInfo.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/MethodInfo.kt index fd5c2d5f3..de8207be0 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/MethodInfo.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/MethodInfo.kt @@ -1,8 +1,11 @@ package org.leafygreens.kompendium.models.meta +// TODO Seal and extend by method type? data class MethodInfo( val summary: String, val description: String? = null, + val responseInfo: ResponseInfo? = null, + val requestInfo: RequestInfo? = null, val tags: Set = emptySet(), val deprecated: Boolean = false ) diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/RequestInfo.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/RequestInfo.kt new file mode 100644 index 000000000..95f44952b --- /dev/null +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/RequestInfo.kt @@ -0,0 +1,7 @@ +package org.leafygreens.kompendium.models.meta + +data class RequestInfo( + val description: String, + val required: Boolean = true, + val mediaTypes: List = listOf("application/json") +) diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/ResponseInfo.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/ResponseInfo.kt new file mode 100644 index 000000000..018141f39 --- /dev/null +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/ResponseInfo.kt @@ -0,0 +1,7 @@ +package org.leafygreens.kompendium.models.meta + +data class ResponseInfo( + val status: Int, // TODO How to handle error codes? + val description: String, + val mediaTypes: List = listOf("application/json") +) diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt index 81e12cceb..1ed5bcf8b 100644 --- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt +++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt @@ -92,6 +92,11 @@ object Helpers { return result } + fun KType.getReferenceSlug(): String = when { + arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}" + else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}" + } + /** * Will build a reference slug that is useful for schema caching and references, particularly * in the case of a class with type parameters diff --git a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt index 610fa269c..ccd2670be 100644 --- a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt +++ b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt @@ -25,11 +25,14 @@ 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.meta.RequestInfo +import org.leafygreens.kompendium.models.meta.ResponseInfo 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.ComplexRequest +import org.leafygreens.kompendium.util.KompendiumHttpCodes import org.leafygreens.kompendium.util.TestCreatedResponse import org.leafygreens.kompendium.util.TestData import org.leafygreens.kompendium.util.TestDeleteResponse @@ -289,11 +292,31 @@ internal class KompendiumTest { } } + @Test + fun `Can notarize a top level list response`() { + withTestApplication({ + configModule() + openApiModule() + returnsList() + }) { + // do + val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content + + // expect + val expected = TestData.getFileSnapshot("response_list.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") + val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor") + val testPostResponse = ResponseInfo(KompendiumHttpCodes.CREATED, "A Successful Endeavor") + val testDeleteResponse = ResponseInfo(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor") + val testRequest = RequestInfo("A Test request") + val testGetInfo = MethodInfo("Another get test", "testing more", testGetResponse) + val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!", testPostResponse, testRequest) + val testPutInfo = MethodInfo("Test put endpoint", "Put your tests here!", testPostResponse, testRequest) + val testDeleteInfo = MethodInfo("Test delete endpoint", "testing my deletes", testDeleteResponse) } private fun Application.configModule() { @@ -387,6 +410,16 @@ internal class KompendiumTest { } } + private fun Application.returnsList() { + routing { + route("/test") { + notarizedGet>(testGetInfo) { + call.respondText { "hey dude ur doing amazing work!" } + } + } + } + } + private fun Application.complexType() { routing { route("/test") { diff --git a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestModels.kt b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestModels.kt index 071bf4542..7b84b7173 100644 --- a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestModels.kt +++ b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/util/TestModels.kt @@ -2,8 +2,6 @@ package org.leafygreens.kompendium.util import java.util.UUID import org.leafygreens.kompendium.annotations.KompendiumField -import org.leafygreens.kompendium.annotations.KompendiumRequest -import org.leafygreens.kompendium.annotations.KompendiumResponse data class TestSimpleModel(val a: String, val b: Int) @@ -25,7 +23,6 @@ data class TestNested(val nesty: String) data class TestWithUUID(val id: UUID) -@KompendiumRequest("Example Request") data class TestRequest( @KompendiumField(name = "field_name") val fieldName: TestNested, @@ -33,16 +30,12 @@ data class TestRequest( val aaa: List ) -@KompendiumResponse(KompendiumHttpCodes.OK, "A Successful Endeavor") data class TestResponse(val c: String) -@KompendiumResponse(KompendiumHttpCodes.CREATED, "Created Successfully") data class TestCreatedResponse(val id: Int, val c: String) -@KompendiumResponse(KompendiumHttpCodes.NO_CONTENT, "Entity was deleted successfully") object TestDeleteResponse -@KompendiumRequest("Request object to create a backbone project") data class ComplexRequest( val org: String, @KompendiumField("amazing_field") diff --git a/kompendium-core/src/test/resources/complex_type.json b/kompendium-core/src/test/resources/complex_type.json index 54e9152ce..2a993a9fd 100644 --- a/kompendium-core/src/test/resources/complex_type.json +++ b/kompendium-core/src/test/resources/complex_type.json @@ -29,7 +29,7 @@ "summary" : "Test put endpoint", "description" : "Put your tests here!", "requestBody" : { - "description" : "Request object to create a backbone project", + "description" : "A Test request", "content" : { "application/json" : { "schema" : { @@ -40,7 +40,7 @@ "required" : false }, "responses" : { - "200" : { + "201" : { "description" : "A Successful Endeavor", "content" : { "application/json" : { diff --git a/kompendium-core/src/test/resources/notarized_delete.json b/kompendium-core/src/test/resources/notarized_delete.json index dec95e2d8..0b442e467 100644 --- a/kompendium-core/src/test/resources/notarized_delete.json +++ b/kompendium-core/src/test/resources/notarized_delete.json @@ -30,7 +30,7 @@ "description" : "testing my deletes", "responses" : { "204" : { - "description" : "Entity was deleted successfully", + "description" : "A Successful Endeavor", "content" : { "application/json" : { "schema" : { diff --git a/kompendium-core/src/test/resources/notarized_post.json b/kompendium-core/src/test/resources/notarized_post.json index 1d8f4767d..d6001d461 100644 --- a/kompendium-core/src/test/resources/notarized_post.json +++ b/kompendium-core/src/test/resources/notarized_post.json @@ -29,7 +29,7 @@ "summary" : "Test post endpoint", "description" : "Post your tests here!", "requestBody" : { - "description" : "Example Request", + "description" : "A Test request", "content" : { "application/json" : { "schema" : { @@ -41,7 +41,7 @@ }, "responses" : { "201" : { - "description" : "Created Successfully", + "description" : "A Successful Endeavor", "content" : { "application/json" : { "schema" : { diff --git a/kompendium-core/src/test/resources/notarized_primitives.json b/kompendium-core/src/test/resources/notarized_primitives.json index 90c36ea96..b293f346d 100644 --- a/kompendium-core/src/test/resources/notarized_primitives.json +++ b/kompendium-core/src/test/resources/notarized_primitives.json @@ -28,6 +28,29 @@ "tags" : [ ], "summary" : "Test put endpoint", "description" : "Put your tests here!", + "requestBody" : { + "description" : "A Test request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Int" + } + } + }, + "required" : false + }, + "responses" : { + "201" : { + "description" : "A Successful Endeavor", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Boolean" + } + } + } + } + }, "deprecated" : false } } diff --git a/kompendium-core/src/test/resources/notarized_put.json b/kompendium-core/src/test/resources/notarized_put.json index 41f146b85..5a759a6ae 100644 --- a/kompendium-core/src/test/resources/notarized_put.json +++ b/kompendium-core/src/test/resources/notarized_put.json @@ -29,7 +29,7 @@ "summary" : "Test put endpoint", "description" : "Put your tests here!", "requestBody" : { - "description" : "Example Request", + "description" : "A Test request", "content" : { "application/json" : { "schema" : { @@ -41,7 +41,7 @@ }, "responses" : { "201" : { - "description" : "Created Successfully", + "description" : "A Successful Endeavor", "content" : { "application/json" : { "schema" : { diff --git a/kompendium-core/src/test/resources/response_list.json b/kompendium-core/src/test/resources/response_list.json new file mode 100644 index 000000000..afb775e8c --- /dev/null +++ b/kompendium-core/src/test/resources/response_list.json @@ -0,0 +1,71 @@ +{ + "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/List-TestResponse" + } + } + } + } + }, + "deprecated" : false + } + } + }, + "components" : { + "schemas" : { + "String" : { + "type" : "string" + }, + "TestResponse" : { + "properties" : { + "c" : { + "$ref" : "#/components/schemas/String" + } + }, + "type" : "object" + }, + "List-TestResponse" : { + "items" : { + "$ref" : "#/components/schemas/TestResponse" + }, + "type" : "array" + } + }, + "securitySchemes" : { } + }, + "security" : [ ], + "tags" : [ ] +} diff --git a/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt index 5a6e82833..78a417db7 100644 --- a/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt +++ b/kompendium-playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt @@ -7,7 +7,6 @@ import io.ktor.application.call import io.ktor.application.install import io.ktor.features.ContentNegotiation import io.ktor.html.respondHtml -import io.ktor.http.HttpStatusCode import io.ktor.jackson.jackson import io.ktor.response.respond import io.ktor.response.respondText @@ -32,9 +31,9 @@ import org.leafygreens.kompendium.Kompendium.notarizedPost import org.leafygreens.kompendium.Kompendium.notarizedPut import org.leafygreens.kompendium.Kompendium.openApiSpec 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.RequestInfo +import org.leafygreens.kompendium.models.meta.ResponseInfo import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense @@ -54,38 +53,6 @@ fun main() { ).start(wait = true) } -data class ExampleParams(val a: String, val aa: Int) - -data class ExampleNested(val nesty: String) - -@KompendiumResponse(KompendiumHttpCodes.NO_CONTENT, "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 -) - -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 { - 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") -} - fun Application.mainModule() { install(ContentNegotiation) { jackson { @@ -120,6 +87,74 @@ fun Application.mainModule() { } } +data class ExampleParams(val a: String, val aa: Int) + +data class ExampleNested(val nesty: String) + +object DeleteResponse + +data class ExampleRequest( + @KompendiumField(name = "field_name") + val fieldName: ExampleNested, + val b: Double, + val aaa: List +) + +data class ExampleResponse(val c: String) + +data class ExampleCreatedResponse(val id: Int, val c: String) + +object KompendiumTOC { + val testIdGetInfo = MethodInfo( + summary = "Get Test", + description = "Test for the getting", + tags = setOf("test", "sample", "get"), + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.OK, + description = "Returns sample info" + ) + ) + val testSingleGetInfo = MethodInfo( + summary = "Another get test", + description = "testing more", + tags = setOf("anotherTest", "sample"), + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.OK, + description = "Returns a different sample" + ) + ) + val testSinglePostInfo = MethodInfo( + summary = "Test post endpoint", + description = "Post your tests here!", + requestInfo = RequestInfo( + description = "Simple request body" + ), + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.CREATED, + description = "Worlds most complex response" + ) + ) + val testSinglePutInfo = MethodInfo( + summary = "Test put endpoint", + description = "Put your tests here!", + requestInfo = RequestInfo( + description = "Info needed to perform this put request" + ), + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.CREATED, + description = "What we give you when u do the puts" + ) + ) + val testSingleDeleteInfo = MethodInfo( + summary = "Test delete endpoint", + description = "testing my deletes", + responseInfo = ResponseInfo( + status = KompendiumHttpCodes.NO_CONTENT, + description = "Signifies that your item was deleted succesfully" + ) + ) +} + fun Routing.openApi() { route("/openapi.json") { get { @@ -162,8 +197,7 @@ fun Routing.redoc() { call.respondHtml { head { title { - // TODO Make this load project title - +"Docs" + +"${openApiSpec.info.title}" } meta { charset = "utf-8" @@ -183,7 +217,7 @@ fun Routing.redoc() { } } body { - // TODO Make this its own DSL class + // TODO needs to mirror openApi route unsafe { +"" } script { src = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"