Skip to content

Commit 63296fd

Browse files
committed
Merge branch 'develop' into release
2 parents 4959df6 + 68976b4 commit 63296fd

12 files changed

Lines changed: 86 additions & 40 deletions

File tree

.github/workflows/checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Checks
22

3-
on: [ push, pull_request ]
3+
on: [ push, pull_request, workflow_dispatch ]
44

55
permissions:
66
contents: read

gradle.properties

Lines changed: 1 addition & 1 deletion
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=3.0.1
6+
projectVersion=3.1.0
77

88
# publishing information
99
projectNameBase=Ktor Swagger UI

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.smiley4.ktorswaggerui.examples
22

33
import io.github.smiley4.ktorswaggerui.SwaggerUI
4+
import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor
45
import io.github.smiley4.ktorswaggerui.dsl.routing.get
56
import io.github.smiley4.ktorswaggerui.routing.openApiSpec
67
import io.github.smiley4.ktorswaggerui.routing.swaggerUI
@@ -12,6 +13,7 @@ import io.ktor.server.netty.Netty
1213
import io.ktor.server.response.respondText
1314
import io.ktor.server.routing.route
1415
import io.ktor.server.routing.routing
16+
import kotlin.reflect.typeOf
1517

1618
fun main() {
1719
embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
@@ -37,6 +39,15 @@ private fun Application.myModule() {
3739
)
3840
}
3941

42+
// encoder used in "custom-encoder" example
43+
encoder { type, example ->
44+
// encode just the wrapped value for CustomEncoderData class
45+
if (type is KTypeDescriptor && type.type == typeOf<CustomEncoderData>())
46+
(example as CustomEncoderData).number
47+
// return the example unmodified to fall back to default encoder
48+
else
49+
example
50+
}
4051
}
4152
}
4253

@@ -86,6 +97,19 @@ private fun Application.myModule() {
8697
call.respondText("...")
8798
}
8899

100+
get("custom-encoder", {
101+
request {
102+
body<CustomEncoderData> {
103+
// The type is CustomEncoderData, but it's actually encoded as a plain number
104+
// See configuration for encoder
105+
example("Example 1") {
106+
value = CustomEncoderData(123)
107+
}
108+
}
109+
}
110+
}) {
111+
call.respondText("...")
112+
}
89113
}
90114

