-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: ignore __type when deserializing union for AWS Json protocols #964
Changes from 1 commit
3c7febc
febc8da
c2cfdbb
c207a32
5769b44
5258f30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,23 +6,6 @@ package aws.smithy.kotlin.runtime.serde | |
|
||
import aws.smithy.kotlin.runtime.InternalApi | ||
|
||
/** | ||
* This tag interface provides a mechanism to attach type-specific metadata to any field. | ||
* See [aws.smithy.kotlin.runtime.serde.xml.XmlList] for an example implementation. | ||
* | ||
* For example, to specify that a list should be serialized in XML such that values are wrapped | ||
* in a tag called "boo", pass an instance of XmlList to the FieldDescriptor of `XmlList(elementName="boo")`. | ||
*/ | ||
@InternalApi | ||
public interface FieldTrait | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are these moved? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the file was named |
||
|
||
/** | ||
* Denotes that a Map or List may contain null values | ||
* Details at https://awslabs.github.io/smithy/1.0/spec/core/type-refinement-traits.html#sparse-trait | ||
*/ | ||
@InternalApi | ||
public object SparseValues : FieldTrait | ||
|
||
/** | ||
* A protocol-agnostic type description of a field. | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package aws.smithy.kotlin.runtime.serde | ||
|
||
import aws.smithy.kotlin.runtime.InternalApi | ||
|
||
/** | ||
* This tag interface provides a mechanism to attach type-specific metadata to any field. | ||
* See [aws.smithy.kotlin.runtime.serde.xml.XmlList] for an example implementation. | ||
* | ||
* For example, to specify that a list should be serialized in XML such that values are wrapped | ||
* in a tag called "boo", pass an instance of XmlList to the FieldDescriptor of `XmlList(elementName="boo")`. | ||
*/ | ||
@InternalApi | ||
public interface FieldTrait | ||
|
||
/** | ||
* Indicates to deserializers to ignore field/key | ||
* | ||
* @param key The key to ignore in the payload | ||
* @param regardlessOfInModel If true will ignore key even though the model indicates key should be there | ||
* | ||
*/ | ||
@InternalApi | ||
public data class IgnoreKey(public val key: String, public val regardlessOfInModel: Boolean = true) : FieldTrait | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: Why do we need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking of a case where we have a union with a member called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't put it in a JSON specific file because I think this might be useful in the future for other deserializers like the XML one. I don't know if we have a use case already but that was my reasoning behind it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think? Should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Shouldn't this just be a choice by codegen though? If we want it deserialized because it's in the model don't add the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
YAGNI probably. Also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense but the issue this is trying to solve: smithy-lang/smithy#1945 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't follow, if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess that logic could live in codegen, now it's in the deserializer |
||
|
||
/** | ||
* Denotes that a Map or List may contain null values | ||
* Details at https://awslabs.github.io/smithy/1.0/spec/core/type-refinement-traits.html#sparse-trait | ||
*/ | ||
@InternalApi | ||
public object SparseValues : FieldTrait |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package aws.smithy.kotlin.runtime.serde.json | ||
|
||
import aws.smithy.kotlin.runtime.serde.* | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
class JsonDeserializerIgnoresKeysTest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Every test has explicitly modeled field descriptors. You're missing coverage for unmodeled fields that we want to skip rather than enumerate as unknown. |
||
class IgnoresKeysTest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this double-nested class seems unnecessary, I think you can just declare these at the top-level without using a |
||
companion object { | ||
val X_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("x")) | ||
val Y_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("y")) | ||
val Z_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("z")) | ||
val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { | ||
trait(IgnoreKey("z")) // <---- | ||
field(X_DESCRIPTOR) | ||
field(Y_DESCRIPTOR) | ||
field(Z_DESCRIPTOR) | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun itIgnoresKeys() { | ||
val payload = """ | ||
{ | ||
"x": 1, | ||
"y": 2, | ||
"z": 3 | ||
} | ||
""".trimIndent().encodeToByteArray() | ||
|
||
val deserializer = JsonDeserializer(payload) | ||
var x: Int? = null | ||
var y: Int? = null | ||
var z: Int? = null | ||
deserializer.deserializeStruct(IgnoresKeysTest.OBJ_DESCRIPTOR) { | ||
loop@ while (true) { | ||
when (findNextFieldIndex()) { | ||
IgnoresKeysTest.X_DESCRIPTOR.index -> x = deserializeInt() | ||
IgnoresKeysTest.Y_DESCRIPTOR.index -> y = deserializeInt() | ||
IgnoresKeysTest.Z_DESCRIPTOR.index -> z = deserializeInt() | ||
null -> break@loop | ||
} | ||
} | ||
} | ||
|
||
assertEquals(1, x) | ||
assertEquals(2, y) | ||
assertEquals(null, z) | ||
} | ||
|
||
@Test | ||
fun itIgnoresKeysOutOfOrder() { | ||
val payload = """ | ||
{ | ||
"z": 3, | ||
"x": 1, | ||
"y": 2 | ||
} | ||
""".trimIndent().encodeToByteArray() | ||
|
||
val deserializer = JsonDeserializer(payload) | ||
var x: Int? = null | ||
var y: Int? = null | ||
var z: Int? = null | ||
deserializer.deserializeStruct(IgnoresKeysTest.OBJ_DESCRIPTOR) { | ||
loop@ while (true) { | ||
when (findNextFieldIndex()) { | ||
IgnoresKeysTest.X_DESCRIPTOR.index -> x = deserializeInt() | ||
IgnoresKeysTest.Y_DESCRIPTOR.index -> y = deserializeInt() | ||
IgnoresKeysTest.Z_DESCRIPTOR.index -> z = deserializeInt() | ||
null -> break@loop | ||
} | ||
} | ||
} | ||
|
||
assertEquals(1, x) | ||
assertEquals(2, y) | ||
assertEquals(null, z) | ||
} | ||
|
||
@Test | ||
fun itIgnoresKeysManyTimes() { | ||
val payload = """ | ||
{ | ||
"x": 1, | ||
"y": 2, | ||
"z": 3, | ||
"z": 3, | ||
"z": 3 | ||
} | ||
""".trimIndent().encodeToByteArray() | ||
|
||
val deserializer = JsonDeserializer(payload) | ||
var x: Int? = null | ||
var y: Int? = null | ||
var z: Int? = null | ||
deserializer.deserializeStruct(IgnoresKeysTest.OBJ_DESCRIPTOR) { | ||
loop@ while (true) { | ||
when (findNextFieldIndex()) { | ||
IgnoresKeysTest.X_DESCRIPTOR.index -> x = deserializeInt() | ||
IgnoresKeysTest.Y_DESCRIPTOR.index -> y = deserializeInt() | ||
IgnoresKeysTest.Z_DESCRIPTOR.index -> z = deserializeInt() | ||
null -> break@loop | ||
} | ||
} | ||
} | ||
|
||
assertEquals(1, x) | ||
assertEquals(2, y) | ||
assertEquals(null, z) | ||
} | ||
|
||
class IgnoresMultipleKeysTest { | ||
companion object { | ||
val W_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("w")) | ||
val X_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("x")) | ||
val Y_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("y")) | ||
val Z_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("z")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. duplicated, can re-use the others descriptors above |
||
val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { | ||
trait(IgnoreKey("w")) // <---- | ||
trait(IgnoreKey("z")) // <---- | ||
field(W_DESCRIPTOR) | ||
field(X_DESCRIPTOR) | ||
field(Y_DESCRIPTOR) | ||
field(Z_DESCRIPTOR) | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun itIgnoresMultipleKeys() { | ||
val payload = """ | ||
{ | ||
"w": 0, | ||
"x": 1, | ||
"y": 2, | ||
"z": 3 | ||
} | ||
""".trimIndent().encodeToByteArray() | ||
|
||
val deserializer = JsonDeserializer(payload) | ||
var w: Int? = null | ||
var x: Int? = null | ||
var y: Int? = null | ||
var z: Int? = null | ||
deserializer.deserializeStruct(IgnoresMultipleKeysTest.OBJ_DESCRIPTOR) { | ||
loop@ while (true) { | ||
when (findNextFieldIndex()) { | ||
IgnoresMultipleKeysTest.W_DESCRIPTOR.index -> w = deserializeInt() | ||
IgnoresMultipleKeysTest.X_DESCRIPTOR.index -> x = deserializeInt() | ||
IgnoresMultipleKeysTest.Y_DESCRIPTOR.index -> y = deserializeInt() | ||
IgnoresMultipleKeysTest.Z_DESCRIPTOR.index -> z = deserializeInt() | ||
null -> break@loop | ||
} | ||
} | ||
} | ||
|
||
assertEquals(null, w) | ||
assertEquals(1, x) | ||
assertEquals(2, y) | ||
assertEquals(null, z) | ||
} | ||
|
||
// Now testing `regardlessOfInModel = false` option | ||
class IgnoresKeysConsideringModelTest { | ||
companion object { | ||
val X_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("x")) | ||
val Y_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("y")) | ||
val Z_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("z")) | ||
val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { | ||
trait(IgnoreKey("x", false)) // <---- | ||
field(X_DESCRIPTOR) | ||
field(Y_DESCRIPTOR) | ||
field(Z_DESCRIPTOR) | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun itDoesNotIgnoreKeyBecauseItWasInTheModel() { | ||
val payload = """ | ||
{ | ||
"x": 0 | ||
} | ||
""".trimIndent().encodeToByteArray() | ||
|
||
val deserializer = JsonDeserializer(payload) | ||
var unionValue: Int? = null | ||
deserializer.deserializeStruct(IgnoresKeysConsideringModelTest.OBJ_DESCRIPTOR) { | ||
loop@ while (true) { | ||
when (findNextFieldIndex()) { | ||
IgnoresKeysConsideringModelTest.X_DESCRIPTOR.index -> unionValue = deserializeInt() | ||
IgnoresKeysConsideringModelTest.Y_DESCRIPTOR.index -> unionValue = deserializeInt() | ||
IgnoresKeysConsideringModelTest.Z_DESCRIPTOR.index -> unionValue = deserializeInt() | ||
null -> break@loop | ||
else -> unionValue = deserializeInt() | ||
} | ||
} | ||
} | ||
|
||
assertEquals(0, unionValue) | ||
} | ||
|
||
class IgnoresKeysBecauseNotInModelTest { | ||
companion object { | ||
val Y_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("y")) | ||
val Z_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Integer, JsonSerialName("z")) | ||
val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { | ||
trait(IgnoreKey("x", false)) // <---- | ||
field(Y_DESCRIPTOR) | ||
field(Z_DESCRIPTOR) | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun itIgnoresKeyBecauseWasNotInTheModel() { | ||
val payload = """ | ||
{ | ||
"x": 0 | ||
} | ||
""".trimIndent().encodeToByteArray() | ||
|
||
val deserializer = JsonDeserializer(payload) | ||
var unionValue: Int? = null | ||
deserializer.deserializeStruct(IgnoresKeysBecauseNotInModelTest.OBJ_DESCRIPTOR) { | ||
loop@ while (true) { | ||
when (findNextFieldIndex()) { | ||
IgnoresKeysBecauseNotInModelTest.Y_DESCRIPTOR.index -> unionValue = deserializeInt() | ||
IgnoresKeysBecauseNotInModelTest.Z_DESCRIPTOR.index -> unionValue = deserializeInt() | ||
null -> break@loop | ||
else -> unionValue = deserializeInt() | ||
} | ||
} | ||
} | ||
|
||
assertEquals(null, unionValue) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: Line length > 120 chars