From b8258d5bd9586ee618e05746446f5ada0ba34f7b Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 9 Mar 2023 20:54:07 +0100 Subject: [PATCH 1/2] bump version to 1.3.0 --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index c1ee5ef..b7a8f3f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } group = "io.github.smiley4" -version = "1.3.0" +version = "1.4.0" repositories { mavenCentral() From bdaf5eeab99c0d63bbea42b9a88338435b1c83a8 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Mon, 20 Mar 2023 21:00:20 +0100 Subject: [PATCH 2/2] support schema-annotations for addition field information --- build.gradle.kts | 1 + .../ktorswaggerui/SwaggerUIPluginConfig.kt | 2 + .../JsonToOpenApiSchemaConverter.kt | 14 +++-- .../examples/SchemaAnnotationExample.kt | 60 +++++++++++++++++++ 4 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaAnnotationExample.kt diff --git a/build.gradle.kts b/build.gradle.kts index b7a8f3f..5297ab1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { val jsonSchemaGeneratorVersion = "4.28.0" implementation("com.github.victools:jsonschema-generator:$jsonSchemaGeneratorVersion") implementation("com.github.victools:jsonschema-module-jackson:$jsonSchemaGeneratorVersion") + implementation("com.github.victools:jsonschema-module-swagger-2:$jsonSchemaGeneratorVersion") val kotlinLoggingVersion = "2.1.23" implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIPluginConfig.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIPluginConfig.kt index 90f6f5b..ab52375 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIPluginConfig.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIPluginConfig.kt @@ -5,6 +5,7 @@ import com.github.victools.jsonschema.generator.OptionPreset import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder import com.github.victools.jsonschema.generator.SchemaVersion import com.github.victools.jsonschema.module.jackson.JacksonModule +import com.github.victools.jsonschema.module.swagger2.Swagger2Module import io.github.smiley4.ktorswaggerui.dsl.CustomSchemas import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker import io.github.smiley4.ktorswaggerui.dsl.OpenApiInfo @@ -156,6 +157,7 @@ class SwaggerUIPluginConfig { var schemaGeneratorConfigBuilder: SchemaGeneratorConfigBuilder = SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON) .with(JacksonModule()) + .with(Swagger2Module()) .without(Option.DEFINITIONS_FOR_ALL_OBJECTS) .with(Option.INLINE_ALL_SCHEMAS) .with(Option.EXTRA_OPEN_API_FORMAT_VALUES) diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/specbuilder/JsonToOpenApiSchemaConverter.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/specbuilder/JsonToOpenApiSchemaConverter.kt index 0d2dd47..71989ad 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/specbuilder/JsonToOpenApiSchemaConverter.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/specbuilder/JsonToOpenApiSchemaConverter.kt @@ -2,17 +2,25 @@ package io.github.smiley4.ktorswaggerui.specbuilder import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.ArrayNode import io.swagger.v3.oas.models.media.Schema class JsonToOpenApiSchemaConverter { fun toSchema(json: String) = toSchema(ObjectMapper().readTree(json)) - fun toSchema(node: JsonNode): Schema { return Schema().apply { node["\$schema"]?.let { this.`$schema` = it.asText() } - node["type"]?.let { this.type = it.asText() } + node["description"]?.let { this.description = it.asText() } + node["title"]?.let { this.title = it.asText() } + node["type"]?.let { + val types = if (it is ArrayNode) it.collectElements().map { e -> e.asText() } else listOf(it.asText()) + this.type = types.firstOrNull { e -> e != "null" } + if (types.contains("null")) { + this.nullable = true + } + } node["format"]?.let { this.format = it.asText() } node["items"]?.let { this.items = toSchema(it) } node["properties"]?.let { this.properties = it.collectFields().associate { prop -> prop.key to toSchema(prop.value) } } @@ -27,12 +35,10 @@ class JsonToOpenApiSchemaConverter { } } - private fun JsonNode.collectFields(): List> { return this.fields().asSequence().toList() } - private fun JsonNode.collectElements(): List { return this.elements().asSequence().toList() } diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaAnnotationExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaAnnotationExample.kt new file mode 100644 index 0000000..f1cfc4e --- /dev/null +++ b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaAnnotationExample.kt @@ -0,0 +1,60 @@ +package io.github.smiley4.ktorswaggerui.examples + +import com.fasterxml.jackson.core.util.DefaultIndenter +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter +import com.fasterxml.jackson.databind.SerializationFeature +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.dsl.get +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.jackson.jackson +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.response.respond +import io.ktor.server.routing.routing +import io.swagger.v3.oas.annotations.media.Schema + +/** + * An example showing the [Schema]-annotation, adding additional information to models + */ +fun main() { + embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) +} + +private fun Application.myModule() { + install(SwaggerUI) + install(ContentNegotiation) { + jackson { + configure(SerializationFeature.INDENT_OUTPUT, true) + setDefaultPrettyPrinter(DefaultPrettyPrinter().apply { + indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance) + indentObjectsWith(DefaultIndenter(" ", "\n")) + }) + } + } + routing { + get("somebody", { + response { + HttpStatusCode.OK to { + body() + } + } + }) { + call.respond(Person("Somebody", 42)) + } + } +} + +@Schema(title = "The Schema for a person") +data class Person( + + @field:Schema(description = "the name of the person") + val name: String, + + @field:Schema(description = "the age of the person in years", nullable = true) + val age: Int + +) \ No newline at end of file