From 3469417c0886d087eab70b1e4e4d137ee5fb0e6f Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Mon, 21 Jun 2021 15:39:01 -0700 Subject: [PATCH 1/2] Fix for parsing string headers --- codegen-test/model/rest-xml-extras.smithy | 32 ++++++++++++++++++- .../http/ResponseBindingGenerator.kt | 7 ++++ rust-runtime/smithy-http/src/header.rs | 16 +++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/codegen-test/model/rest-xml-extras.smithy b/codegen-test/model/rest-xml-extras.smithy index df3e62970b..7c7a7a3993 100644 --- a/codegen-test/model/rest-xml-extras.smithy +++ b/codegen-test/model/rest-xml-extras.smithy @@ -17,7 +17,8 @@ service RestXmlExtras { XmlMapsFlattenedNestedXmlNamespace, EnumKeys, PrimitiveIntOpXml, - ChecksumRequired + ChecksumRequired, + StringHeader, ] } @@ -203,3 +204,32 @@ operation ChecksumRequired { structure ChecksumRequiredInput { field: String } + + +@httpResponseTests([{ + id: "DeserHeaderStringCommas", + code: 200, + documentation: """ + Regression test for https://github.com/awslabs/aws-sdk-rust/issues/122 + where `,` was eagerly used to split fields in cases where the input was not + a list. + """, + body: "", + headers: { "x-field": "a,b,c" }, + params: { + field: "a,b,c" + }, + protocol: "aws.protocols#restXml" +}]) +@http(uri: "/StringHeader", method: "POST") +operation StringHeader { + output: StringHeaderOutput +} + +structure StringHeaderOutput { + @httpHeader("x-field") + field: String, + + @httpHeader("x-enum") + enumHeader: StringEnum +} diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/http/ResponseBindingGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/http/ResponseBindingGenerator.kt index f724658bb6..7d969e9c7c 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/http/ResponseBindingGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/http/ResponseBindingGenerator.kt @@ -212,6 +212,13 @@ class ResponseBindingGenerator(protocolConfig: ProtocolConfig, private val opera */ private fun RustWriter.deserializeFromHeader(targetType: Shape, memberShape: MemberShape) { val rustType = symbolProvider.toSymbol(targetType).rustType().stripOuter() + // Normally, we go through a flow that looks for `,`s but that's wrong if the output + // is just a single string (which might include `,`s.). + // MediaType doesn't include `,` since it's base64, send that through the normal path + if (targetType is StringShape && !targetType.hasTrait()) { + rust("#T::exactly_one(headers)", headerUtil) + return + } val (coreType, coreShape) = if (targetType is CollectionShape) { rustType.stripOuter() to model.expectShape(targetType.member.target) } else { diff --git a/rust-runtime/smithy-http/src/header.rs b/rust-runtime/smithy-http/src/header.rs index 93b98ed869..578a4bfed2 100644 --- a/rust-runtime/smithy-http/src/header.rs +++ b/rust-runtime/smithy-http/src/header.rs @@ -73,8 +73,22 @@ where Ok(out) } +pub fn exactly_one( + mut values: ValueIter, +) -> Result, ParseError> { + let first = match values.next() { + Some(v) => v, + None => return Ok(None), + }; + let value = std::str::from_utf8(first.as_bytes()).map_err(|_| ParseError)?; + match values.next() { + None => T::from_str(value.trim()).map_err(|_| ParseError).map(Some), + Some(_) => Err(ParseError), + } +} + /// Read one comma delimited value for `FromStr` types -pub fn read_one(s: &[u8]) -> Result<(T, &[u8]), ParseError> +fn read_one(s: &[u8]) -> Result<(T, &[u8]), ParseError> where T: FromStr, { From df65db0f8ca4f393d5558ad55d2e77f5c6fd10a7 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Mon, 21 Jun 2021 15:42:28 -0700 Subject: [PATCH 2/2] Rename function & add docs --- .../smithy/generators/http/ResponseBindingGenerator.kt | 2 +- rust-runtime/smithy-http/src/header.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/http/ResponseBindingGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/http/ResponseBindingGenerator.kt index 7d969e9c7c..d190347100 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/http/ResponseBindingGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/http/ResponseBindingGenerator.kt @@ -216,7 +216,7 @@ class ResponseBindingGenerator(protocolConfig: ProtocolConfig, private val opera // is just a single string (which might include `,`s.). // MediaType doesn't include `,` since it's base64, send that through the normal path if (targetType is StringShape && !targetType.hasTrait()) { - rust("#T::exactly_one(headers)", headerUtil) + rust("#T::one_or_none(headers)", headerUtil) return } val (coreType, coreShape) = if (targetType is CollectionShape) { diff --git a/rust-runtime/smithy-http/src/header.rs b/rust-runtime/smithy-http/src/header.rs index 578a4bfed2..00262fc66b 100644 --- a/rust-runtime/smithy-http/src/header.rs +++ b/rust-runtime/smithy-http/src/header.rs @@ -73,7 +73,10 @@ where Ok(out) } -pub fn exactly_one( +/// Read exactly one or none from a headers iterator +/// +/// This function does not perform comma splitting like `read_many` +pub fn one_or_none( mut values: ValueIter, ) -> Result, ParseError> { let first = match values.next() {