Skip to content

Commit f0cdba6

Browse files
committed
Merge branch 'develop' into release
2 parents 0a85abe + 12cfd09 commit f0cdba6

File tree

15 files changed

+206
-36
lines changed

15 files changed

+206
-36
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ dependencies {
3232
```
3333

3434

35+
## Ktor compatibility
36+
37+
- Ktor 2.x: ktor-swagger-ui up to 3.x
38+
- Ktor 3.x: ktor-swagger-ui starting with 4.0
39+
40+
3541
## Examples
3642

3743
Runnable examples can be found in [ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples](https://github.com/SMILEY4/ktor-swagger-ui/tree/release/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples).

gradle.properties

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ kotlin.code.style=official
33
# project id
44
projectGroupId=io.github.smiley4
55
projectArtifactIdBase=ktor-swagger-ui
6-
projectVersion=4.0.0
6+
projectVersion=4.1.0
77

88
# publishing information
99
projectNameBase=Ktor Swagger UI
@@ -19,9 +19,10 @@ projectDeveloperUrl=https://github.com/SMILEY4
1919
versionKtor=3.0.0
2020
versionSwaggerUI=5.17.11
2121
versionSwaggerParser=2.1.22
22-
versionSchemaKenerator=1.5.0
22+
versionSchemaKenerator=1.6.0
2323
versionKotlinLogging=7.0.0
2424
versionKotest=5.8.0
2525
versionKotlinTest=2.0.21
2626
versionMockk=1.13.12
2727
versionLogback=1.5.6
28+
versionJackson=2.18.1

ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ private fun Application.myModule() {
4242

4343
// Install and configure the "SwaggerUI"-Plugin
4444
install(SwaggerUI) {
45+
schemas { }
4546
security {
4647
// configure a basic-auth security scheme
4748
securityScheme("MySecurityScheme") {
@@ -53,6 +54,7 @@ private fun Application.myModule() {
5354
// if no other response is documented for "401 Unauthorized", this information is used instead
5455
defaultUnauthorizedResponse {
5556
description = "Username or password is invalid"
57+
body<AuthRequired>()
5658
}
5759
}
5860
}
@@ -74,6 +76,12 @@ private fun Application.myModule() {
7476
}) {
7577
call.respondText("Hello World!")
7678
}
79+
80+
get("protected2", {
81+
// response for "401 Unauthorized" is automatically added if configured in the plugin-config and not specified otherwise
82+
}) {
83+
call.respondText("Hello World!")
84+
}
7785
}
7886

7987
// route is not in an "authenticate"-block but "protected"-flag is set (e.g. because is it protected by an external reverse-proxy
@@ -96,3 +104,6 @@ private fun Application.myModule() {
96104
}
97105

98106
}
107+
108+
109+
class AuthRequired(val message: String)

ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ private fun Application.myModule() {
138138
specAssigner = { _, _ -> PluginConfigDsl.DEFAULT_SPEC_ID }
139139
pathFilter = { _, url -> url.firstOrNull() != "hidden" }
140140
ignoredRouteSelectors = emptySet()
141+
ignoredRouteSelectorClassNames = emptySet()
141142
postBuild = { api, name -> println("Completed api '$name': $api") }
142143
}
143144

ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizedSchemaGenerator.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import io.github.smiley4.schemakenerator.serialization.processKotlinxSerializati
88
import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot
99
import io.github.smiley4.schemakenerator.swagger.data.TitleType
1010
import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema
11-
import io.github.smiley4.schemakenerator.swagger.withAutoTitle
11+
import io.github.smiley4.schemakenerator.swagger.withTitle
1212
import io.ktor.http.HttpStatusCode
1313
import io.ktor.server.application.Application
14-
import io.ktor.server.application.call
1514
import io.ktor.server.application.install
1615
import io.ktor.server.engine.embeddedServer
1716
import io.ktor.server.netty.Netty
@@ -37,7 +36,7 @@ private fun Application.myModule() {
3736
// see https://github.com/SMILEY4/schema-kenerator for more information
3837
.processKotlinxSerialization()
3938
.generateSwaggerSchema()
40-
.withAutoTitle(TitleType.SIMPLE)
39+
.withTitle(TitleType.SIMPLE)
4140
.compileReferencingRoot()
4241
}
4342
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.github.smiley4.ktorswaggerui.examples
2+
3+
import io.github.smiley4.ktorswaggerui.SwaggerUI
4+
import io.github.smiley4.ktorswaggerui.data.kotlinxExampleEncoder
5+
import io.github.smiley4.ktorswaggerui.dsl.routing.get
6+
import io.github.smiley4.ktorswaggerui.routing.openApiSpec
7+
import io.github.smiley4.ktorswaggerui.routing.swaggerUI
8+
import io.github.smiley4.schemakenerator.serialization.processKotlinxSerialization
9+
import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot
10+
import io.github.smiley4.schemakenerator.swagger.data.TitleType
11+
import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema
12+
import io.github.smiley4.schemakenerator.swagger.withTitle
13+
import io.ktor.http.HttpStatusCode
14+
import io.ktor.server.application.Application
15+
import io.ktor.server.application.install
16+
import io.ktor.server.engine.embeddedServer
17+
import io.ktor.server.netty.Netty
18+
import io.ktor.server.response.respondText
19+
import io.ktor.server.routing.route
20+
import io.ktor.server.routing.routing
21+
import kotlinx.serialization.Serializable
22+
23+
fun main() {
24+
embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
25+
}
26+
27+
private fun Application.myModule() {
28+
29+
install(SwaggerUI) {
30+
schemas {
31+
// configure the schema generator to use kotlinx-serializer
32+
// (see https://github.com/SMILEY4/schema-kenerator/wiki for more information)
33+
generator = { type ->
34+
type
35+
.processKotlinxSerialization()
36+
.generateSwaggerSchema()
37+
.withTitle(TitleType.SIMPLE)
38+
.compileReferencingRoot()
39+
}
40+
}
41+
examples {
42+
// configure the example encoder to encode kotlin objects using kotlinx-serializer
43+
exampleEncoder = kotlinxExampleEncoder
44+
}
45+
}
46+
47+
routing {
48+
49+
// add the routes for swagger-ui and api-spec
50+
route("swagger") {
51+
swaggerUI("/api.json")
52+
}
53+
route("api.json") {
54+
openApiSpec()
55+
}
56+
57+
// a documented route
58+
get("hello", {
59+
description = "A Hello-World route"
60+
request {
61+
queryParameter<String>("name") {
62+
description = "the name to greet"
63+
example("Name Parameter") {
64+
value = "Mr. Example"
65+
}
66+
}
67+
}
68+
response {
69+
code(HttpStatusCode.OK) {
70+
description = "successful request - always returns 'Hello World!'"
71+
body<TestResponse> {
72+
example("Success Response") {
73+
value = TestResponse(
74+
name = "Mr. Example",
75+
length = 11
76+
)
77+
}
78+
}
79+
}
80+
}
81+
}) {
82+
call.respondText("Hello ${call.request.queryParameters["name"]}")
83+
}
84+
85+
}
86+
87+
}
88+
89+
90+
@Serializable
91+
data class TestResponse(
92+
val name: String,
93+
val length: Int,
94+
)

ktor-swagger-ui/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
val versionKotest: String by project
3030
val versionKotlinTest: String by project
3131
val versionMockk: String by project
32+
val versionJackson: String by project
3233

3334
implementation("io.ktor:ktor-server-core-jvm:$versionKtor")
3435
implementation("io.ktor:ktor-server-auth:$versionKtor")
@@ -38,6 +39,8 @@ dependencies {
3839

3940
implementation("io.swagger.parser.v3:swagger-parser:$versionSwaggerParser")
4041

42+
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$versionJackson")
43+
4144
implementation("io.github.smiley4:schema-kenerator-core:$versionSchemaKenerator")
4245
implementation("io.github.smiley4:schema-kenerator-reflection:$versionSchemaKenerator")
4346
implementation("io.github.smiley4:schema-kenerator-swagger:$versionSchemaKenerator")
@@ -54,6 +57,7 @@ dependencies {
5457
testImplementation("io.kotest:kotest-assertions-core:$versionKotest")
5558
testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest")
5659
testImplementation("io.mockk:mockk:$versionMockk")
60+
5761
}
5862

5963
kotlin {

ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class RouteCollector(
7575
is ParameterRouteSelector -> route.parent?.let { getPath(it, config) } ?: ""
7676
is ConstantParameterRouteSelector -> route.parent?.let { getPath(it, config) } ?: ""
7777
is OptionalParameterRouteSelector -> route.parent?.let { getPath(it, config) } ?: ""
78-
else -> (route.parent?.let { getPath(it, config) } ?: "") + "/" + route.selector.toString()
78+
else -> (route.parent?.let { getPath(it, config) } ?: "").dropLastWhile { it == '/' } + "/" + route.selector.toString()
7979
}
8080
}
8181
}
@@ -91,7 +91,8 @@ class RouteCollector(
9191
is ParameterRouteSelector -> true
9292
is ConstantParameterRouteSelector -> true
9393
is OptionalParameterRouteSelector -> true
94-
else -> config.ignoredRouteSelectors.any { selector::class.isSubclassOf(it) }
94+
else -> config.ignoredRouteSelectors.any { selector::class.isSubclassOf(it) } or
95+
config.ignoredRouteSelectorClassNames.any { selector::class.java.name == it }
9596
}
9697
}
9798

@@ -112,5 +113,4 @@ class RouteCollector(
112113
return (listOf(root) + root.children.flatMap { allRoutes(it) })
113114
.filter { it.selector is HttpMethodRouteSelector }
114115
}
115-
116116
}

ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,21 @@ class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : SchemaCont
1515

1616
fun addGlobal(config: SchemaConfigData) {
1717
config.securitySchemas.forEach { typeDescriptor ->
18-
val schema = collapseRootRef(generateSchema(typeDescriptor))
18+
val schema = generateSchema(typeDescriptor)
1919
rootSchemas[typeDescriptor] = schema.swagger
20+
schema.componentSchemas.forEach { (k, v) ->
21+
componentSchemas[k] = v
22+
}
2023
}
2124
config.schemas.forEach { (schemaId, typeDescriptor) ->
22-
val schema = collapseRootRef(generateSchema(typeDescriptor))
25+
val schema = generateSchema(typeDescriptor)
2326
componentSchemas[schemaId] = schema.swagger
2427
schema.componentSchemas.forEach { (k, v) ->
2528
componentSchemas[k] = v
2629
}
2730
}
2831
}
2932

30-
private fun collapseRootRef(schema: CompiledSwaggerSchema): CompiledSwaggerSchema {
31-
if (schema.swagger.`$ref` == null) {
32-
return schema
33-
} else {
34-
val referencedSchemaId = schema.swagger.`$ref`!!.replace("#/components/schemas/", "")
35-
val referencedSchema = schema.componentSchemas[referencedSchemaId]!!
36-
return CompiledSwaggerSchema(
37-
typeData = schema.typeData,
38-
swagger = referencedSchema,
39-
componentSchemas = schema.componentSchemas.toMutableMap().also {
40-
it.remove(referencedSchemaId)
41-
}
42-
)
43-
}
44-
}
45-
4633
fun add(routes: Collection<RouteMeta>) {
4734
collectTypeDescriptor(routes).forEach { typeDescriptor ->
4835
val schema = generateSchema(typeDescriptor)

ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
package io.github.smiley4.ktorswaggerui.data
22

3+
import com.fasterxml.jackson.core.type.TypeReference
4+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
5+
import kotlinx.serialization.json.Json
6+
import kotlinx.serialization.serializer
7+
38
/**
49
* Encoder to produce the final example value.
510
* Return the unmodified example to fall back to the default encoder.
611
*/
712
typealias ExampleEncoder = (type: TypeDescriptor?, example: Any?) -> Any?
813

14+
/**
15+
* [ExampleEncoder] using kotlinx-serialization to encode example objects.
16+
*/
17+
val kotlinxExampleEncoder: ExampleEncoder = { type, example ->
18+
if (type is KTypeDescriptor) {
19+
val jsonString = Json.encodeToString(serializer(type.type), example)
20+
val jsonObj = jacksonObjectMapper().readValue(jsonString, object : TypeReference<Any>() {})
21+
jsonObj
22+
} else {
23+
example
24+
}
25+
}
26+
927
class ExampleConfigData(
1028
val sharedExamples: Map<String, ExampleDescriptor>,
1129
val securityExamples: OpenApiSimpleBodyData?,

0 commit comments

Comments
 (0)