Skip to content

Commit

Permalink
Merge branch 'support-spec-as-yaml' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
SMILEY4 committed Oct 20, 2024
2 parents 2d1a1f7 + accc878 commit 09ed474
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 88 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import io.ktor.server.routing.Routing
import io.ktor.server.webjars.Webjars
import io.swagger.v3.core.util.Json31
import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.smiley4.ktorswaggerui.data.OutputFormat
import io.swagger.v3.core.util.Yaml31

/**
* This version must match the version of the gradle dependency
Expand Down Expand Up @@ -72,7 +74,7 @@ val SwaggerUI = createApplicationPlugin(name = "SwaggerUI", createConfiguration
}
}

private fun buildOpenApiSpecs(config: PluginConfigData, routes: List<RouteMeta>): Map<String, String> {
private fun buildOpenApiSpecs(config: PluginConfigData, routes: List<RouteMeta>): Map<String, Pair<String, OutputFormat>> {
val routesBySpec = buildMap<String, MutableList<RouteMeta>> {
routes.forEach { route ->
val specName =
Expand All @@ -88,7 +90,7 @@ private fun buildOpenApiSpecs(config: PluginConfigData, routes: List<RouteMeta>)
}
}

private fun buildOpenApiSpec(specName: String, pluginConfig: PluginConfigData, routes: List<RouteMeta>): String {
private fun buildOpenApiSpec(specName: String, pluginConfig: PluginConfigData, routes: List<RouteMeta>): Pair<String, OutputFormat> {
return try {
val schemaContext = SchemaContextImpl(pluginConfig.schemaConfig).also {
it.addGlobal(pluginConfig.schemaConfig)
Expand All @@ -100,10 +102,13 @@ private fun buildOpenApiSpec(specName: String, pluginConfig: PluginConfigData, r
}
val openApi = builder(pluginConfig, schemaContext, exampleContext).build(routes)
pluginConfig.postBuild?.let { it(openApi, specName) }
Json31.pretty(openApi)
when(pluginConfig.outputFormat) {
OutputFormat.JSON -> Json31.pretty(openApi) to pluginConfig.outputFormat
OutputFormat.YAML -> Yaml31.pretty(openApi) to pluginConfig.outputFormat
}
} catch (e: Exception) {
logger.error(e) { "Error during openapi-generation" }
"{}"
return pluginConfig.outputFormat.empty to pluginConfig.outputFormat
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.smiley4.ktorswaggerui.data

enum class OutputFormat(val empty: String) {
JSON("{}"),
YAML("")
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ data class PluginConfigData(
val schemaConfig: SchemaConfigData,
val exampleConfig: ExampleConfigData,
val securityConfig: SecurityData,
val tagsConfig: TagsData
val tagsConfig: TagsData,
val outputFormat: OutputFormat
) {

companion object {
Expand All @@ -36,7 +37,8 @@ data class PluginConfigData(
schemaConfig = SchemaConfigData.DEFAULT,
exampleConfig = ExampleConfigData.DEFAULT,
securityConfig = SecurityData.DEFAULT,
tagsConfig = TagsData.DEFAULT
tagsConfig = TagsData.DEFAULT,
outputFormat = OutputFormat.JSON
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package io.github.smiley4.ktorswaggerui.dsl.config

import io.github.smiley4.ktorswaggerui.data.*
import io.github.smiley4.ktorswaggerui.data.DataUtils.merge
import io.github.smiley4.ktorswaggerui.data.OutputFormat
import io.github.smiley4.ktorswaggerui.data.PathFilter
import io.github.smiley4.ktorswaggerui.data.PluginConfigData
import io.github.smiley4.ktorswaggerui.data.PostBuild
import io.github.smiley4.ktorswaggerui.data.ServerData
import io.github.smiley4.ktorswaggerui.data.SpecAssigner
import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker
import io.ktor.server.routing.*
import io.ktor.server.routing.RouteSelector
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
Expand Down Expand Up @@ -109,6 +114,7 @@ class PluginConfigDsl {

private val specConfigs = mutableMapOf<String, PluginConfigDsl>()


/**
* Assigns routes without an [io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRoute.specId]] to a specified openapi-spec.
*/
Expand All @@ -128,6 +134,12 @@ class PluginConfigDsl {
var ignoredRouteSelectors: Set<KClass<*>> = PluginConfigData.DEFAULT.ignoredRouteSelectors


/**
* The format of the generated api-spec
*/
var outputFormat: OutputFormat = PluginConfigData.DEFAULT.outputFormat


/**
* Invoked after generating the openapi-spec. Can be to e.g. further customize the spec.
*/
Expand Down Expand Up @@ -160,6 +172,7 @@ class PluginConfigDsl {
},
specConfigs = mutableMapOf(),
postBuild = merge(base.postBuild, postBuild),
outputFormat = outputFormat
).also {
specConfigs.forEach { (specId, config) ->
it.specConfigs[specId] = config.build(it)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
package io.github.smiley4.ktorswaggerui.routing

import io.github.smiley4.ktorswaggerui.data.OutputFormat
import io.github.smiley4.ktorswaggerui.data.SwaggerUIData

object ApiSpec {

var swaggerUiConfig: SwaggerUIData = SwaggerUIData.DEFAULT

private val apiSpecs = mutableMapOf<String, String>()
private val apiSpecs = mutableMapOf<String, Pair<String, OutputFormat>>()

fun setAll(specs: Map<String, String>) {
fun setAll(specs: Map<String, Pair<String, OutputFormat>>) {
apiSpecs.clear()
apiSpecs.putAll(specs)
}

fun set(name: String, spec: String) {
fun set(name: String, spec: Pair<String, OutputFormat>) {
apiSpecs[name] = spec
}

fun get(name: String): String {
return apiSpecs[name] ?: throw NoSuchElementException("No api-spec with name '$name' registered.")
return apiSpecs[name]?.first ?: throw NoSuchElementException("No api-spec with name '$name' registered.")
}

fun getFormat(name: String): OutputFormat {
return apiSpecs[name]?.second ?: throw NoSuchElementException("No api-spec with name '$name' registered.")
}

fun getAll(): Map<String, String> {
return apiSpecs
return apiSpecs.mapValues { it.value.first }
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.smiley4.ktorswaggerui.routing

import io.github.smiley4.ktorswaggerui.SWAGGER_UI_WEBJARS_VERSION
import io.github.smiley4.ktorswaggerui.SwaggerUI
import io.github.smiley4.ktorswaggerui.data.OutputFormat
import io.github.smiley4.ktorswaggerui.data.SwaggerUIData
import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort
import io.github.smiley4.ktorswaggerui.data.SwaggerUiSyntaxHighlight
Expand All @@ -19,7 +20,11 @@ import io.ktor.server.routing.*
fun Route.openApiSpec(specId: String = PluginConfigDsl.DEFAULT_SPEC_ID) {
route({ hidden = true }) {
get {
call.respondText(ContentType.Application.Json, HttpStatusCode.OK) { ApiSpec.get(specId) }
val contentType = when(ApiSpec.getFormat(specId)) {
OutputFormat.JSON -> ContentType.Application.Json
OutputFormat.YAML -> ContentType.Text.Plain
}
call.respondText(contentType, HttpStatusCode.OK) { ApiSpec.get(specId) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.github.smiley4.ktorswaggerui.misc

import io.github.smiley4.ktorswaggerui.SwaggerUI
import io.github.smiley4.ktorswaggerui.data.OutputFormat
import io.github.smiley4.ktorswaggerui.routing.openApiSpec
import io.github.smiley4.ktorswaggerui.routing.swaggerUI
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldNotBeEmpty
import io.kotest.matchers.string.shouldStartWith
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
Expand Down Expand Up @@ -49,21 +51,61 @@ class RoutingTests {
it.status shouldBe HttpStatusCode.OK
it.contentType shouldBe ContentType.Application.Json
it.body.shouldNotBeEmpty()
it.body shouldStartWith "{\n \"openapi\" : \"3.1.0\","
}
}

private fun swaggerUITestApplication(block: suspend TestContext.() -> Unit) {

@Test
fun basicRoutingYml() = swaggerUITestApplication(OutputFormat.YAML) {
get("hello").also {
it.status shouldBe HttpStatusCode.OK
it.body shouldBe "Hello Test"
}
get("/").also {
it.status shouldBe HttpStatusCode.NotFound
}
get("/swagger").also {
it.status shouldBe HttpStatusCode.OK
it.contentType shouldBe ContentType.Text.Html
it.body.shouldNotBeEmpty()
}
get("/swagger/index.html").also {
it.status shouldBe HttpStatusCode.OK
it.contentType shouldBe ContentType.Text.Html
it.body.shouldNotBeEmpty()
}
get("/swagger/swagger-initializer.js").also {
it.status shouldBe HttpStatusCode.OK
it.contentType shouldBe ContentType.Application.JavaScript
it.body shouldContain "url: \"/api.yml\""
}
get("/api.yml").also {
it.status shouldBe HttpStatusCode.OK
it.contentType shouldBe ContentType.Text.Plain.withParameter("charset", "utf-8")
it.body.shouldNotBeEmpty()
it.body shouldStartWith "openapi: 3.1.0\n"
}
}

private fun swaggerUITestApplication(format: OutputFormat = OutputFormat.JSON, block: suspend TestContext.() -> Unit) {
testApplication {
val client = createClient {
this.followRedirects = followRedirects
}
install(SwaggerUI)
install(SwaggerUI) {
outputFormat = format
}
routing {
route("api.json") {
val routeSuffix = when(format) {
OutputFormat.JSON -> "json"
OutputFormat.YAML -> "yml"
}
route("api.$routeSuffix") {
openApiSpec()
}
route("swagger") {
swaggerUI("/api.json")
swaggerUI("/api.$routeSuffix")
}
get("hello") {
call.respondText("Hello Test")
Expand Down

0 comments on commit 09ed474

Please sign in to comment.