diff --git a/CHANGELOG.md b/CHANGELOG.md index bea26febc06..be5e7de3a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## vNext (Month Day Year) + +**New This Week** +- :bug: Bugfix: Fix parsing bug where whitespace was stripped when parsing XML (#590) + ## v0.15 (June 29th 2021) This week, we've added EKS, ECR and Cloudwatch. The JSON deserialization implementation has been replaced, please be on the lookout for potential issues. diff --git a/aws/sdk/aws-models/s3-tests.smithy b/aws/sdk/aws-models/s3-tests.smithy index 69028b6b8ae..a9e0f701593 100644 --- a/aws/sdk/aws-models/s3-tests.smithy +++ b/aws/sdk/aws-models/s3-tests.smithy @@ -38,6 +38,69 @@ apply GetBucketLocation @httpResponseTests([ } ]) +apply ListObjects @httpResponseTests([ + { + id: "KeysWithWhitespace", + documentation: "This test validates that parsing respects whitespace", + code: 200, + bodyMediaType: "application/xml", + protocol: "aws.protocols#restXml", + body: """ + \n + + bucketname + + + 1000 + false + + + 2021-07-16T16:20:53.000Z + "etag123" + 0 + + owner + + STANDARD + + + a + 2021-07-16T16:02:10.000Z + "etag123" + 0 + + owner + + STANDARD + + + """, + params: { + MaxKeys: 1000, + IsTruncated: false, + Marker: "", + Name: "bucketname", + Prefix: "", + Contents: [{ + Key: " ", + LastModified: 1626452453, + ETag: "\"etag123\"", + Size: 0, + Owner: { ID: "owner" }, + StorageClass: "STANDARD" + }, { + Key: " a ", + LastModified: 1626451330, + ETag: "\"etag123\"", + Size: 0, + Owner: { ID: "owner" }, + StorageClass: "STANDARD" + }] + } + } +]) + apply PutBucketLifecycleConfiguration @httpRequestTests([ { id: "PutBucketLifecycleConfiguration", diff --git a/aws/sdk/build.gradle.kts b/aws/sdk/build.gradle.kts index af581452cba..aea1832ebfd 100644 --- a/aws/sdk/build.gradle.kts +++ b/aws/sdk/build.gradle.kts @@ -164,10 +164,10 @@ fun discoverServices(allServices: Boolean, generateOnly: Set): List - check(modules.contains(service)) { "Service $service was in list of tier 1 services but not generated!" } + check(modules.contains(service)) { "Service $service was in list of tier 1 services but not generated! ($generateOnly)" } } } return services diff --git a/aws/sdk/examples/s3/src/bin/list-objects.rs b/aws/sdk/examples/s3/src/bin/list-objects.rs index 3585b73e5b8..df099963a5b 100644 --- a/aws/sdk/examples/s3/src/bin/list-objects.rs +++ b/aws/sdk/examples/s3/src/bin/list-objects.rs @@ -7,11 +7,10 @@ use std::process; use s3::{Client, Config, Region}; -use aws_types::region::ProvideRegion; +use aws_types::region; +use aws_types::region::ProvideRegion; use structopt::StructOpt; -use tracing_subscriber::fmt::format::FmtSpan; -use tracing_subscriber::fmt::SubscriberBuilder; #[derive(Debug, StructOpt)] struct Opt { @@ -44,23 +43,21 @@ async fn main() { verbose, } = Opt::from_args(); - let region = default_region - .as_ref() - .map(|region| Region::new(region.clone())) - .or_else(|| aws_types::region::default_provider().region()) - .unwrap_or_else(|| Region::new("us-west-2")); + let region = region::ChainProvider::first_try(default_region.map(Region::new)) + .or_default_provider() + .or_else(Region::new("us-west-2")); + + tracing_subscriber::fmt::init(); if verbose { println!("S3 client version: {}", s3::PKG_VERSION); - println!("Region: {:?}", ®ion); - - SubscriberBuilder::default() - .with_env_filter("info") - .with_span_events(FmtSpan::CLOSE) - .init(); + println!( + "Region: {:?}", + region.region().expect("region must be set") + ); } - let config = Config::builder().region(®ion).build(); + let config = Config::builder().region(region).build(); let client = Client::from_conf(config); @@ -68,7 +65,7 @@ async fn main() { Ok(resp) => { println!("Objects:"); for object in resp.contents.unwrap_or_default() { - println!(" {}", object.key.expect("objects have keys")); + println!(" `{}`", object.key.expect("objects have keys")); } } Err(e) => { diff --git a/rust-runtime/protocol-test-helpers/src/xml.rs b/rust-runtime/protocol-test-helpers/src/xml.rs index 2e070930750..e8197b54c35 100644 --- a/rust-runtime/protocol-test-helpers/src/xml.rs +++ b/rust-runtime/protocol-test-helpers/src/xml.rs @@ -157,8 +157,9 @@ fn is_list(node: Node) -> bool { fn non_empty_children<'a, 'input: 'a>( node: Node<'a, 'input>, ) -> impl Iterator> { + let single_child = node.children().count() == 1; node.children() - .filter(|c| !c.is_text() || !c.text().unwrap().trim().is_empty()) + .filter(move |c| single_child || !c.is_text() || !c.text().unwrap().trim().is_empty()) } #[cfg(test)] diff --git a/rust-runtime/smithy-xml/src/decode.rs b/rust-runtime/smithy-xml/src/decode.rs index b50f316326c..b179c93de98 100644 --- a/rust-runtime/smithy-xml/src/decode.rs +++ b/rust-runtime/smithy-xml/src/decode.rs @@ -393,9 +393,7 @@ pub fn try_data<'a, 'inp>( loop { match tokens.next().map(|opt| opt.map(|opt| opt.0)) { None => return Ok(Cow::Borrowed("")), - Some(Ok(Token::Text { text })) if !text.as_str().trim().is_empty() => { - return unescape(text.as_str().trim()) - } + Some(Ok(Token::Text { text })) => return unescape(text.as_str()), Some(Ok(e @ Token::ElementStart { .. })) => { return Err(XmlError::custom(format!( "Looking for a data element, found: {:?}", @@ -487,6 +485,25 @@ mod test { assert_eq!(try_data(&mut scoped).unwrap(), "hello"); } + /// Whitespace within an element is preserved + #[test] + fn read_data_whitespace() { + let xml = r#" hello "#; + let mut doc = Document::new(xml); + let mut scoped = doc.root_element().unwrap(); + assert_eq!(try_data(&mut scoped).unwrap(), " hello "); + } + + #[test] + fn ignore_insignificant_whitespace() { + let xml = r#" "#; + let mut doc = Document::new(xml); + let mut resp = doc.root_element().unwrap(); + let mut a = resp.next_tag().expect("should be a"); + let data = try_data(&mut a).expect("valid"); + assert_eq!(data, " "); + } + #[test] fn read_attributes() { let xml = r#"hello"#;