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"#;