Unverified Commit 23cdff1d authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Allow 'null' variants in unions (#3481)

## Motivation and Context
- https://github.com/awslabs/aws-sdk-rust/issues/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](https://github.com/smithy-lang/smithy/pull/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._
parent 0b35a092
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -15,8 +15,11 @@ buildscript {
}

allprojects {
    val allowLocalDeps: String by project
    repositories {
        // mavenLocal()
        if (allowLocalDeps.toBoolean()) {
         mavenLocal()
        }
        mavenCentral()
        google()
    }
+0 −1
Original line number Diff line number Diff line
@@ -12,7 +12,6 @@ plugins {
repositories {
    mavenCentral()
    google()
    /* mavenLocal() */
}

// Load properties manually to avoid hard coding smithy version
+0 −2
Original line number Diff line number Diff line
@@ -114,8 +114,6 @@ val allCodegenTests = listOf(
    ),
    ClientTest("aws.protocoltests.misc#QueryCompatService", "query-compat-test", dependsOn = listOf("aws-json-query-compat.smithy")),
).map(ClientTest::toCodegenTest)
// use this line to run just one test
// .filter { it.module == "query-compat-test" }

project.registerGenerateSmithyBuildTask(rootProject, pluginName, allCodegenTests)
project.registerGenerateCargoWorkspaceTask(rootProject, pluginName, allCodegenTests, workingDirUnderBuildDir)
+21 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.withBlockTemplate
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
import software.amazon.smithy.rust.codegen.core.smithy.canUseDefault
import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.Section
@@ -121,6 +122,7 @@ class JsonParserGenerator(
            "skip_to_end" to smithyJson.resolve("deserialize::token::skip_to_end"),
            "Token" to smithyJson.resolve("deserialize::Token"),
            "or_empty" to orEmptyJson(),
            *preludeScope,
        )

    /**
@@ -560,6 +562,15 @@ class JsonParserGenerator(
                            *codegenScope,
                        ) {
                            objectKeyLoop(hasMembers = shape.members().isNotEmpty()) {
                                rustTemplate(
                                    """
                                    if let #{Some}(#{Ok}(#{Token}::ValueNull { .. })) = tokens.peek() {
                                        let _ = tokens.next().expect("peek returned a token")?;
                                        continue;
                                    }
                                    """,
                                    *codegenScope,
                                )
                                rustTemplate(
                                    """
                                    let key = key.to_unescaped()?;
@@ -622,6 +633,16 @@ class JsonParserGenerator(
                            *codegenScope,
                        )
                    }
                    // If we've gotten to the point where the union had a `{ ... }` section, we can't return None
                    // anymore. If we didn't parse a union at this point, this is an error.
                    rustTemplate(
                        """
                        if variant.is_none() {
                            return Err(#{Error}::custom("Union did not contain a valid variant."))
                        }
                        """,
                        *codegenScope,
                    )
                    rust("Ok(variant)")
                }
            }
+20 −0
Original line number Diff line number Diff line
@@ -183,6 +183,26 @@ class JsonParserGeneratorTest {
                """,
            )

            unitTest(
                "allow_null_for_variants",
                """
                // __type field should be ignored during deserialization
                let input = br#"{ "top": { "choice": { "blob": null, "boolean": null, "int": 5, "long": null, "__type": "value-should-be-ignored-anyway" } } }"#;
                let output = ${format(operationGenerator)}(input, test_output::OpOutput::builder()).unwrap().build();
                use test_model::Choice;
                assert_eq!(Choice::Int(5), output.top.unwrap().choice);
                """,
            )

            unitTest(
                "all_variants_null",
                """
                // __type field should be ignored during deserialization
                let input = br#"{ "top": { "choice": { "blob": null, "boolean": null, "int": null, "long": null, "__type": "value-should-be-ignored-anyway" } } }"#;
                let _err = ${format(operationGenerator)}(input, test_output::OpOutput::builder()).expect_err("invalid union");
                """,
            )

            unitTest(
                "empty_error",
                """
Loading