From 7d39bf6b63ad3eba2074b935d7918a1e12479182 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Tue, 9 Mar 2021 09:19:19 -0500 Subject: [PATCH] Add DynamoDB pseudo integration test (#240) --- aws/rust-runtime/aws-hyper/Cargo.toml | 8 +- aws/rust-runtime/aws-hyper/src/lib.rs | 1 + .../aws-hyper/src/test_connection.rs | 21 +- .../amazon/smithy/rustsdk/AwsHyperDevDep.kt | 6 +- aws/sdk/build.gradle.kts | 2 +- .../dynamodb/tests/data.json | 53 +++ .../dynamodb/tests/movies.rs | 422 ++++++++++++++++++ .../smithy/generators/UnionGenerator.kt | 2 + 8 files changed, 503 insertions(+), 12 deletions(-) create mode 100644 aws/sdk/integration-tests/dynamodb/tests/data.json create mode 100644 aws/sdk/integration-tests/dynamodb/tests/movies.rs diff --git a/aws/rust-runtime/aws-hyper/Cargo.toml b/aws/rust-runtime/aws-hyper/Cargo.toml index 883c85d050..3e7ce9b87c 100644 --- a/aws/rust-runtime/aws-hyper/Cargo.toml +++ b/aws/rust-runtime/aws-hyper/Cargo.toml @@ -6,6 +6,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +test-util = ["protocol-test-helpers"] + [dependencies] hyper = { version = "0.14.2", features = ["client", "http1", "http2", "tcp", "runtime"] } tower = { version = "0.4.6", features = ["util", "retry"] } @@ -21,10 +24,13 @@ smithy-http = { path = "../../../rust-runtime/smithy-http" } smithy-types = { path = "../../../rust-runtime/smithy-types" } smithy-http-tower = { path = "../../../rust-runtime/smithy-http-tower" } fastrand = "1.4.0" -tokio = { version = "1", features = ["time"]} +tokio = { version = "1", features = ["time"] } + pin-project = "1" tracing = "0.1.25" +protocol-test-helpers = { path = "../../../rust-runtime/protocol-test-helpers", optional = true } + [dev-dependencies] tokio = { version = "1", features = ["full", "test-util"] } tower-test = "0.4.0" diff --git a/aws/rust-runtime/aws-hyper/src/lib.rs b/aws/rust-runtime/aws-hyper/src/lib.rs index 9db6340c2f..29292d9189 100644 --- a/aws/rust-runtime/aws-hyper/src/lib.rs +++ b/aws/rust-runtime/aws-hyper/src/lib.rs @@ -1,5 +1,6 @@ pub mod conn; mod retry; +#[cfg(feature = "test-util")] pub mod test_connection; pub use retry::RetryConfig; diff --git a/aws/rust-runtime/aws-hyper/src/test_connection.rs b/aws/rust-runtime/aws-hyper/src/test_connection.rs index 3b78129178..8dedfd61c5 100644 --- a/aws/rust-runtime/aws-hyper/src/test_connection.rs +++ b/aws/rust-runtime/aws-hyper/src/test_connection.rs @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ -use http::header::HeaderName; +use http::header::{HeaderName, CONTENT_TYPE}; use http::Request; use smithy_http::body::SdkBody; use std::future::Ready; @@ -11,6 +11,7 @@ use std::ops::Deref; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; use tower::BoxError; +use protocol_test_helpers::{validate_body, MediaType, assert_ok}; type ConnectVec = Vec<(http::Request, http::Response)>; @@ -27,14 +28,20 @@ impl ValidateRequest { let actual_header = actual .headers() .get(name) - .unwrap_or_else(||panic!("Header {:?} missing", name)); + .unwrap_or_else(|| panic!("Header {:?} missing", name)); assert_eq!(actual_header, value, "Header mismatch for {:?}", name); } } let actual_str = std::str::from_utf8(actual.body().bytes().unwrap_or(&[])); let expected_str = std::str::from_utf8(expected.body().bytes().unwrap_or(&[])); + let media_type = if actual.headers().get(CONTENT_TYPE).map(|v| v.to_str().unwrap().contains("json")).unwrap_or(false) { + MediaType::Json + } else { + MediaType::Other("unknown".to_string()) + }; match (actual_str, expected_str) { - (Ok(actual), Ok(expected)) => assert_eq!(actual, expected), + (Ok(actual), Ok(expected)) => + assert_ok(validate_body(actual, expected, media_type)), _ => assert_eq!(actual.body().bytes(), expected.body().bytes()), }; assert_eq!(actual.uri(), expected.uri()); @@ -87,7 +94,7 @@ impl TestConnection { } } - pub fn requests(&self) -> impl Deref> + '_ { + pub fn requests(&self) -> impl Deref> + '_ { self.requests.lock().unwrap() } } @@ -126,9 +133,9 @@ mod tests { fn meets_trait_bounds() { fn check() -> impl tower::Service< http::Request, - Response = http::Response, - Error = BoxError, - Future = impl Send, + Response=http::Response, + Error=BoxError, + Future=impl Send, > + Clone { TestConnection::::new(vec![]) } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsHyperDevDep.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsHyperDevDep.kt index c2e3ea26ed..14b701445a 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsHyperDevDep.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsHyperDevDep.kt @@ -17,7 +17,7 @@ import software.amazon.smithy.rust.codegen.smithy.generators.LibRsSection import software.amazon.smithy.rust.codegen.smithy.generators.ProtocolConfig import software.amazon.smithy.rust.codegen.smithy.letIf -val TestedServices = setOf("kms") +val TestedServices = setOf("kms", "dynamodb") class IntegrationTestDecorator : RustCodegenDecorator { override val name: String = "IntegrationTest" @@ -41,5 +41,5 @@ class AwsHyperDevDep(private val runtimeConfig: RuntimeConfig) : LibRsCustomizat } } -val Tokio = CargoDependency("tokio", CratesIo("1"), features = listOf("macros"), scope = DependencyScope.Dev) -fun RuntimeConfig.awsHyper() = CargoDependency("aws-hyper", Local(relativePath)) +val Tokio = CargoDependency("tokio", CratesIo("1"), features = listOf("macros", "test-util"), scope = DependencyScope.Dev) +fun RuntimeConfig.awsHyper() = CargoDependency("aws-hyper", Local(relativePath), features = listOf("test-util")) diff --git a/aws/sdk/build.gradle.kts b/aws/sdk/build.gradle.kts index 57705f3675..222be63547 100644 --- a/aws/sdk/build.gradle.kts +++ b/aws/sdk/build.gradle.kts @@ -19,7 +19,7 @@ plugins { val smithyVersion: String by project val sdkOutputDir = buildDir.resolve("aws-sdk") -val runtimeModules = listOf("smithy-types", "smithy-http", "smithy-http-tower") +val runtimeModules = listOf("smithy-types", "smithy-http", "smithy-http-tower", "protocol-test-helpers") val awsModules = listOf("aws-auth", "aws-endpoint", "aws-types", "aws-hyper", "aws-sig-auth", "aws-http") buildscript { diff --git a/aws/sdk/integration-tests/dynamodb/tests/data.json b/aws/sdk/integration-tests/dynamodb/tests/data.json new file mode 100644 index 0000000000..315aa202aa --- /dev/null +++ b/aws/sdk/integration-tests/dynamodb/tests/data.json @@ -0,0 +1,53 @@ +[ + { + "year": 2013, + "title": "Turn It Down, Or Else!", + "info": { + "directors": [ + "Alice Smith", + "Bob Jones" + ], + "release_date": "2013-01-18T00:00:00Z", + "rating": 6.2, + "genres": [ + "Comedy", + "Drama" + ], + "image_url": "http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg", + "plot": "A rock band plays their music at high volumes, annoying the neighbors.", + "rank": 11, + "running_time_secs": 5215, + "actors": [ + "David Matthewman", + "Ann Thomas", + "Jonathan G. Neff" + ] + } + }, + { + "year": 2013, + "title": "Rush", + "info": { + "directors": [ + "Ron Howard" + ], + "release_date": "2013-09-02T00:00:00Z", + "rating": 8.3, + "genres": [ + "Action", + "Biography", + "Drama", + "Sport" + ], + "image_url": "http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg", + "plot": "A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda.", + "rank": 2, + "running_time_secs": 7380, + "actors": [ + "Daniel Bruhl", + "Chris Hemsworth", + "Olivia Wilde" + ] + } + } +] diff --git a/aws/sdk/integration-tests/dynamodb/tests/movies.rs b/aws/sdk/integration-tests/dynamodb/tests/movies.rs new file mode 100644 index 0000000000..92cbcfed08 --- /dev/null +++ b/aws/sdk/integration-tests/dynamodb/tests/movies.rs @@ -0,0 +1,422 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +use aws_http::AwsErrorRetryPolicy; +use aws_hyper::test_connection::TestConnection; +use aws_hyper::{SdkError, SdkSuccess}; +use dynamodb::error::DescribeTableError; +use dynamodb::input::{ + create_table_input, put_item_input, query_input, DescribeTableInput, PutItemInput, QueryInput, +}; +use dynamodb::model::{ + AttributeDefinition, AttributeValue, KeySchemaElement, KeyType, ProvisionedThroughput, + ScalarAttributeType, TableStatus, +}; +use dynamodb::operation::{CreateTable, DescribeTable}; +use dynamodb::output::DescribeTableOutput; +use dynamodb::{Config, Region, Credentials}; +use http::Uri; +use serde_json::Value; +use smithy_http::operation::Operation; +use smithy_http::body::SdkBody; +use smithy_http::retry::ClassifyResponse; +use smithy_types::retry::RetryKind; +use std::collections::HashMap; +use std::time::Duration; +use tokio::time::Instant; +use http::header::{AUTHORIZATION, HeaderName}; + +fn create_table(table_name: &str) -> create_table_input::Builder { + CreateTable::builder() + .table_name(table_name) + .key_schema(vec![ + KeySchemaElement::builder() + .attribute_name("year") + .key_type(KeyType::Hash) + .build(), + KeySchemaElement::builder() + .attribute_name("title") + .key_type(KeyType::Range) + .build(), + ]) + .attribute_definitions(vec![ + AttributeDefinition::builder() + .attribute_name("year") + .attribute_type(ScalarAttributeType::N) + .build(), + AttributeDefinition::builder() + .attribute_name("title") + .attribute_type(ScalarAttributeType::S) + .build(), + ]) + .provisioned_throughput( + ProvisionedThroughput::builder() + .read_capacity_units(10) + .write_capacity_units(10) + .build(), + ) +} + +fn value_to_item(value: Value) -> AttributeValue { + match value { + Value::Null => AttributeValue::Null(true), + Value::Bool(b) => AttributeValue::Bool(b), + Value::Number(n) => AttributeValue::N(n.to_string()), + Value::String(s) => AttributeValue::S(s), + Value::Array(a) => AttributeValue::L(a.into_iter().map(value_to_item).collect()), + Value::Object(o) => { + AttributeValue::M(o.into_iter().map(|(k, v)| (k, value_to_item(v))).collect()) + } + } +} + +fn add_item(table_name: impl Into, item: Value) -> put_item_input::Builder { + let attribute_value = match value_to_item(item) { + AttributeValue::M(map) => map, + other => panic!("can only insert top level values, got {:?}", other), + }; + + PutItemInput::builder() + .table_name(table_name) + .item(attribute_value) +} + +fn movies_in_year(table_name: &str, year: u16) -> query_input::Builder { + let mut expr_attrib_names = HashMap::new(); + expr_attrib_names.insert("#yr".to_string(), "year".to_string()); + let mut expr_attrib_values = HashMap::new(); + expr_attrib_values.insert(":yyyy".to_string(), AttributeValue::N(year.to_string())); + QueryInput::builder() + .table_name(table_name) + .key_condition_expression("#yr = :yyyy") + .expression_attribute_names(expr_attrib_names) + .expression_attribute_values(expr_attrib_values) +} + +/// Hand-written waiter to retry every second until the table is out of `Creating` state +#[derive(Clone)] +struct WaitForReadyTable { + inner: R, +} + +impl ClassifyResponse, SdkError> + for WaitForReadyTable +where + R: ClassifyResponse, SdkError>, +{ + fn classify( + &self, + response: Result<&SdkSuccess, &SdkError>, + ) -> RetryKind { + match self.inner.classify(response.clone()) { + RetryKind::NotRetryable => (), + other => return other, + }; + match response { + Ok(SdkSuccess { parsed, .. }) => { + if parsed + .table + .as_ref() + .unwrap() + .table_status + .as_ref() + .unwrap() + == &TableStatus::Creating + { + RetryKind::Explicit(Duration::from_secs(1)) + } else { + RetryKind::NotRetryable + } + } + _ => RetryKind::NotRetryable, + } + } +} + +/// Construct a `DescribeTable` request with a policy to retry every second until the table +/// is ready +fn wait_for_ready_table( + table_name: &str, + conf: &Config, +) -> Operation> { + let operation = DescribeTableInput::builder() + .table_name(table_name) + .build(&conf); + let waiting_policy = WaitForReadyTable { + inner: operation.retry_policy().clone(), + }; + operation.with_retry_policy(waiting_policy) +} + + +/// Validate that time has passed with a 5ms tolerance +/// +/// This is to account for some non-determinism in the Tokio timer +fn assert_time_passed(initial: Instant, passed: Duration) { + let now = tokio::time::Instant::now(); + let delta = now - initial; + if (delta.as_millis() as i128 - passed.as_millis() as i128).abs() > 5 { + assert_eq!(delta, passed) + } +} + +/// A partial reimplementation of https://docs.amazonaws.cn/en_us/amazondynamodb/latest/developerguide/GettingStarted.Ruby.html +/// in Rust +/// +/// - Create table +/// - Wait for table to be ready +/// - Add a couple of rows +/// - Query for those rows +#[tokio::test] +async fn movies_it() { + let table_name = "Movies-5"; + // The waiter will retry 5 times + tokio::time::pause(); + let conn = movies_it_test_connection(); // RecordingConnection::https(); + let client = aws_hyper::Client::new(conn.clone()); + let conf = dynamodb::Config::builder() + .region(Region::new("us-east-1")) + .credentials_provider(Credentials::from_keys("AKNOTREAL", "NOT_A_SECRET", None)) + .build(); + client + .call(create_table(table_name).build(&conf)) + .await + .expect("failed to create table"); + + let waiter_start = tokio::time::Instant::now(); + client + .call(wait_for_ready_table(table_name, &conf)) + .await + .expect("table should become ready"); + + assert_time_passed(waiter_start, Duration::from_secs(4)); + // data.json contains 2 movies from 2013 + let data = match serde_json::from_str(include_str!("data.json")).expect("should be valid JSON") + { + Value::Array(inner) => inner, + data => panic!("data must be an array, got: {:?}", data), + }; + for item in data { + client + .call(add_item(table_name, item.clone()).build(&conf)) + .await + .expect("failed to insert item"); + } + let films_2222 = client + .call(movies_in_year(table_name, 2222).build(&conf)) + .await + .expect("query should succeed"); + // this isn't back to the future, there are no movies from 2022 + assert_eq!(films_2222.count, 0); + + let films_2013 = client + .call(movies_in_year(table_name, 2013).build(&conf)) + .await + .expect("query should succeed"); + assert_eq!(films_2013.count, 2); + let titles: Vec = films_2013.items.unwrap().into_iter().map(|mut row|row.remove("title").expect("row should have title")).collect(); + assert_eq!(titles, vec![AttributeValue::S("Rush".to_string()), AttributeValue::S("Turn It Down, Or Else!".to_string())]); + + for req in conn.requests().iter() { + req.assert_matches(vec![AUTHORIZATION, HeaderName::from_static("x-amz-date")]); + } +} + +/// Test connection for the movies IT +/// headers are signed with actual creds, at some point we could replace them with verifiable test +/// credentials, but there are plenty of other tests that target signing +fn movies_it_test_connection() -> TestConnection<&'static str> { + TestConnection::new(vec![( + http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.CreateTable") + .header("content-length", "313") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=4a832eba37651836b524b587986be607607b077ad133c57b4bf7300d2e02f476") + .header("x-amz-date", "20210308T155118Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"AttributeDefinitions":[{"AttributeName":"year","AttributeType":"N"},{"AttributeName":"title","AttributeType":"S"}],"TableName":"Movies-5","KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"ReadCapacityUnits":10,"WriteCapacityUnits":10}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "572") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "RCII0AALE00UALC7LJ9AD600B7VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "3715137447") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=01b0129a2a4fb3af14559fde8163d59de9c43907152a12479002b3a7c75fa0df") + .header("x-amz-date", "20210308T155119Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "O1C6QKCG8GT7D2K922T4QRL9N3VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=7f3a743bb460f26296640ae775d282f0153eda750855ec00ace1815becfd2de5") + .header("x-amz-date", "20210308T155120Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")).body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:20 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "EN5N26BO1FAOEMUUSD7B7SUPPVVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=46a148c560139bc0da171bd915ea8c0b96a7012629f5db7b6bf70fcd1a66fd24") + .header("x-amz-date", "20210308T155121Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:21 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "PHCMGEVI6JLN9JNMKSSA3M76H3VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=15bb7c9b2350747d62349091b3ea59d9e1800d1dca04029943329259bba85cb4") + .header("x-amz-date", "20210308T155122Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:22 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "1Q22O983HD3511TN6Q5RRTP0MFVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=6d0a78087bc112c68a91b4b2d457efd8c09149b85b8f998f8c4b3f9916c8a743") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "559") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "ONJBNV2A9GBNUT34KH73JLL23BVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "24113616") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"ACTIVE"}}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.PutItem") + .header("content-length", "619") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=85fc7d2064a0e6d9c38d64751d39d311ad415ae4079ef21ef254b23ecf093519") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"rating":{"N":"6.2"},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"release_date":{"S":"2013-01-18T00:00:00Z"},"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"running_time_secs":{"N":"5215"},"rank":{"N":"11"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]}}},"title":{"S":"Turn It Down, Or Else!"},"year":{"N":"2013"}}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "2") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "E6TGS5HKHHV08HSQA31IO1IDMFVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "2745614147") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.PutItem") + .header("content-length", "636") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=e4b1658c9f5129b3656381f6592a30e0061b1566263fbf27d982817ea79483f6") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"rating":{"N":"8.3"},"rank":{"N":"2"},"release_date":{"S":"2013-09-02T00:00:00Z"},"directors":{"L":[{"S":"Ron Howard"}]},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"running_time_secs":{"N":"7380"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]}}},"title":{"S":"Rush"},"year":{"N":"2013"}}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "2") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "B63D54LP2FOGQK9JE5KLJT49HJVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "2745614147") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.Query") + .header("content-length", "156") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=c9a0fdd0c7c3a792faddabca1fc154c8fbb54ddee7b06a8082e1c587615198b5") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2222"}}}"##)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "39") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "AUAS9KJ0TK9BSR986TRPC2RGTRVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "3413411624") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{"Count":0,"Items":[],"ScannedCount":0}"#).unwrap()), + (http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.Query") + .header("content-length", "156") + .header("host", "dynamodb.us-east-1.amazonaws.com") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=504d6b4de7093b20255b55057085937ec515f62f3c61da68c03bff3f0ce8a160") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2013"}}}"##)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "1231") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "A5FGSJ9ET4OKB8183S9M47RQQBVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "624725176") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(r#"{"Count":2,"Items":[{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"release_date":{"S":"2013-09-02T00:00:00Z"},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]},"directors":{"L":[{"S":"Ron Howard"}]},"rating":{"N":"8.3"},"rank":{"N":"2"},"running_time_secs":{"N":"7380"}}},"title":{"S":"Rush"}},{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"release_date":{"S":"2013-01-18T00:00:00Z"},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]},"rating":{"N":"6.2"},"rank":{"N":"11"},"running_time_secs":{"N":"5215"}}},"title":{"S":"Turn It Down, Or Else!"}}],"ScannedCount":2}"#).unwrap()) + ]) +} diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/UnionGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/UnionGenerator.kt index 7112e365b5..21d7c38b7d 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/UnionGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/UnionGenerator.kt @@ -10,6 +10,7 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.rust.codegen.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.rustlang.documentShape import software.amazon.smithy.rust.codegen.rustlang.rustBlock import software.amazon.smithy.rust.codegen.smithy.expectRustMetadata import software.amazon.smithy.rust.codegen.util.toPascalCase @@ -33,6 +34,7 @@ class UnionGenerator( writer.rustBlock("enum ${symbol.name}") { sortedMembers.forEach { member -> val memberSymbol = symbolProvider.toSymbol(member) + documentShape(member, model) memberSymbol.expectRustMetadata().renderAttributes(this) write("${member.memberName.toPascalCase()}(#T),", symbolProvider.toSymbol(member)) }