Skip to content

Commit 3f25373

Browse files
committed
Merge branch 'develop' into release
2 parents f4b13c2 + 3c0e8c3 commit 3f25373

File tree

8 files changed

+101
-13
lines changed

8 files changed

+101
-13
lines changed

docs/changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ search:
55

66
# Changelog
77

8+
## 5.5.0
9+
10+
- added Jackson example encoder configured for use with Kotlin (now the new default)
11+
- upgrade schema-kenerator from 2.5.0 to [2.6.0](https://github.com/SMILEY4/schema-kenerator/releases/tag/2.6.0)
12+
- fix: kotlinx-serialization example encoding [#226](https://github.com/SMILEY4/ktor-openapi-tools/pull/226)
13+
814
## 5.4.0
915

1016
- support for ReDoc extensions `x-displayName` and `x-tagGroups` [#221](https://github.com/SMILEY4/ktor-openapi-tools/pull/221), [#219](https://github.com/SMILEY4/ktor-openapi-tools/pull/219)

docs/openapi/providing_openapi_specification.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ val format: OutputFormat = OpenApiPlugin.getOpenApiSpecFormat( // (2)!
5050
Specifications can be regenerated programmatically at any time.
5151

5252
```kotlin
53-
OpenApiPlugin.regenerateOpenApiSpec(OpenApiPluginConfig.DEFAULT_SPEC_ID)
53+
OpenApiPlugin.generateOpenApiSpecs(OpenApiPluginConfig.DEFAULT_SPEC_ID)
5454
```
5555

5656
This discards a previously created specification, collects all routes and information again and generates a new specification.

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ kotlin.code.style=official
22

33
# project id
44
projectGroupId=io.github.smiley4
5-
projectVersion=5.4.0
5+
projectVersion=5.5.0
66

77
# common publishing information
88
projectBaseScmUrl=https://github.com/SMILEY4/
@@ -16,7 +16,7 @@ projectDeveloperUrl=https://github.com/SMILEY4
1616
versionKtor=3.3.2
1717
versionSwaggerUI=5.17.14
1818
versionSwaggerParser=2.1.24
19-
versionSchemaKenerator=2.5.0
19+
versionSchemaKenerator=2.6.0
2020
versionKotlinLogging=7.0.0
2121
versionKotest=5.8.0
2222
versionKotlinTest=2.0.21

ktor-openapi/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ dependencies {
6060
val versionMockk: String by project
6161
testImplementation("io.mockk:mockk:$versionMockk")
6262

63+
val versionLogback: String by project
64+
testImplementation("ch.qos.logback:logback-classic:$versionLogback")
65+
6366
}
6467

6568
kotlin {

ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ExampleEncoder.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.github.smiley4.ktoropenapi.config
22

3-
import com.fasterxml.jackson.core.type.TypeReference
3+
import com.fasterxml.jackson.databind.ObjectMapper
44
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
55
import io.github.smiley4.ktoropenapi.config.descriptors.KTypeDescriptor
66
import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
@@ -23,17 +23,25 @@ object ExampleEncoder {
2323
example
2424
}
2525

26+
/**
27+
* [GenericExampleEncoder] using a Jackson mapper to encode example objects.
28+
* @param json a jackson object mapper encoding objects to json. Set `null` to use a pre-configured object mapper.
29+
*/
30+
fun jackson(json: ObjectMapper? = null): GenericExampleEncoder = { type, example ->
31+
val mapper = json ?: jacksonObjectMapper()
32+
mapper.writeValueAsString(example)
33+
}
34+
2635
/**
2736
* [GenericExampleEncoder] using kotlinx-serialization to encode example objects.
28-
* @param json the kotlinx json serializer to use for encoding objects to json. Set `null` to use default kotlinx json serializer.
37+
* @param json a kotlinx json serializer to use for encoding objects to json. Set `null` to use default kotlinx json serializer.
2938
*/
3039
fun kotlinx(json: Json? = null): GenericExampleEncoder = { type, example ->
3140
when(type) {
3241
is KTypeDescriptor -> {
3342
val jsonEncoder = json ?: Json
34-
val jsonString = jsonEncoder.encodeToString(serializer(type.type), example)
35-
val jsonObj = jacksonObjectMapper().readValue(jsonString, object : TypeReference<Any>() {})
36-
jsonObj
43+
val serializer = jsonEncoder.serializersModule.serializer(type.type)
44+
jsonEncoder.encodeToString(serializer, example)
3745
}
3846
else -> example
3947
}

ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/ExampleConfigData.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal class ExampleConfigData(
1414
val DEFAULT = ExampleConfigData(
1515
sharedExamples = emptyMap(),
1616
securityExamples = null,
17-
exampleEncoder = ExampleEncoder.internal()
17+
exampleEncoder = ExampleEncoder.jackson()
1818
)
1919
}
2020

ktor-openapi/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ class OperationBuilderTest : StringSpec({
440440
param.schema
441441
.also { it.shouldNotBeNull() }
442442
?.also { it.type = "string" }
443-
param.example shouldBe "MyExample"
443+
param.example shouldBe "\"MyExample\""
444444
param.examples shouldBe null
445445
param.content shouldBe null
446446
param.extensions shouldBe null
@@ -501,7 +501,7 @@ class OperationBuilderTest : StringSpec({
501501
?.also { example ->
502502
example.summary shouldBe "the example 1"
503503
example.description shouldBe "the first example"
504-
example.value shouldBe "MyExample1"
504+
example.value shouldBe "\"MyExample1\""
505505
example.externalValue shouldBe null
506506
example.`$ref` shouldBe null
507507
example.extensions shouldBe null
@@ -525,7 +525,7 @@ class OperationBuilderTest : StringSpec({
525525
?.also { example ->
526526
example.summary shouldBe "the example 1"
527527
example.description shouldBe "the first example"
528-
example.value shouldBe "MyExample1"
528+
example.value shouldBe "\"MyExample1\""
529529
example.externalValue shouldBe null
530530
example.`$ref` shouldBe null
531531
example.extensions shouldBe null
@@ -787,7 +787,7 @@ class OperationBuilderTest : StringSpec({
787787
content.schema.`$ref` shouldBe "#/components/schemas/${SimpleObject::class.qualifiedName}"
788788
val example = content.examples["Example 1"]
789789
example.shouldNotBeNull()
790-
example.value shouldBeEqual SimpleObject(text = "Some text", number = 123)
790+
example.value shouldBeEqual "{\"text\":\"Some text\",\"number\":123}"
791791
}
792792
responses["default"]
793793
.also { it.shouldNotBeNull() }
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package io.github.smiley4.ktorswaggerui.misc
2+
3+
import io.github.smiley4.ktoropenapi.OpenApi
4+
import io.github.smiley4.ktoropenapi.config.ExampleEncoder
5+
import io.github.smiley4.ktoropenapi.get
6+
import io.github.smiley4.ktoropenapi.openApi
7+
import io.kotest.core.spec.style.StringSpec
8+
import io.kotest.matchers.shouldNotBe
9+
import io.ktor.client.request.get
10+
import io.ktor.client.statement.bodyAsText
11+
import io.ktor.http.HttpStatusCode
12+
import io.ktor.server.response.respond
13+
import io.ktor.server.routing.route
14+
import io.ktor.server.testing.testApplication
15+
import kotlinx.serialization.KSerializer
16+
import kotlinx.serialization.descriptors.PrimitiveKind
17+
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
18+
import kotlinx.serialization.descriptors.SerialDescriptor
19+
import kotlinx.serialization.encoding.Decoder
20+
import kotlinx.serialization.encoding.Encoder
21+
import kotlinx.serialization.json.Json
22+
import kotlinx.serialization.modules.SerializersModule
23+
import java.time.LocalDate
24+
import java.time.format.DateTimeFormatter
25+
26+
27+
class ExampleEncoderTest : StringSpec({
28+
29+
val json = Json {
30+
classDiscriminator = "_type"
31+
serializersModule = SerializersModule {
32+
contextual(LocalDate::class, object : KSerializer<LocalDate> {
33+
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE
34+
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
35+
override fun deserialize(decoder: Decoder) = LocalDate.parse(decoder.decodeString(), formatter)
36+
override fun serialize(encoder: Encoder, value: LocalDate) = encoder.encodeString(formatter.format(value))
37+
})
38+
}
39+
}
40+
41+
"ExampleEncoder.kotlinx should not fail with contextual type" {
42+
testApplication {
43+
install(OpenApi) {
44+
examples {
45+
encoder(ExampleEncoder.kotlinx(json))
46+
}
47+
}
48+
routing {
49+
route("api.yml") {
50+
openApi()
51+
}
52+
53+
get("test", {
54+
response {
55+
HttpStatusCode.OK to {
56+
body<LocalDate> {
57+
example("example") {
58+
value = LocalDate.of(2025, 12, 9)
59+
}
60+
}
61+
}
62+
}
63+
}) {
64+
call.respond(HttpStatusCode.OK)
65+
}
66+
}
67+
68+
createClient {}.get("api.yml").bodyAsText() shouldNotBe "{}"
69+
}
70+
}
71+
})

0 commit comments

Comments
 (0)