Skip to content

Commit

Permalink
update swagger UI to latest version
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasForst committed Sep 19, 2022
1 parent c4ef187 commit 2db9d8d
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 55 deletions.
15 changes: 3 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ then [MinimalExampleTest.kt](src/test/kotlin/MinimalExampleTest.kt) that it actu
fun Application.minimalExample() {
// install OpenAPI plugin
install(OpenAPIGen) {
// this automatically servers Swagger UI on /swagger-ui
// this servers OpenAPI definition on /openapi.json
serveOpenApiJson = true
// this servers Swagger UI on /swagger-ui/index.html
serveSwaggerUi = true
info {
title = "Minimal Example API"
Expand All @@ -33,17 +35,6 @@ fun Application.minimalExample() {
install(ContentNegotiation) {
jackson()
}
// add basic routes for openapi.json and redirect to UI
routing {
// serve openapi.json
get("/openapi.json") {
call.respond(this@routing.application.openAPIGen.api.serialize())
}
// and do redirect to make it easier to remember
get("/swagger-ui") {
call.respondRedirect("/swagger-ui/index.html?url=/openapi.json", true)
}
}
// and now example routing
apiRouting {
route("/example/{name}") {
Expand Down
14 changes: 7 additions & 7 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"

id("net.nemerosa.versioning") version "2.15.1"
id("org.jetbrains.dokka") version "1.6.10"
id("org.jetbrains.dokka") version "1.7.10"
}

group = "dev.forst"
Expand All @@ -30,17 +30,17 @@ dependencies {
implementation("io.ktor", "ktor-server-auth", ktorVersion)
implementation("io.ktor", "ktor-serialization-jackson", ktorVersion)
implementation("io.ktor", "ktor-server-content-negotiation", ktorVersion)
implementation("io.ktor","ktor-server-status-pages",ktorVersion)
implementation("io.ktor", "ktor-server-status-pages", ktorVersion)

implementation("org.slf4j:slf4j-api:1.7.36")
implementation("org.slf4j:slf4j-api:2.0.1")

implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3") // needed for multipart parsing
implementation("org.webjars:swagger-ui:3.25.0")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4") // needed for multipart parsing
implementation("org.webjars:swagger-ui:4.14.0")

implementation("org.reflections:reflections:0.10.2") // only used while initializing

// testing
testImplementation("io.ktor","ktor-server-netty",ktorVersion)
testImplementation("io.ktor", "ktor-server-netty", ktorVersion)
testImplementation("io.ktor", "ktor-server-core", ktorVersion)
testImplementation("io.ktor", "ktor-server-test-host", ktorVersion)
testImplementation("io.ktor", "ktor-server-auth", ktorVersion)
Expand All @@ -52,7 +52,7 @@ dependencies {
testImplementation(kotlin("test"))
testImplementation(kotlin("stdlib-jdk8"))

testImplementation("ch.qos.logback", "logback-classic", "1.3.0-alpha5") // logging framework for the tests
testImplementation("ch.qos.logback", "logback-classic", "1.4.1") // logging framework for the tests

val junitVersion = "5.9.0"
testImplementation("org.junit.jupiter", "junit-jupiter-api", junitVersion) // junit testing framework
Expand Down
28 changes: 23 additions & 5 deletions src/main/kotlin/com/papsign/ktor/openapigen/OpenAPIGen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import io.ktor.server.application.ApplicationCallPipeline
import io.ktor.server.application.BaseApplicationPlugin
import io.ktor.server.application.call
import io.ktor.server.request.path
import io.ktor.server.response.respond
import io.ktor.server.response.respondRedirect
import io.ktor.util.AttributeKey
import org.reflections.Reflections
import kotlin.reflect.full.starProjectedType
Expand Down Expand Up @@ -58,9 +60,12 @@ class OpenAPIGen(
api.externalDocs = ExternalDocumentationModel(url).apply(configure)
}

var openApiJsonPath = "/openapi.json"
var serveOpenApiJson = true

var swaggerUiPath = "swagger-ui"
var serveSwaggerUi = true
var swaggerUiVersion = "3.25.0"
var swaggerUiVersion = "4.14.0"

var scanPackagesForModules: Array<String> = arrayOf()

Expand Down Expand Up @@ -105,12 +110,25 @@ class OpenAPIGen(
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): OpenAPIGen {
val api = OpenAPIModel()
val cfg = Configuration(api).apply(configure)

if (cfg.serveOpenApiJson) {
pipeline.intercept(ApplicationCallPipeline.Call) {
if (call.request.path() == cfg.openApiJsonPath) {
call.respond(api.serialize())
}
}
}

if (cfg.serveSwaggerUi) {
val ui = SwaggerUi(cfg.swaggerUiPath, cfg.swaggerUiVersion)
val ui = SwaggerUi(cfg.swaggerUiPath, cfg.swaggerUiVersion, if (cfg.serveOpenApiJson) cfg.openApiJsonPath else null)
val swaggerRoot = "/${cfg.swaggerUiPath.removePrefix("/")}"
val swaggerUiResources = "/${cfg.swaggerUiPath.trim('/')}/"
pipeline.intercept(ApplicationCallPipeline.Call) {
val cmp = "/${cfg.swaggerUiPath.trim('/')}/"
if (call.request.path().startsWith(cmp))
ui.serve(call.request.path().removePrefix(cmp), call)
when {
call.request.path() == swaggerRoot -> call.respondRedirect("${swaggerRoot}/index.html")
call.request.path().startsWith(swaggerUiResources) ->
ui.serve(call.request.path().removePrefix(swaggerUiResources), call)
}
}
}
return OpenAPIGen(cfg, pipeline)
Expand Down
29 changes: 23 additions & 6 deletions src/main/kotlin/com/papsign/ktor/openapigen/SwaggerUI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import io.ktor.server.request.port
import io.ktor.server.response.respond
import java.net.URL

class SwaggerUi(private val basePath: String, private val version: String) {
class SwaggerUi(
private val basePath: String,
private val version: String,
private val openApiJsonUrl: String?,
) {
private val notFound = mutableListOf<String>()
private val content = mutableMapOf<String, ResourceContent>()
suspend fun serve(filename: String?, call: ApplicationCall) {
Expand All @@ -27,7 +31,7 @@ class SwaggerUi(private val basePath: String, private val version: String) {
notFound.add(filename)
return
}
call.respond(content.getOrPut(filename) { ResourceContent(resource, call.redirectUrl()) })
call.respond(content.getOrPut(filename) { ResourceContent(resource, call.redirectUrl(), openApiJsonUrl) })
}
}
}
Expand All @@ -41,18 +45,31 @@ class SwaggerUi(private val basePath: String, private val version: String) {
}



private val contentTypes = mapOf(
"html" to Html,
"css" to CSS,
"js" to JavaScript,
"json" to ContentType.Application.Json.withCharset(Charsets.UTF_8),
"png" to PNG)
"png" to PNG
)

private class ResourceContent(val resource: URL, val address: String) : OutgoingContent.ByteArrayContent() {
private class ResourceContent(
val resource: URL,
val address: String,
val openApiJsonUrl: String?,
) : OutgoingContent.ByteArrayContent() {
private val bytes by lazy {
if (contentType == JavaScript) {
resource.readText().replace("http://localhost:3200/oauth2-redirect.html", address + "oauth2-redirect.html").toByteArray()
resource.readText()
.replace("http://localhost:3200/oauth2-redirect.html", address + "oauth2-redirect.html")
.let {
if (openApiJsonUrl != null) {
it.replace("https://petstore.swagger.io/v2/swagger.json", openApiJsonUrl)
} else {
it
}
}
.toByteArray()
} else resource.readBytes()
}

Expand Down
6 changes: 1 addition & 5 deletions src/test/kotlin/Basic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,8 @@ object Basic {

// normal Ktor routing
routing {
get("/openapi.json") {
call.respond(this.application.openAPIGen.api.serialize())
}

get("/") {
call.respondRedirect("/swagger-ui/index.html?url=/openapi.json", true)
call.respondRedirect("/swagger-ui")
}
}

Expand Down
6 changes: 1 addition & 5 deletions src/test/kotlin/Minimal.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,8 @@ object Minimal {

// normal Ktor routing
routing {
get("/openapi.json") {
call.respond(this.application.openAPIGen.api.serialize())
}

get("/") {
call.respondRedirect("/swagger-ui/index.html?url=/openapi.json", true)
call.respondRedirect("/swagger-ui/index.html", false)
}
}

Expand Down
19 changes: 4 additions & 15 deletions src/test/kotlin/MinimalExample.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import com.papsign.ktor.openapigen.OpenAPIGen
import com.papsign.ktor.openapigen.annotations.parameters.PathParam
import com.papsign.ktor.openapigen.openAPIGen
import com.papsign.ktor.openapigen.route.apiRouting
import com.papsign.ktor.openapigen.route.path.normal.post
import com.papsign.ktor.openapigen.route.response.respond
Expand All @@ -10,7 +9,6 @@ import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respond
import io.ktor.server.response.respondRedirect
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
Expand All @@ -21,7 +19,9 @@ import io.ktor.server.routing.routing
fun Application.minimalExample() {
// install OpenAPI plugin
install(OpenAPIGen) {
// this automatically servers Swagger UI on /swagger-ui
// this servers OpenAPI definition on /openapi.json
serveOpenApiJson = true
// this servers Swagger UI on /swagger-ui/index.html
serveSwaggerUi = true
info {
title = "Minimal Example API"
Expand All @@ -31,17 +31,6 @@ fun Application.minimalExample() {
install(ContentNegotiation) {
jackson()
}
// add basic routes for openapi.json and redirect to UI
routing {
// serve openapi.json
get("/openapi.json") {
call.respond(this@routing.application.openAPIGen.api.serialize())
}
// and do redirect to make it easier to remember
get("/swagger-ui") {
call.respondRedirect("/swagger-ui/index.html?url=/openapi.json", true)
}
}
// and now example routing
apiRouting {
route("/example/{name}") {
Expand All @@ -56,4 +45,4 @@ fun Application.minimalExample() {

data class SomeParams(@PathParam("who to say hello") val name: String)
data class SomeRequest(val foo: String)
data class SomeResponse(val bar: String)
data class SomeResponse(val bar: String)

0 comments on commit 2db9d8d

Please sign in to comment.