diff --git a/README.md b/README.md index 7d2d4f3..f6507e5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Ktor Swagger-UI [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.smiley4/ktor-swagger-ui/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.smiley4/ktor-swagger-ui) -[![Checks Passing](https://github.com/SMILEY4/ktor-swagger-ui/actions/workflows/buildTestVerify.yml/badge.svg?branch=develop)](https://github.com/SMILEY4/ktor-swagger-ui/actions/workflows/buildTestVerify.yml) +[![Checks Passing](https://github.com/SMILEY4/ktor-swagger-ui/actions/workflows/checks.yml/badge.svg?branch=develop)](https://github.com/SMILEY4/ktor-swagger-ui/actions/workflows/checks.yml) This library provides a Ktor plugin to document routes, generate an OpenApi Specification and serve a Swagger UI. It is meant to be minimally invasive, meaning it can be plugged into existing application without requiring immediate changes to the code. Routes can then be gradually enhanced with documentation. diff --git a/build.gradle.kts b/build.gradle.kts index 4af8914..56122de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ import io.gitlab.arturbosch.detekt.Detekt object Meta { const val groupId = "io.github.smiley4" const val artifactId = "ktor-swagger-ui" - const val version = "2.3.1" + const val version = "2.4.0" const val name = "Ktor Swagger-UI" const val description = "Ktor plugin to document routes and provide Swagger UI" const val licenseName = "The Apache License, Version 2.0" diff --git a/config/detekt.yml b/config/detekt.yml index 55ecdc4..db9252e 100644 --- a/config/detekt.yml +++ b/config/detekt.yml @@ -783,6 +783,6 @@ style: active: true ignoreLateinitVar: false WildcardImport: - active: true + active: false excludeImports: - 'java.util.*' diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index aadc05d..f019555 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -1,32 +1,9 @@ package io.github.smiley4.ktorswaggerui import com.fasterxml.jackson.databind.ObjectMapper -import io.github.smiley4.ktorswaggerui.dsl.SchemaType -import io.github.smiley4.ktorswaggerui.dsl.getSchemaType import io.github.smiley4.ktorswaggerui.spec.example.ExampleContext import io.github.smiley4.ktorswaggerui.spec.example.ExampleContextBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ComponentsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ContactBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ContentBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ExampleBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ExternalDocumentationBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.HeaderBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.InfoBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.LicenseBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.OAuthFlowsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.OpenApiBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.OperationBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.OperationTagsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ParameterBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.PathBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.PathsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.RequestBodyBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ResponseBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ResponsesBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.SecurityRequirementsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.SecuritySchemesBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ServerBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.TagBuilder +import io.github.smiley4.ktorswaggerui.spec.openapi.* import io.github.smiley4.ktorswaggerui.spec.route.RouteCollector import io.github.smiley4.ktorswaggerui.spec.route.RouteDocumentationMerger import io.github.smiley4.ktorswaggerui.spec.route.RouteMeta @@ -34,18 +11,12 @@ import io.github.smiley4.ktorswaggerui.spec.schema.SchemaBuilder import io.github.smiley4.ktorswaggerui.spec.schema.SchemaContext import io.github.smiley4.ktorswaggerui.spec.schema.SchemaContextBuilder import io.github.smiley4.ktorswaggerui.spec.schema.TypeOverwrites -import io.ktor.server.application.Application -import io.ktor.server.application.ApplicationStarted -import io.ktor.server.application.createApplicationPlugin -import io.ktor.server.application.hooks.MonitoringEvent -import io.ktor.server.application.install -import io.ktor.server.application.plugin -import io.ktor.server.application.pluginOrNull -import io.ktor.server.routing.Routing -import io.ktor.server.webjars.Webjars +import io.ktor.server.application.* +import io.ktor.server.application.hooks.* +import io.ktor.server.routing.* +import io.ktor.server.webjars.* import io.swagger.v3.core.util.Json import mu.KotlinLogging -import java.io.File /** * This version must match the version of the gradle dependency @@ -112,9 +83,10 @@ private fun builder(config: SwaggerUIPluginConfig, schemaContext: SchemaContext, contactBuilder = ContactBuilder(), licenseBuilder = LicenseBuilder() ), + externalDocumentationBuilder = ExternalDocumentationBuilder(), serverBuilder = ServerBuilder(), tagBuilder = TagBuilder( - externalDocumentationBuilder = ExternalDocumentationBuilder() + tagExternalDocumentationBuilder = TagExternalDocumentationBuilder() ), pathsBuilder = PathsBuilder( pathBuilder = PathBuilder( diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIPluginConfig.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIPluginConfig.kt index e5e549d..9059928 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIPluginConfig.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIPluginConfig.kt @@ -1,17 +1,8 @@ package io.github.smiley4.ktorswaggerui -import io.github.smiley4.ktorswaggerui.dsl.CustomSchemas -import io.github.smiley4.ktorswaggerui.dsl.EncodingConfig -import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker -import io.github.smiley4.ktorswaggerui.dsl.OpenApiInfo -import io.github.smiley4.ktorswaggerui.dsl.OpenApiResponse -import io.github.smiley4.ktorswaggerui.dsl.OpenApiSecurityScheme -import io.github.smiley4.ktorswaggerui.dsl.OpenApiServer -import io.github.smiley4.ktorswaggerui.dsl.OpenApiTag -import io.github.smiley4.ktorswaggerui.dsl.SwaggerUIDsl -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.server.routing.RouteSelector +import io.github.smiley4.ktorswaggerui.dsl.* +import io.ktor.http.* +import io.ktor.server.routing.* import kotlin.reflect.KClass /** @@ -101,6 +92,18 @@ class SwaggerUIPluginConfig { fun getServers(): List = servers + /** + * OpenAPI external docs configuration - link and description of an external documentation + */ + fun externalDocs(block: OpenApiExternalDocs.() -> Unit) { + externalDocs = OpenApiExternalDocs().apply(block) + } + + private var externalDocs = OpenApiExternalDocs() + + fun getExternalDocs() = externalDocs + + /** * Defines security schemes that can be used by operations */ diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExternalDocs.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExternalDocs.kt new file mode 100644 index 0000000..c1ce30d --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExternalDocs.kt @@ -0,0 +1,17 @@ +package io.github.smiley4.ktorswaggerui.dsl + +/** + * An object representing external documentation. + */ +@OpenApiDslMarker +class OpenApiExternalDocs { + /** + * A short description of the external documentation + */ + var description: String? = null + + /** + * A URL to the external documentation + */ + var url: String = "/" +} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/ExternalDocumentationBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/ExternalDocumentationBuilder.kt index 1a13a8d..7c463e4 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/ExternalDocumentationBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/ExternalDocumentationBuilder.kt @@ -1,14 +1,15 @@ package io.github.smiley4.ktorswaggerui.spec.openapi +import io.github.smiley4.ktorswaggerui.dsl.OpenApiExternalDocs import io.swagger.v3.oas.models.ExternalDocumentation - class ExternalDocumentationBuilder { - fun build(url: String, description: String): ExternalDocumentation = + fun build(externalDocs: OpenApiExternalDocs): ExternalDocumentation = ExternalDocumentation().also { - it.url = url - it.description = description + it.url = externalDocs.url + it.description = externalDocs.description } } + diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/OpenApiBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/OpenApiBuilder.kt index 14e2142..0f1c694 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/OpenApiBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/OpenApiBuilder.kt @@ -11,6 +11,7 @@ class OpenApiBuilder( private val schemaContext: SchemaContext, private val exampleContext: ExampleContext, private val infoBuilder: InfoBuilder, + private val externalDocumentationBuilder: ExternalDocumentationBuilder, private val serverBuilder: ServerBuilder, private val tagBuilder: TagBuilder, private val pathsBuilder: PathsBuilder, @@ -20,6 +21,7 @@ class OpenApiBuilder( fun build(routes: Collection): OpenAPI { return OpenAPI().also { it.info = infoBuilder.build(config.getInfo()) + it.externalDocs = externalDocumentationBuilder.build(config.getExternalDocs()) it.servers = config.getServers().map { server -> serverBuilder.build(server) } it.tags = config.getTags().map { tag -> tagBuilder.build(tag) } it.paths = pathsBuilder.build(routes) diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/TagBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/TagBuilder.kt index be878f0..60284d7 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/TagBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/TagBuilder.kt @@ -4,7 +4,7 @@ import io.github.smiley4.ktorswaggerui.dsl.OpenApiTag import io.swagger.v3.oas.models.tags.Tag class TagBuilder( - private val externalDocumentationBuilder: ExternalDocumentationBuilder + private val tagExternalDocumentationBuilder: TagExternalDocumentationBuilder ) { fun build(tag: OpenApiTag): Tag = @@ -12,7 +12,7 @@ class TagBuilder( it.name = tag.name it.description = tag.description if(tag.externalDocUrl != null && tag.externalDocDescription != null) { - it.externalDocs = externalDocumentationBuilder.build(tag.externalDocUrl!!, tag.externalDocDescription!!) + it.externalDocs = tagExternalDocumentationBuilder.build(tag.externalDocUrl!!, tag.externalDocDescription!!) } } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/TagExternalDocumentationBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/TagExternalDocumentationBuilder.kt new file mode 100644 index 0000000..805cd31 --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/spec/openapi/TagExternalDocumentationBuilder.kt @@ -0,0 +1,12 @@ +package io.github.smiley4.ktorswaggerui.spec.openapi + +import io.swagger.v3.oas.models.ExternalDocumentation + +class TagExternalDocumentationBuilder { + + fun build(url: String, description: String): ExternalDocumentation = + ExternalDocumentation().also { + it.url = url + it.description = description + } +} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteExample.kt index f9be583..6529215 100644 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteExample.kt +++ b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteExample.kt @@ -66,6 +66,10 @@ private fun Application.myModule() { version = "latest" description = "Example API for testing and demonstration purposes." } + externalDocs { + url = "https://github.com/SMILEY4/ktor-swagger-ui/wiki" + description = "Sample external documentation object" + } server { url = "http://localhost:8080" description = "Development Server" diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompletePluginConfigExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompletePluginConfigExample.kt index f92a416..539b90d 100644 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompletePluginConfigExample.kt +++ b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompletePluginConfigExample.kt @@ -66,6 +66,10 @@ private fun Application.myModule() { url = "example.com/license" } } + externalDocs { + url = "https://docs.example.com" + description = "Example external documentation description" + } server { url = "localhost:8080" description = "develop server" diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ExternalDocsBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ExternalDocsBuilderTest.kt new file mode 100644 index 0000000..2213852 --- /dev/null +++ b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ExternalDocsBuilderTest.kt @@ -0,0 +1,38 @@ +package io.github.smiley4.ktorswaggerui.tests.openapi + +import io.github.smiley4.ktorswaggerui.dsl.OpenApiExternalDocs +import io.github.smiley4.ktorswaggerui.spec.openapi.ExternalDocumentationBuilder +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import io.swagger.v3.oas.models.ExternalDocumentation + +class ExternalDocsBuilderTest : StringSpec({ + + "default external docs object" { + buildExternalDocsObject {}.also { docs -> + docs.url shouldBe "/" + docs.description shouldBe null + } + } + + "complete server object" { + buildExternalDocsObject { + url = "Test URL" + description = "Test Description" + }.also { docs -> + docs.url shouldBe "Test URL" + docs.description shouldBe "Test Description" + } + } + +}) { + + companion object { + + private fun buildExternalDocsObject(builder: OpenApiExternalDocs.() -> Unit): ExternalDocumentation { + return ExternalDocumentationBuilder().build(OpenApiExternalDocs().apply(builder)) + } + + } + +} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OpenApiBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OpenApiBuilderTest.kt index 9713ab6..b63df8e 100644 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OpenApiBuilderTest.kt +++ b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OpenApiBuilderTest.kt @@ -4,28 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.github.smiley4.ktorswaggerui.SwaggerUIPluginConfig import io.github.smiley4.ktorswaggerui.spec.example.ExampleContext import io.github.smiley4.ktorswaggerui.spec.example.ExampleContextBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ComponentsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ContactBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ContentBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ExampleBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ExternalDocumentationBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.HeaderBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.InfoBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.LicenseBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.OAuthFlowsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.OpenApiBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.OperationBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.OperationTagsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ParameterBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.PathBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.PathsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.RequestBodyBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ResponseBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ResponsesBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.SecurityRequirementsBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.SecuritySchemesBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.ServerBuilder -import io.github.smiley4.ktorswaggerui.spec.openapi.TagBuilder +import io.github.smiley4.ktorswaggerui.spec.openapi.* import io.github.smiley4.ktorswaggerui.spec.route.RouteMeta import io.github.smiley4.ktorswaggerui.spec.schema.SchemaBuilder import io.github.smiley4.ktorswaggerui.spec.schema.SchemaContext @@ -47,6 +26,7 @@ class OpenApiBuilderTest : StringSpec({ openapi.info shouldNotBe null openapi.extensions shouldBe null openapi.servers shouldHaveSize 0 + openapi.externalDocs shouldNotBe null openapi.security shouldBe null openapi.tags shouldHaveSize 0 openapi.paths shouldHaveSize 0 @@ -130,9 +110,10 @@ class OpenApiBuilderTest : StringSpec({ contactBuilder = ContactBuilder(), licenseBuilder = LicenseBuilder() ), + externalDocumentationBuilder = ExternalDocumentationBuilder(), serverBuilder = ServerBuilder(), tagBuilder = TagBuilder( - externalDocumentationBuilder = ExternalDocumentationBuilder() + tagExternalDocumentationBuilder = TagExternalDocumentationBuilder() ), pathsBuilder = PathsBuilder( pathBuilder = PathBuilder( diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/TagsBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/TagsBuilderTest.kt index d706682..bc2f335 100644 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/TagsBuilderTest.kt +++ b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/TagsBuilderTest.kt @@ -1,8 +1,8 @@ package io.github.smiley4.ktorswaggerui.tests.openapi import io.github.smiley4.ktorswaggerui.dsl.OpenApiTag -import io.github.smiley4.ktorswaggerui.spec.openapi.ExternalDocumentationBuilder import io.github.smiley4.ktorswaggerui.spec.openapi.TagBuilder +import io.github.smiley4.ktorswaggerui.spec.openapi.TagExternalDocumentationBuilder import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -45,7 +45,7 @@ class TagsBuilderTest : StringSpec({ private fun buildTagObject(name: String, builder: OpenApiTag.() -> Unit): Tag { return TagBuilder( - externalDocumentationBuilder = ExternalDocumentationBuilder() + tagExternalDocumentationBuilder = TagExternalDocumentationBuilder() ).build(OpenApiTag(name).apply(builder)) }