diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c0391c3b..49d3185bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## [0.5.0] - April 19th, 2021
+
+### Added
+
+- Expose `/openapi.json` and `/docs` as opt-in pre-built Routes
+
## [0.4.0] - April 17th, 2021
### Added
diff --git a/gradle.properties b/gradle.properties
index 1c879250a..4ed214c41 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
# Kompendium
-project.version=0.4.0
+project.version=0.5.0
# Kotlin
kotlin.code.style=official
# Gradle
diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/routes/OpenApiRoute.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/routes/OpenApiRoute.kt
new file mode 100644
index 000000000..bb91eb674
--- /dev/null
+++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/routes/OpenApiRoute.kt
@@ -0,0 +1,16 @@
+package org.leafygreens.kompendium.routes
+
+import io.ktor.application.call
+import io.ktor.response.respond
+import io.ktor.routing.Routing
+import io.ktor.routing.get
+import io.ktor.routing.route
+import org.leafygreens.kompendium.models.oas.OpenApiSpec
+
+fun Routing.openApi(oas: OpenApiSpec) {
+ route("/openapi.json") {
+ get {
+ call.respond(oas)
+ }
+ }
+}
diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/routes/Redoc.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/routes/Redoc.kt
new file mode 100644
index 000000000..c7bd09fbf
--- /dev/null
+++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/routes/Redoc.kt
@@ -0,0 +1,53 @@
+package org.leafygreens.kompendium.routes
+
+import io.ktor.application.call
+import io.ktor.html.respondHtml
+import io.ktor.routing.Routing
+import io.ktor.routing.get
+import io.ktor.routing.route
+import kotlinx.html.body
+import kotlinx.html.head
+import kotlinx.html.link
+import kotlinx.html.meta
+import kotlinx.html.script
+import kotlinx.html.style
+import kotlinx.html.title
+import kotlinx.html.unsafe
+import org.leafygreens.kompendium.models.oas.OpenApiSpec
+
+fun Routing.redoc(oas: OpenApiSpec) {
+ route("/docs") {
+ get {
+ call.respondHtml {
+ head {
+ title {
+ +"${oas.info.title}"
+ }
+ meta {
+ charset = "utf-8"
+ }
+ meta {
+ name = "viewport"
+ content = "width=device-width, initial-scale=1"
+ }
+ link {
+ href = "https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
+ rel = "stylesheet"
+ }
+ style {
+ unsafe {
+ raw("body { margin: 0; padding: 0; }")
+ }
+ }
+ }
+ body {
+ // TODO needs to mirror openApi route
+ unsafe { +"" }
+ script {
+ src = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"
+ }
+ }
+ }
+ }
+ }
+}
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 9a06a7b50..b565fd108 100644
--- a/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt
+++ b/kompendium-core/src/test/kotlin/org/leafygreens/kompendium/KompendiumTest.kt
@@ -11,7 +11,6 @@ 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
@@ -31,11 +30,12 @@ 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.routes.openApi
+import org.leafygreens.kompendium.routes.redoc
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
import org.leafygreens.kompendium.util.TestParams
import org.leafygreens.kompendium.util.TestRequest
import org.leafygreens.kompendium.util.TestResponse
@@ -56,7 +56,7 @@ internal class KompendiumTest {
fun `Notarized Get records all expected information`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
notarizedGetModule()
}) {
// do
@@ -72,7 +72,7 @@ internal class KompendiumTest {
fun `Notarized Get does not interrupt the pipeline`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
notarizedGetModule()
}) {
// do
@@ -88,7 +88,7 @@ internal class KompendiumTest {
fun `Notarized Post records all expected information`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
notarizedPostModule()
}) {
// do
@@ -104,7 +104,7 @@ internal class KompendiumTest {
fun `Notarized post does not interrupt the pipeline`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
notarizedPostModule()
}) {
// do
@@ -120,7 +120,7 @@ internal class KompendiumTest {
fun `Notarized Put records all expected information`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
notarizedPutModule()
}) {
// do
@@ -137,7 +137,7 @@ internal class KompendiumTest {
fun `Notarized put does not interrupt the pipeline`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
notarizedPutModule()
}) {
// do
@@ -153,7 +153,7 @@ internal class KompendiumTest {
fun `Notarized delete records all expected information`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
notarizedDeleteModule()
}) {
// do
@@ -169,7 +169,7 @@ internal class KompendiumTest {
fun `Notarized delete does not interrupt the pipeline`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
notarizedDeleteModule()
}) {
// do
@@ -184,7 +184,7 @@ internal class KompendiumTest {
fun `Path parser stores the expected path`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
pathParsingTestModule()
}) {
// do
@@ -200,7 +200,7 @@ internal class KompendiumTest {
fun `Can notarize the root route`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
rootModule()
}) {
// do
@@ -216,7 +216,7 @@ internal class KompendiumTest {
fun `Can call the root route`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
rootModule()
}) {
// do
@@ -232,7 +232,7 @@ internal class KompendiumTest {
fun `Can notarize a trailing slash route`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
trailingSlash()
}) {
// do
@@ -248,7 +248,7 @@ internal class KompendiumTest {
fun `Can call a trailing slash route`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
trailingSlash()
}) {
// do
@@ -264,7 +264,7 @@ internal class KompendiumTest {
fun `Can notarize a complex type`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
complexType()
}) {
// do
@@ -280,7 +280,7 @@ internal class KompendiumTest {
fun `Can notarize primitives`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
primitives()
}) {
// do
@@ -296,7 +296,7 @@ internal class KompendiumTest {
fun `Can notarize a top level list response`() {
withTestApplication({
configModule()
- openApiModule()
+ docs()
returnsList()
}) {
// do
@@ -308,6 +308,23 @@ internal class KompendiumTest {
}
}
+ @Test
+ fun `Generates the expected redoc`() {
+ withTestApplication({
+ configModule()
+ docs()
+ returnsList()
+ }) {
+
+ // do
+ val html = handleRequest(HttpMethod.Get, "/docs").response.content
+
+ // expected
+ val expected = TestData.getFileSnapshot("redoc.html")
+ assertEquals(expected, html)
+ }
+ }
+
private companion object {
val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor")
val testPostResponse = ResponseInfo(KompendiumHttpCodes.CREATED, "A Successful Endeavor")
@@ -440,41 +457,38 @@ internal class KompendiumTest {
}
}
- private fun Application.openApiModule() {
+ private val oas = 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"
+ )
+ )
+ )
+
+ private fun Application.docs() {
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"
- )
- )
- )
- )
- }
- }
+ openApi(oas)
+ redoc(oas)
}
}
diff --git a/kompendium-core/src/test/resources/redoc.html b/kompendium-core/src/test/resources/redoc.html
new file mode 100644
index 000000000..21d16e55a
--- /dev/null
+++ b/kompendium-core/src/test/resources/redoc.html
@@ -0,0 +1,13 @@
+
+
+
+ Test API
+
+
+
+
+
+
+
+
+
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 18d53ebd3..78542e196 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
@@ -6,30 +6,18 @@ import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.ContentNegotiation
-import io.ktor.html.respondHtml
import io.ktor.jackson.jackson
-import io.ktor.response.respond
import io.ktor.response.respondText
-import io.ktor.routing.Routing
-import io.ktor.routing.get
import io.ktor.routing.route
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import java.net.URI
-import kotlinx.html.body
-import kotlinx.html.head
-import kotlinx.html.link
-import kotlinx.html.meta
-import kotlinx.html.script
-import kotlinx.html.style
-import kotlinx.html.title
-import kotlinx.html.unsafe
+import org.leafygreens.kompendium.Kompendium
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.Kompendium.openApiSpec
import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.PathParam
import org.leafygreens.kompendium.annotations.QueryParam
@@ -45,8 +33,38 @@ import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleDeleteInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePostInfo
import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePutInfo
+import org.leafygreens.kompendium.routes.openApi
+import org.leafygreens.kompendium.routes.redoc
import org.leafygreens.kompendium.util.KompendiumHttpCodes
+private val oas = 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"
+ )
+ )
+)
+
fun main() {
embeddedServer(
Netty,
@@ -63,8 +81,8 @@ fun Application.mainModule() {
}
}
routing {
- openApi()
- redoc()
+ openApi(oas)
+ redoc(oas)
route("/test") {
route("/{id}") {
notarizedGet(testIdGetInfo) {
@@ -163,76 +181,3 @@ object KompendiumTOC {
)
)
}
-
-fun Routing.openApi() {
- route("/openapi.json") {
- get {
- call.respond(
- 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"
- )
- )
- )
- )
- }
- }
-}
-
-fun Routing.redoc() {
- route("/docs") {
- get {
- call.respondHtml {
- head {
- title {
- +"${openApiSpec.info.title}"
- }
- meta {
- charset = "utf-8"
- }
- meta {
- name = "viewport"
- content = "width=device-width, initial-scale=1"
- }
- link {
- href = "https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
- rel = "stylesheet"
- }
- style {
- unsafe {
- raw("body { margin: 0; padding: 0; }")
- }
- }
- }
- body {
- // TODO needs to mirror openApi route
- unsafe { +"" }
- script {
- src = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"
- }
- }
- }
- }
- }
-}