Skip to content

Commit f0c27da

Browse files
authored
fix: correctly deserialize lists of documents (#748)
1 parent 1f954c8 commit f0c27da

File tree

4 files changed

+92
-20
lines changed

4 files changed

+92
-20
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "2032f172-904d-474b-b554-4d7fa04e160c",
3+
"type": "bugfix",
4+
"description": "Fix deserialization error for shapes with lists of document types"
5+
}

smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/lang/KotlinTypes.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ object KotlinTypes {
3838
val List: Symbol = builtInSymbol("List", "kotlin.collections")
3939
val Set: Symbol = builtInSymbol("Set", "kotlin.collections")
4040
val Map: Symbol = builtInSymbol("Map", "kotlin.collections")
41+
val mutableListOf: Symbol = builtInSymbol("mutableListOf", "kotlin.collections")
42+
val mutableMapOf: Symbol = builtInSymbol("mutableMapOf", "kotlin.collections")
4143
}
4244

4345
object Jvm {

smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/DeserializeStructGenerator.kt

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package software.amazon.smithy.kotlin.codegen.rendering.serde
66

77
import software.amazon.smithy.codegen.core.CodegenException
88
import software.amazon.smithy.kotlin.codegen.core.*
9+
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
910
import software.amazon.smithy.kotlin.codegen.model.*
1011
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
1112
import software.amazon.smithy.model.shapes.*
@@ -137,15 +138,20 @@ open class DeserializeStructGenerator(
137138
val nestingLevel = 0
138139
val memberName = ctx.symbolProvider.toMemberName(memberShape)
139140
val descriptorName = memberShape.descriptorName()
140-
val mutableCollectionType = targetShape.mutableCollectionType()
141141
val valueCollector = deserializationResultName("builder.$memberName")
142142
val mutableCollectionName = nestingLevel.variableNameFor(NestedIdentifierType.MAP)
143143
val collectionReturnExpression = collectionReturnExpression(memberShape, mutableCollectionName)
144144

145145
writer.write("$descriptorName.index -> $valueCollector = ")
146146
.indent()
147147
.withBlock("deserializer.#T($descriptorName) {", "}", RuntimeTypes.Serde.deserializeMap) {
148-
write("val $mutableCollectionName = $mutableCollectionType()")
148+
write(
149+
"val #L = #T<String, #T#L>()",
150+
mutableCollectionName,
151+
KotlinTypes.Collections.mutableMapOf,
152+
ctx.symbolProvider.toSymbol(targetShape.value),
153+
nullabilitySuffix(targetShape.isSparse),
154+
)
149155
withBlock("while (hasNextEntry()) {", "}") {
150156
delegateMapDeserialization(memberShape, targetShape, nestingLevel, mutableCollectionName)
151157
}
@@ -252,7 +258,6 @@ open class DeserializeStructGenerator(
252258
val valueName = nestingLevel.variableNameFor(NestedIdentifierType.VALUE)
253259
val populateNullValuePostfix = if (isSparse) "" else "; continue"
254260
val descriptorName = rootMemberShape.descriptorName(nestingLevel.nestedDescriptorName())
255-
val mutableCollectionType = mapShape.mutableCollectionType()
256261
val nextNestingLevel = nestingLevel + 1
257262
val memberName = nextNestingLevel.variableNameFor(NestedIdentifierType.MAP)
258263
val collectionReturnExpression = collectionReturnExpression(rootMemberShape, memberName)
@@ -261,7 +266,13 @@ open class DeserializeStructGenerator(
261266
writer.withBlock("val $valueName =", "") {
262267
withBlock("if (nextHasValue()) {", "} else { deserializeNull()$populateNullValuePostfix }") {
263268
withBlock("deserializer.#T($descriptorName) {", "}", RuntimeTypes.Serde.deserializeMap) {
264-
write("val $memberName = $mutableCollectionType()")
269+
write(
270+
"val #L = #T<String, #T#L>()",
271+
memberName,
272+
KotlinTypes.Collections.mutableMapOf,
273+
ctx.symbolProvider.toSymbol(mapShape.value),
274+
nullabilitySuffix(mapShape.isSparse),
275+
)
265276
withBlock("while (hasNextEntry()) {", "}") {
266277
delegateMapDeserialization(rootMemberShape, mapShape, nextNestingLevel, memberName)
267278
}
@@ -298,7 +309,6 @@ open class DeserializeStructGenerator(
298309
val valueName = nestingLevel.variableNameFor(NestedIdentifierType.VALUE)
299310
val populateNullValuePostfix = if (isSparse) "" else "; continue"
300311
val descriptorName = rootMemberShape.descriptorName(nestingLevel.nestedDescriptorName())
301-
val mutableCollectionType = collectionShape.mutableCollectionType()
302312
val nextNestingLevel = nestingLevel + 1
303313
val memberName = nextNestingLevel.variableNameFor(NestedIdentifierType.COLLECTION)
304314
val collectionReturnExpression = collectionReturnExpression(rootMemberShape, memberName)
@@ -307,7 +317,13 @@ open class DeserializeStructGenerator(
307317
writer.withBlock("val $valueName =", "") {
308318
withBlock("if (nextHasValue()) {", "} else { deserializeNull()$populateNullValuePostfix }") {
309319
withBlock("deserializer.#T($descriptorName) {", "}", RuntimeTypes.Serde.deserializeList) {
310-
write("val $memberName = $mutableCollectionType()")
320+
write(
321+
"val #L = #T<#T#L>()",
322+
memberName,
323+
KotlinTypes.Collections.mutableListOf,
324+
ctx.symbolProvider.toSymbol(collectionShape.member),
325+
nullabilitySuffix(collectionShape.isSparse),
326+
)
311327
withBlock("while (hasNextElement()) {", "}") {
312328
delegateListDeserialization(rootMemberShape, collectionShape, nextNestingLevel, memberName)
313329
}
@@ -353,15 +369,20 @@ open class DeserializeStructGenerator(
353369
val nestingLevel = 0
354370
val memberName = ctx.symbolProvider.toMemberName(memberShape)
355371
val descriptorName = memberShape.descriptorName()
356-
val mutableCollectionType = targetShape.mutableCollectionType()
357372
val valueCollector = deserializationResultName("builder.$memberName")
358373
val mutableCollectionName = nestingLevel.variableNameFor(NestedIdentifierType.COLLECTION)
359374
val collectionReturnExpression = collectionReturnExpression(memberShape, mutableCollectionName)
360375

361376
writer.write("$descriptorName.index -> $valueCollector = ")
362377
.indent()
363378
.withBlock("deserializer.#T($descriptorName) {", "}", RuntimeTypes.Serde.deserializeList) {
364-
write("val $mutableCollectionName = $mutableCollectionType()")
379+
write(
380+
"val #L = #T<#T#L>()",
381+
mutableCollectionName,
382+
KotlinTypes.Collections.mutableListOf,
383+
ctx.symbolProvider.toSymbol(targetShape.member),
384+
nullabilitySuffix(targetShape.isSparse),
385+
)
365386
withBlock("while (hasNextElement()) {", "}") {
366387
delegateListDeserialization(memberShape, targetShape, nestingLevel, mutableCollectionName)
367388
}
@@ -454,11 +475,16 @@ open class DeserializeStructGenerator(
454475
val elementName = nestingLevel.variableNameFor(NestedIdentifierType.ELEMENT)
455476
val nextNestingLevel = nestingLevel + 1
456477
val mapName = nextNestingLevel.variableNameFor(NestedIdentifierType.MAP)
457-
val mutableCollectionType = mapShape.mutableCollectionType()
458478
val collectionReturnExpression = collectionReturnExpression(rootMemberShape, mapName)
459479

460480
writer.withBlock("val $elementName = deserializer.#T($descriptorName) {", "}", RuntimeTypes.Serde.deserializeMap) {
461-
write("val $mapName = $mutableCollectionType()")
481+
write(
482+
"val #L = #T<String, #T#L>()",
483+
mapName,
484+
KotlinTypes.Collections.mutableMapOf,
485+
ctx.symbolProvider.toSymbol(mapShape.value),
486+
nullabilitySuffix(mapShape.isSparse),
487+
)
462488
withBlock("while (hasNextEntry()) {", "}") {
463489
delegateMapDeserialization(rootMemberShape, mapShape, nextNestingLevel, mapName)
464490
}
@@ -487,11 +513,16 @@ open class DeserializeStructGenerator(
487513
val elementName = nestingLevel.variableNameFor(NestedIdentifierType.ELEMENT)
488514
val nextNestingLevel = nestingLevel + 1
489515
val listName = nextNestingLevel.variableNameFor(NestedIdentifierType.COLLECTION)
490-
val mutableCollectionType = elementShape.mutableCollectionType()
491516
val collectionReturnExpression = collectionReturnExpression(rootMemberShape, listName)
492517

493518
writer.withBlock("val $elementName = deserializer.#T($descriptorName) {", "}", RuntimeTypes.Serde.deserializeList) {
494-
write("val $listName = $mutableCollectionType()")
519+
write(
520+
"val #L = #T<#T#L>()",
521+
listName,
522+
KotlinTypes.Collections.mutableListOf,
523+
ctx.symbolProvider.toSymbol(elementShape.member),
524+
nullabilitySuffix(elementShape.isSparse),
525+
)
495526
withBlock("while (hasNextElement()) {", "}") {
496527
delegateListDeserialization(rootMemberShape, elementShape, nextNestingLevel, listName)
497528
}
@@ -569,12 +600,6 @@ open class DeserializeStructGenerator(
569600
else -> throw CodegenException("unknown deserializer for member: $shape; target: $target")
570601
}
571602
}
572-
573-
// Return the function to generate a mutable instance of collection type of input shape.
574-
private fun MapShape.mutableCollectionType(): String =
575-
ctx.symbolProvider.toSymbol(this).getProperty(SymbolProperty.MUTABLE_COLLECTION_FUNCTION).get() as String
576-
577-
// Return the function to generate a mutable instance of collection type of input shape.
578-
private fun CollectionShape.mutableCollectionType(): String =
579-
ctx.symbolProvider.toSymbol(this).getProperty(SymbolProperty.MUTABLE_COLLECTION_FUNCTION).get() as String
580603
}
604+
605+
private fun nullabilitySuffix(isSparse: Boolean): String = if (isSparse) "?" else ""

smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/DeserializeStructGeneratorTest.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,46 @@ class DeserializeStructGeneratorTest {
118118
actual.shouldContainOnlyOnceWithDiff(expected)
119119
}
120120

121+
@Test
122+
fun `it deserializes a structure with a list of document values`() {
123+
val model = (
124+
modelPrefix + """
125+
structure FooResponse {
126+
payload: DocumentList
127+
}
128+
129+
list DocumentList {
130+
member: Document
131+
}
132+
"""
133+
).toSmithyModel()
134+
135+
val expected = """
136+
deserializer.deserializeStruct(OBJ_DESCRIPTOR) {
137+
loop@while (true) {
138+
when (findNextFieldIndex()) {
139+
PAYLOAD_DESCRIPTOR.index -> builder.payload =
140+
deserializer.deserializeList(PAYLOAD_DESCRIPTOR) {
141+
val col0 = mutableListOf<Document>()
142+
while (hasNextElement()) {
143+
val el0 = if (nextHasValue()) { deserializeDocument() } else { deserializeNull(); continue }
144+
col0.add(el0)
145+
}
146+
col0
147+
}
148+
null -> break@loop
149+
else -> skipValue()
150+
}
151+
}
152+
}
153+
""".trimIndent()
154+
155+
val actual = codegenDeserializerForShape(model, "com.test#Foo")
156+
157+
actual.shouldContainOnlyOnceWithDiff(expected)
158+
actual.shouldContainOnlyOnceWithDiff("import aws.smithy.kotlin.runtime.smithy.Document")
159+
}
160+
121161
@Test
122162
fun `it deserializes a structure with a nested structure`() {
123163
val model = (

0 commit comments

Comments
 (0)