Skip to content

Commit

Permalink
upgrade to openapi 3.1.0 (from 3.0.1) and add more properties to dsl
Browse files Browse the repository at this point in the history
  • Loading branch information
SMILEY4 committed May 31, 2024
1 parent 3e6f528 commit 47bca53
Show file tree
Hide file tree
Showing 33 changed files with 305 additions and 35 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ projectDeveloperUrl=https://github.com/SMILEY4
# dependency versions
versionKtor=2.3.11
versionSwaggerUI=5.9.0
versionSwaggerParser=2.1.19
versionSwaggerParser=2.1.22
versionSchemaKenerator=0.4.0
versionKotlinLogging=3.0.5
versionKotest=5.8.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot
import io.github.smiley4.schemakenerator.swagger.data.TitleType
import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema
import io.github.smiley4.schemakenerator.swagger.withAutoTitle
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.application.call
Expand All @@ -30,12 +31,16 @@ fun main() {
embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
}

class Greeting(
val name: String
)

/**
* A (nearly) complete - and mostly nonsensical - plugin configuration
*/
private fun Application.myModule() {


install(SwaggerUI) {
info {
title = "Example API"
Expand All @@ -50,6 +55,7 @@ private fun Application.myModule() {
license {
name = "Example License"
url = "example.com"
identifier = "Apache-2.0"
}
}
externalDocs {
Expand All @@ -59,10 +65,20 @@ private fun Application.myModule() {
server {
url = "localhost"
description = "local dev-server"
variable("version") {
default = "1.0"
enum = setOf("1.0", "2.0", "3.0")
description = "the version of the server api"
}
}
server {
url = "example.com"
description = "productive server"
variable("version") {
default = "1.0"
enum = setOf("1.0", "2.0")
description = "the version of the server api"
}
}
swagger {
displayOperationId = true
Expand Down Expand Up @@ -138,15 +154,53 @@ private fun Application.myModule() {

// a documented route
get("hello", {
description = "A Hello-World route"
operationId = "hello"
summary = "hello world route"
description = "A Hello-World route as an example."
tags = setOf("hello", "example")
specId = PluginConfigDsl.DEFAULT_SPEC_ID
deprecated = false
hidden = false
protected = false
securitySchemeNames = emptyList()
externalDocs {
url = "example.com/hello"
description = "external documentation of 'hello'-route"
}
request {
queryParameter<String>("name") {
description = "the name to greet"
example("Josh") {
value = "Josh"
summary = "Example name"
description = "An example name for this query parameter"
}
}
body<Unit>()
}
response {
HttpStatusCode.OK to {
description = "successful request - always returns 'Hello World!'"
header<String>("x-random") {
description = "A header with some random number"
required = true
deprecated = false
explode = false
}
body<Greeting> {
description = "the greeting object with the name of the person to greet."
mediaTypes = setOf(ContentType.Application.Json)
required = true
}
}
}
server {
url = "example.com"
description = "productive server for 'hello'-route"
variable("version") {
default = "1.0"
enum = setOf("1.0", "2.0")
description = "the version of the server api"
}
}
}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ val SwaggerUI = createApplicationPlugin(name = "SwaggerUI", createConfiguration
private fun buildOpenApiSpecs(config: PluginConfigData, routes: List<RouteMeta>): Map<String, String> {
val routesBySpec = buildMap<String, MutableList<RouteMeta>> {
routes.forEach { route ->
val specName = route.documentation.specId ?: config.specAssigner(route.path, route.documentation.tags)
val specName = route.documentation.specId ?: config.specAssigner(route.path, route.documentation.tags.toList())
computeIfAbsent(specName) { mutableListOf() }.add(route)
}
}
Expand Down Expand Up @@ -157,6 +157,8 @@ private fun builder(
config = config
),
securityRequirementsBuilder = SecurityRequirementsBuilder(config),
externalDocumentationBuilder = ExternalDocumentationBuilder(),
serverBuilder = ServerBuilder()
)
)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class HeaderBuilder(
it.required = header.required
it.deprecated = header.deprecated
it.schema = header.type?.let { t -> schemaContext.getSchema(t) }
it.explode = header.explode
// it.example = TODO()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class InfoBuilder(
info.license?.also { license ->
it.license = licenseBuilder.build(license)
}
it.summary = info.summary
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class LicenseBuilder {
License().also {
it.name = license.name
it.url = license.url
it.identifier = license.identifier
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.github.smiley4.ktorswaggerui.data.PluginConfigData
import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta
import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.SpecVersion

class OpenApiBuilder(
private val config: PluginConfigData,
Expand All @@ -20,6 +21,8 @@ class OpenApiBuilder(

fun build(routes: Collection<RouteMeta>): OpenAPI {
return OpenAPI().also {
it.specVersion = SpecVersion.V31
it.openapi = "3.1.0"
it.info = infoBuilder.build(config.info)
it.externalDocs = externalDocumentationBuilder.build(config.externalDocs)
it.servers = config.servers.map { server -> serverBuilder.build(server) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ class OperationBuilder(
private val parameterBuilder: ParameterBuilder,
private val requestBodyBuilder: RequestBodyBuilder,
private val responsesBuilder: ResponsesBuilder,
private val securityRequirementsBuilder: SecurityRequirementsBuilder
private val securityRequirementsBuilder: SecurityRequirementsBuilder,
private val externalDocumentationBuilder: ExternalDocumentationBuilder,
private val serverBuilder: ServerBuilder
) {

fun build(route: RouteMeta): Operation =
Expand All @@ -30,6 +32,10 @@ class OperationBuilder(
}
}
}
it.externalDocs = route.documentation.externalDocs?.let { docs -> externalDocumentationBuilder.build(docs) }
if (route.documentation.servers.isNotEmpty()) {
it.servers = route.documentation.servers.map { server -> serverBuilder.build(server) }
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ class ParameterBuilder(
it.deprecated = parameter.deprecated
it.allowEmptyValue = parameter.allowEmptyValue
it.explode = parameter.explode
it.example = parameter.example?.let { e -> exampleContext.getExample(e).value }
it.example = parameter.example?.let { e -> exampleContext.getExample(e).value } // todo: example"S" ?
it.allowReserved = parameter.allowReserved
it.schema = schemaContext.getSchema(parameter.type)
it.style = parameter.style
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@ package io.github.smiley4.ktorswaggerui.builder.openapi

import io.github.smiley4.ktorswaggerui.data.ServerData
import io.swagger.v3.oas.models.servers.Server
import io.swagger.v3.oas.models.servers.ServerVariable
import io.swagger.v3.oas.models.servers.ServerVariables

class ServerBuilder {

fun build(server: ServerData): Server =
Server().also {
it.url = server.url
it.description = server.description
if (server.variables.isNotEmpty()) {
it.variables = ServerVariables().also { variables ->
server.variables.forEach { entry ->
variables.addServerVariable(entry.name, ServerVariable().also { variable ->
variable.enum = entry.enum.toList()
variable.default = entry.default
variable.description = entry.description
})
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ data class InfoData(
val title: String,
val version: String?,
val description: String?,
val summary: String?,
val termsOfService: String?,
val contact: ContactData?,
val license: LicenseData?
val license: LicenseData?,
) {
companion object {
val DEFAULT = InfoData(
title = "API",
version = null,
description = null,
summary = null,
termsOfService = null,
contact = null,
license = null
license = null,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package io.github.smiley4.ktorswaggerui.data
data class LicenseData(
val name: String?,
val url: String?,
val identifier: String?
) {
companion object {
val DEFAULT = LicenseData(
name = null,
url = null,
identifier = null
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ data class OpenApiHeaderData(
val type: TypeDescriptor?,
val required: Boolean,
val deprecated: Boolean,
val explode: Boolean?,
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.smiley4.ktorswaggerui.data

import io.swagger.v3.oas.models.parameters.Parameter

data class OpenApiRequestParameterData(
val name: String,
val type: TypeDescriptor,
Expand All @@ -10,5 +12,6 @@ data class OpenApiRequestParameterData(
val deprecated: Boolean,
val allowEmptyValue: Boolean,
val explode: Boolean,
val allowReserved: Boolean
val allowReserved: Boolean,
val style: Parameter.StyleEnum?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.github.smiley4.ktorswaggerui.data

data class OpenApiRouteData(
val specId: String?,
val tags: List<String>,
val tags: Set<String>,
val summary: String?,
val description: String?,
val operationId: String?,
Expand All @@ -12,4 +12,6 @@ data class OpenApiRouteData(
val protected: Boolean?,
val request: OpenApiRequestData,
val responses: List<OpenApiResponseData>,
val externalDocs: ExternalDocsData?,
val servers: List<ServerData>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package io.github.smiley4.ktorswaggerui.data
data class ServerData(
val url: String,
val description: String?,
val variables: List<ServerVariableData>
) {

companion object {
val DEFAULT = ServerData(
url = "/",
description = null
description = null,
variables = emptyList()
)
}

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

data class ServerVariableData(
val name: String,
val enum: Set<String>,
val default: String,
val description: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ class OpenApiInfo {
*/
var description: String? = null

/**
* A short summary of the API
*/
var summary: String? = null


/**
* A URL to the Terms of Service for the API. MUST be in the format of a URL.
Expand Down Expand Up @@ -62,7 +67,8 @@ class OpenApiInfo {
description = merge(base.description, this.description),
termsOfService = merge(base.termsOfService, this.termsOfService),
contact = contact?.build(base.contact ?: ContactData.DEFAULT) ?: base.contact,
license = license?.build(base.license ?: LicenseData.DEFAULT) ?: base.license
license = license?.build(base.license ?: LicenseData.DEFAULT) ?: base.license,
summary = merge(base.summary, this.summary)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ class OpenApiLicense {
var url: String? = LicenseData.DEFAULT.url


/**
* An SPDX (https://spdx.org/licenses/) license expression for the API. The identifier field is mutually exclusive of the url field.
*/
var identifier: String? = LicenseData.DEFAULT.identifier

fun build(base: LicenseData) = LicenseData(
name = DataUtils.merge(base.name, name),
url = DataUtils.merge(base.url, url),
identifier = DataUtils.merge(base.identifier, identifier)
)
}
Loading

0 comments on commit 47bca53

Please sign in to comment.