91115
}
@@ -94,3 +118,7 @@ private fun Application.myModule() {
94118
private data class MyExampleClass(
95119
val someValue: String
96120
)
121+
122+
private data class CustomEncoderData(
123+
val number: Int
124+
)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta
3030
import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext
3131
import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextImpl
3232
import io.github.smiley4.ktorswaggerui.data.PluginConfigData
33+
import io.github.smiley4.ktorswaggerui.data.TypeDescriptor
3334
import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl
3435
import io.github.smiley4.ktorswaggerui.routing.ApiSpec
3536
import io.ktor.server.application.Application
@@ -93,7 +94,7 @@ private fun buildOpenApiSpec(pluginConfig: PluginConfigData, routes: List<RouteM
9394
it.addGlobal(pluginConfig.schemaConfig)
9495
it.add(routes)
9596
}
96-
val exampleContext = ExampleContextImpl().also {
97+
val exampleContext = ExampleContextImpl(pluginConfig.exampleConfig.exampleEncoder).also {
9798
it.addShared(pluginConfig.exampleConfig)
9899
it.add(routes)
99100
}

ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import io.swagger.v3.oas.models.examples.Example
99
interface ExampleContext {
1010

1111
/**
12-
* Get an [Example] (or a ref to an example) by its [ExampleDescriptor]
12+
* Get an [Example] (or a ref to an example) by its [ExampleDescriptor].
1313
*/
1414
fun getExample(descriptor: ExampleDescriptor): Example
1515

ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import io.swagger.v3.oas.models.examples.Example
77
/**
88
* Implementation of an [ExampleContext].
99
*/
10-
class ExampleContextImpl : ExampleContext {
10+
class ExampleContextImpl(private val encoder: ExampleEncoder?) : ExampleContext {
1111

1212
private val rootExamples = mutableMapOf<ExampleDescriptor, Example>()
1313
private val componentExamples = mutableMapOf<String, Example>()
@@ -18,11 +18,11 @@ class ExampleContextImpl : ExampleContext {
1818
*/
1919
fun addShared(config: ExampleConfigData) {
2020
config.sharedExamples.forEach { (_, exampleDescriptor) ->
21-
val example = generateExample(exampleDescriptor)
21+
val example = generateExample(exampleDescriptor, null)
2222
componentExamples[exampleDescriptor.name] = example
2323
}
2424
config.securityExamples.forEach { exampleDescriptor ->
25-
val example = generateExample(exampleDescriptor)
25+
val example = generateExample(exampleDescriptor, null)
2626
rootExamples[exampleDescriptor] = example
2727
}
2828
}
@@ -32,55 +32,59 @@ class ExampleContextImpl : ExampleContext {
3232
* Collect and add all examples for the given routes
3333
*/
3434
fun add(routes: Collection<RouteMeta>) {
35-
collectExampleDescriptors(routes).forEach { exampleDescriptor ->
36-
rootExamples[exampleDescriptor] = generateExample(exampleDescriptor)
35+
collectExampleDescriptors(routes).forEach { (exampleDescriptor, typeDescriptor) ->
36+
val example = generateExample(exampleDescriptor, typeDescriptor)
37+
rootExamples[exampleDescriptor] = example
3738
}
3839
}
3940

4041

4142
/**
4243
* Collect all [ExampleDescriptor]s from the given routes
4344
*/
44-
private fun collectExampleDescriptors(routes: Collection<RouteMeta>): List<ExampleDescriptor> {
45-
val descriptors = mutableListOf<ExampleDescriptor>()
46-
routes
47-
.filter { !it.documentation.hidden }
48-
.forEach { route ->
49-
route.documentation.request.also { request ->
50-
request.parameters.forEach { parameter ->
51-
parameter.example?.also { descriptors.add(it) }
52-
}
53-
request.body?.also { body ->
54-
if (body is OpenApiSimpleBodyData) {
55-
descriptors.addAll(body.examples)
45+
private fun collectExampleDescriptors(routes: Collection<RouteMeta>): List<Pair<ExampleDescriptor, TypeDescriptor>> =
46+
buildList {
47+
routes
48+
.filter { !it.documentation.hidden }
49+
.forEach { route ->
50+
route.documentation.request.also { request ->
51+
request.parameters.forEach { parameter ->
52+
parameter.example?.also { add(it to parameter.type) }
53+
}
54+
request.body?.also { body ->
55+
if (body is OpenApiSimpleBodyData) {
56+
addAll(body.examples.map { it to body.type })
57+
}
5658
}
5759
}
58-
}
59-
route.documentation.responses.forEach { response ->
60-
response.body?.also { body ->
61-
if (body is OpenApiSimpleBodyData) {
62-
descriptors.addAll(body.examples)
60+
route.documentation.responses.forEach { response ->
61+
response.body?.also { body ->
62+
if (body is OpenApiSimpleBodyData) {
63+
addAll(body.examples.map { it to body.type })
64+
}
6365
}
6466
}
6567
}
66-
}
67-
return descriptors
68-
}
68+
}
6969

7070

7171
/**
7272
* Generate a swagger [Example] from the given [ExampleDescriptor]
7373
*/
74-
private fun generateExample(exampleDescriptor: ExampleDescriptor): Example {
74+
private fun generateExample(exampleDescriptor: ExampleDescriptor, type: TypeDescriptor?): Example {
7575
return when (exampleDescriptor) {
7676
is ValueExampleDescriptor -> Example().also {
77-
it.value = exampleDescriptor.value
77+
it.value =
78+
if (encoder != null) encoder.invoke(type, exampleDescriptor.value)
79+
else exampleDescriptor.value
7880
it.summary = exampleDescriptor.summary
7981
it.description = exampleDescriptor.description
8082
}
83+
8184
is RefExampleDescriptor -> Example().also {
8285
it.`$ref` = "#/components/examples/${exampleDescriptor.refName}"
8386
}
87+
8488
is SwaggerExampleDescriptor -> exampleDescriptor.example
8589
}
8690
}

ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext
55
import io.github.smiley4.ktorswaggerui.data.OpenApiBaseBodyData
66
import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartBodyData
77
import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData
8-
import io.ktor.http.ContentType
8+
import io.ktor.http.*
99
import io.swagger.v3.oas.models.media.Content
1010
import io.swagger.v3.oas.models.media.Encoding
1111
import io.swagger.v3.oas.models.media.MediaType

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

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

3+
/**
4+
* Encoder to produce the final example value.
5+
* Return the unmodified example to fall back to the default encoder.
6+
*/
7+
typealias ExampleEncoder = (type: TypeDescriptor?, example: Any?) -> Any?
8+
39
class ExampleConfigData(
410
val sharedExamples: Map<String, ExampleDescriptor>,
5-
val securityExamples: List<ExampleDescriptor>
11+
val securityExamples: List<ExampleDescriptor>,
12+
val exampleEncoder: ExampleEncoder?
613
) {
714

815
companion object {
916
val DEFAULT = ExampleConfigData(
1017
sharedExamples = emptyMap(),
11-
securityExamples = emptyList()
18+
securityExamples = emptyList(),
19+
exampleEncoder = null
1220
)
1321
}
1422

ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import io.github.smiley4.ktorswaggerui.dsl.routes.ValueExampleDescriptorDsl
66
import io.swagger.v3.oas.models.examples.Example
77

88
/**
9-
* Configuration for schemas
9+
* Configuration for examples
1010
*/
1111
@OpenApiDslMarker
1212
class ExampleConfig {
1313

1414
val sharedExamples = mutableMapOf<String, ExampleDescriptor>()
15+
var exampleEncoder: ExampleEncoder? = null
1516

1617
fun example(example: ExampleDescriptor) {
1718
sharedExamples[example.name] = example
@@ -32,14 +33,18 @@ class ExampleConfig {
3233
}
3334
)
3435

36+
fun encoder(exampleEncoder: ExampleEncoder) {
37+
this.exampleEncoder = exampleEncoder
38+
}
39+
3540
fun build(securityConfig: SecurityData) = ExampleConfigData(
3641
sharedExamples = sharedExamples,
3742
securityExamples = securityConfig.defaultUnauthorizedResponse?.body?.let {
3843
when (it) {
3944
is OpenApiSimpleBodyData -> it.examples
4045
is OpenApiMultipartBodyData -> emptyList()
4146
}
42-
} ?: emptyList()
47+
} ?: emptyList(),
48+
exampleEncoder = exampleEncoder
4349
)
44-
4550
}

ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class OpenApiBuilderTest : StringSpec({
116116

117117
private fun exampleContext(routes: List<RouteMeta>, pluginConfig: PluginConfigDsl): ExampleContext {
118118
val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT)
119-
return ExampleContextImpl().also {
119+
return ExampleContextImpl(pluginConfigData.exampleConfig.exampleEncoder).also {
120120
it.addShared(pluginConfigData.exampleConfig)
121121
it.add(routes)
122122
}

0 commit comments

Comments
 (0)