Skip to content

Commit 23cdff1

Browse files
authored
Allow 'null' variants in unions (#3481)
## Motivation and Context - awslabs/aws-sdk-rust#1095 ## Description Update the JSON parser generator to allow for `null` to be set in unions. Servers can send unions like this: ```json { "AmazonElasticsearchParameters": null, "AmazonOpenSearchParameters": null, "AppFlowParameters": null, "AthenaParameters": null, "AuroraParameters": null, "AuroraPostgreSqlParameters": null, "AwsIotAnalyticsParameters": null, "BigQueryParameters": null, "DatabricksParameters": null, "Db2Parameters": null, "DenodoParameters": null, "DocumentDBParameters": null, "DremioParameters": null, "ExasolParameters": null, "GoogleAnalyticsParameters": null, "JiraParameters": null, "MariaDbParameters": null, "MongoAtlasParameters": null, "MongoDBParameters": null, "MySqlParameters": null, "OracleParameters": null, "PostgreSqlParameters": null, "PrestoParameters": null, "RdsParameters": null, "RedshiftParameters": null, "S3Parameters": { "IsUploaded": false, "ManifestFileLocation": { "Bucket": "deided-bucket.prod.us-east-1", "Key": "sales/manifest.json" }, "RoleArn": null }, "SalesforceParameters": null, "SapHanaParameters": null, "ServiceNowParameters": null, "SnowflakeParameters": null, "SparkParameters": null, "SqlServerParameters": null, "StarburstParameters": null, "TeradataParameters": null, "TrinoParameters": null, "TwitterParameters": null } ``` This caused our parser to fail. ## Testing <!--- Please describe in detail how you tested your changes --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> - [x] New unit test - [x] Dry run against new [smithy protocol test](smithy-lang/smithy#2180) ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent 0b35a09 commit 23cdff1

File tree

6 files changed

+46
-4
lines changed

6 files changed

+46
-4
lines changed

build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ buildscript {
1515
}
1616

1717
allprojects {
18+
val allowLocalDeps: String by project
1819
repositories {
19-
// mavenLocal()
20+
if (allowLocalDeps.toBoolean()) {
21+
mavenLocal()
22+
}
2023
mavenCentral()
2124
google()
2225
}

buildSrc/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ plugins {
1212
repositories {
1313
mavenCentral()
1414
google()
15-
/* mavenLocal() */
1615
}
1716

1817
// Load properties manually to avoid hard coding smithy version

codegen-client-test/build.gradle.kts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,6 @@ val allCodegenTests = listOf(
114114
),
115115
ClientTest("aws.protocoltests.misc#QueryCompatService", "query-compat-test", dependsOn = listOf("aws-json-query-compat.smithy")),
116116
).map(ClientTest::toCodegenTest)
117-
// use this line to run just one test
118-
// .filter { it.module == "query-compat-test" }
119117

120118
project.registerGenerateSmithyBuildTask(rootProject, pluginName, allCodegenTests)
121119
project.registerGenerateCargoWorkspaceTask(rootProject, pluginName, allCodegenTests, workingDirUnderBuildDir)

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.withBlockTemplate
3636
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
3737
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
3838
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
39+
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
3940
import software.amazon.smithy.rust.codegen.core.smithy.canUseDefault
4041
import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization
4142
import software.amazon.smithy.rust.codegen.core.smithy.customize.Section
@@ -121,6 +122,7 @@ class JsonParserGenerator(
121122
"skip_to_end" to smithyJson.resolve("deserialize::token::skip_to_end"),
122123
"Token" to smithyJson.resolve("deserialize::Token"),
123124
"or_empty" to orEmptyJson(),
125+
*preludeScope,
124126
)
125127

126128
/**
@@ -560,6 +562,15 @@ class JsonParserGenerator(
560562
*codegenScope,
561563
) {
562564
objectKeyLoop(hasMembers = shape.members().isNotEmpty()) {
565+
rustTemplate(
566+
"""
567+
if let #{Some}(#{Ok}(#{Token}::ValueNull { .. })) = tokens.peek() {
568+
let _ = tokens.next().expect("peek returned a token")?;
569+
continue;
570+
}
571+
""",
572+
*codegenScope,
573+
)
563574
rustTemplate(
564575
"""
565576
let key = key.to_unescaped()?;
@@ -622,6 +633,16 @@ class JsonParserGenerator(
622633
*codegenScope,
623634
)
624635
}
636+
// If we've gotten to the point where the union had a `{ ... }` section, we can't return None
637+
// anymore. If we didn't parse a union at this point, this is an error.
638+
rustTemplate(
639+
"""
640+
if variant.is_none() {
641+
return Err(#{Error}::custom("Union did not contain a valid variant."))
642+
}
643+
""",
644+
*codegenScope,
645+
)
625646
rust("Ok(variant)")
626647
}
627648
}

codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGeneratorTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,26 @@ class JsonParserGeneratorTest {
183183
""",
184184
)
185185

186+
unitTest(
187+
"allow_null_for_variants",
188+
"""
189+
// __type field should be ignored during deserialization
190+
let input = br#"{ "top": { "choice": { "blob": null, "boolean": null, "int": 5, "long": null, "__type": "value-should-be-ignored-anyway" } } }"#;
191+
let output = ${format(operationGenerator)}(input, test_output::OpOutput::builder()).unwrap().build();
192+
use test_model::Choice;
193+
assert_eq!(Choice::Int(5), output.top.unwrap().choice);
194+
""",
195+
)
196+
197+
unitTest(
198+
"all_variants_null",
199+
"""
200+
// __type field should be ignored during deserialization
201+
let input = br#"{ "top": { "choice": { "blob": null, "boolean": null, "int": null, "long": null, "__type": "value-should-be-ignored-anyway" } } }"#;
202+
let _err = ${format(operationGenerator)}(input, test_output::OpOutput::builder()).expect_err("invalid union");
203+
""",
204+
)
205+
186206
unitTest(
187207
"empty_error",
188208
"""

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ kotlin.code.style=official
2222
# codegen
2323
smithyGradlePluginVersion=0.9.0
2424
smithyVersion=1.45.0
25+
allowLocalDeps=false
2526

2627
# kotlin
2728
kotlinVersion=1.9.20

0 commit comments

Comments
 (0)