Skip to content

Commit f74f6f7

Browse files
committed
[Bugfix] Adjust $ref handling logic in DefaultJsonSchemaFactory to avoid replacing nested type name if it shares the prefix of the root class name
When you have a type that shares the name of it's parent type (eg: Task & TaskType), with the way the current `fixRefsPrefix` works (recurses through all subtypes to handle nested types) you end up replacing the def path of the subtype and it becomes unserialisable and crashes at runtime, and you have to use custom serialisers or rename your class. This checks that the root definition is followed by a / (to indicate nesting) before replacing it).
1 parent ec4e244 commit f74f6f7

2 files changed

Lines changed: 71 additions & 5 deletions

File tree

sdk-serde-kotlinx/src/main/kotlin/dev/restate/serde/kotlinx/DefaultJsonSchemaFactory.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import io.github.smiley4.schemakenerator.jsonschema.jsonDsl.array
2323
import io.github.smiley4.schemakenerator.serialization.SerializationSteps.analyzeTypeUsingKotlinxSerialization
2424
import io.github.smiley4.schemakenerator.serialization.SerializationSteps.initial
2525
import io.github.smiley4.schemakenerator.serialization.SerializationSteps.renameMembers
26-
import kotlin.collections.set
2726
import kotlinx.serialization.ExperimentalSerializationApi
2827
import kotlinx.serialization.KSerializer
2928
import kotlinx.serialization.json.Json
@@ -67,7 +66,7 @@ object DefaultJsonSchemaFactory : KotlinSerializationSerdeFactory.JsonSchemaFact
6766
// Add $schema
6867
rootNode.properties.put(
6968
"\$schema",
70-
JsonTextValue("https://json-schema.org/draft/2020-12/schema"),
69+
JsonTextValue("https://json-schema.org/draft/2020-12/schema"),
7170
)
7271
// Add $defs
7372
val definitions =
@@ -109,7 +108,7 @@ object DefaultJsonSchemaFactory : KotlinSerializationSerdeFactory.JsonSchemaFact
109108
(schema.json as JsonObject).properties["title"] == null
110109
) {
111110
(schema.json as JsonObject).properties["title"] =
112-
JsonTextValue(TitleBuilder.BUILDER_SIMPLE(schema.typeData, this.typeDataById))
111+
JsonTextValue(TitleBuilder.BUILDER_SIMPLE(schema.typeData, this.typeDataById))
113112
}
114113
}
115114
}
@@ -126,8 +125,10 @@ object DefaultJsonSchemaFactory : KotlinSerializationSerdeFactory.JsonSchemaFact
126125
private fun JsonObject.fixRefsPrefix(rootDefinition: String) {
127126
this.properties.computeIfPresent("\$ref") { key, node ->
128127
if (node is JsonTextValue) {
129-
if (node.value.startsWith(rootDefinition)) {
130-
JsonTextValue("#/" + node.value.removePrefix(rootDefinition))
128+
if (node.value == rootDefinition) {
129+
JsonTextValue("#/")
130+
} else if (node.value.startsWith("$rootDefinition/")) {
131+
JsonTextValue("#/" + node.value.removePrefix("$rootDefinition/"))
131132
} else {
132133
JsonTextValue("#/\$defs/" + node.value.removePrefix("#/definitions/"))
133134
}

sdk-serde-kotlinx/src/test/kotlin/dev/restate/serde/kotlinx/KotlinxSerdeTest.kt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,71 @@ class KotlinxSerdeTest {
176176
)
177177
}
178178

179+
@Serializable
180+
enum class TaskStatus {
181+
TODO,
182+
IN_PROGRESS,
183+
DONE,
184+
}
185+
186+
@Serializable
187+
enum class PriorityOrder {
188+
HIGH,
189+
MID,
190+
LOW,
191+
}
192+
193+
@Serializable
194+
data class Task(val title: String, val status: TaskStatus, val priority: PriorityOrder)
195+
196+
@Test
197+
fun schemaGenWithExternalEnum() {
198+
testSchemaGen<Task>(
199+
$$"""
200+
{
201+
"type": "object",
202+
"required": [
203+
"title",
204+
"status",
205+
"priority"
206+
],
207+
"properties": {
208+
"title": {
209+
"type": "string"
210+
},
211+
"priority": {
212+
"$ref": "#/$defs/PriorityOrder"
213+
},
214+
"status": {
215+
"$ref": "#/$defs/TaskStatus"
216+
}
217+
},
218+
"title": "Task",
219+
"$schema": "https://json-schema.org/draft/2020-12/schema",
220+
"$defs": {
221+
"TaskStatus": {
222+
"enum": [
223+
"TODO",
224+
"IN_PROGRESS",
225+
"DONE"
226+
],
227+
"title": "TaskStatus"
228+
},
229+
"PriorityOrder": {
230+
"enum": [
231+
"HIGH",
232+
"MID",
233+
"LOW"
234+
],
235+
"title": "PriorityOrder"
236+
}
237+
}
238+
}
239+
"""
240+
.trimIndent()
241+
)
242+
}
243+
179244
inline fun <reified T : Any?> testSchemaGen(expectedSchema: String) {
180245
val expectedJsonElement = Json.decodeFromString<JsonElement>(expectedSchema)
181246
val actualSchema =

0 commit comments

Comments
 (0)