diff --git a/codegen-test/build.gradle.kts b/codegen-test/build.gradle.kts index d93f0de38101e294dd28ab5746584e32cdb718cf..927fdc9f249ac24843a9502832ff3fae9fc2bd69 100644 --- a/codegen-test/build.gradle.kts +++ b/codegen-test/build.gradle.kts @@ -44,6 +44,18 @@ val CodegenTests = listOf( "aws.protocoltests.restxml#RestXml", "rest_xml" ), + CodegenTest( + "aws.protocoltests.restxml.xmlns#RestXmlWithNamespace", + "rest_xml_namespace" + ), + CodegenTest( + "aws.protocoltests.restxml#RestXmlExtras", + "rest_xml_extras" + ), + CodegenTest( + "aws.protocoltests.restxmlunwrapped#RestXmlExtrasUnwrappedErrors", + "rest_xml_extras_unwrapped" + ), CodegenTest( "crate#Config", "naming_test", """ diff --git a/codegen-test/model/rest-xml-extras.smithy b/codegen-test/model/rest-xml-extras.smithy new file mode 100644 index 0000000000000000000000000000000000000000..de48a6bc8ecd1dfbac4df2d9c2961d6a955d81fb --- /dev/null +++ b/codegen-test/model/rest-xml-extras.smithy @@ -0,0 +1,100 @@ +$version: "1.0" +namespace aws.protocoltests.restxml + +use aws.protocols#restXml +use aws.api#service +use smithy.test#httpResponseTests + + +/// A REST XML service that sends XML requests and responses. +@service(sdkId: "Rest XML Protocol") +@restXml +service RestXmlExtras { + version: "2019-12-16", + operations: [AttributeParty, XmlMapsFlattenedNestedXmlNamespace] +} + +@enum([{"value": "enumvalue", "name": "V"}]) +string StringEnum + +integer PrimitiveInt + +structure AttributePartyInputOutput { + @xmlAttribute + enum: StringEnum, + + @xmlAttribute + @xmlName("prefix:anumber") + number: PrimitiveInt, + + @xmlAttribute + ts: Timestamp, + + @xmlAttribute + bool: Boolean +} + +@httpResponseTests([{ + id: "DeserAttributes", + code: 200, + body: "", + params: { + enum: "enumvalue", + number: 5, + ts: 482196050, + bool: true + }, + protocol: "aws.protocols#restXml" + +}]) +@http(uri: "/AttributeParty", method: "POST") +operation AttributeParty { + output: AttributePartyInputOutput +} + +@httpResponseTests([{ + id: "DeserFlatNamespaceMaps", + code: 200, + body: "map2thirdplzfourthonegaimap1secondkonnichiwafirsthi", + params: { + "myMap": { + "map2": {"fourth": "onegai", "third": "plz" }, + "map1": {"second": "konnichiwa", "first": "hi" } + } + }, + protocol: "aws.protocols#restXml" +}]) +@http(uri: "/XmlMapsFlattenedNestedXmlNamespace", method: "POST") +operation XmlMapsFlattenedNestedXmlNamespace { + input: XmlMapsFlattenedNestedXmlNamespaceInputOutput, + output: XmlMapsFlattenedNestedXmlNamespaceInputOutput +} + +@xmlNamespace(uri: "http://aoo.com") +structure XmlMapsFlattenedNestedXmlNamespaceInputOutput { + @xmlNamespace(uri: "http://boo.com") + @xmlFlattened + myMap: XmlMapsNestedNamespaceInputOutputMap, +} + +@xmlNamespace(uri: "http://coo.com") +map XmlMapsNestedNamespaceInputOutputMap { + @xmlNamespace(uri: "http://doo.com") + @xmlName("yek") + key: String, + + @xmlNamespace(uri: "http://eoo.com") + @xmlName("eulav") + value: XmlMapsNestedNestedNamespaceInputOutputMap +} + +@xmlNamespace(uri: "http://foo.com") +map XmlMapsNestedNestedNamespaceInputOutputMap { + @xmlNamespace(uri: "http://goo.com") + @xmlName("K") + key: String, + + @xmlNamespace(uri: "http://hoo.com") + @xmlName("V") + value: String +} diff --git a/codegen-test/model/rest-xml-unwrapped-errors.smithy b/codegen-test/model/rest-xml-unwrapped-errors.smithy new file mode 100644 index 0000000000000000000000000000000000000000..1d22bbc2ebf7737d1b62484e139020f292be8c8e --- /dev/null +++ b/codegen-test/model/rest-xml-unwrapped-errors.smithy @@ -0,0 +1,137 @@ +// This file defines test cases that test error serialization. + +$version: "1.0" + +namespace aws.protocoltests.restxmlunwrapped + +use aws.protocols#restXml +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests +use aws.api#service + + +/// A REST XML service that sends XML requests and responses. +@service(sdkId: "Rest XML Protocol") +@restXml(noErrorWrapping: true) +service RestXmlExtrasUnwrappedErrors { + version: "2019-12-16", + operations: [GreetingWithUnwrappedErrors] +} + +/// This operation has three possible return values: +/// +/// 1. A successful response in the form of GreetingWithErrorsOutput +/// 2. An InvalidGreeting error. +/// 3. A BadRequest error. +/// +/// Implementations must be able to successfully take a response and +/// properly (de)serialize successful and error responses based on the +/// the presence of the +@idempotent +@http(uri: "/GreetingWithErrors", method: "PUT") +operation GreetingWithUnwrappedErrors { + output: GreetingWithErrorsOutput, + errors: [InvalidGreetingUnwrapped, ComplexErrorUnwrapped] +} + +apply GreetingWithUnwrappedErrors @httpResponseTests([ + { + id: "GreetingWithErrorsUnwrapped", + documentation: "Ensures that operations with errors successfully know how to deserialize the successful response", + protocol: restXml, + code: 200, + body: "", + headers: { + "X-Greeting": "Hello" + }, + params: { + greeting: "Hello" + } + } +]) + +structure GreetingWithErrorsOutput { + @httpHeader("X-Greeting") + greeting: String, +} + +/// This error is thrown when an invalid greeting value is provided. +@error("client") +@httpError(400) +structure InvalidGreetingUnwrapped { + Message: String, +} + +apply InvalidGreetingUnwrapped @httpResponseTests([ + { + id: "InvalidGreetingErrorUnwrapped", + documentation: "Parses simple XML errors", + protocol: restXml, + params: { + Message: "Hi" + }, + code: 400, + headers: { + "Content-Type": "application/xml" + }, + body: """ + + Sender + InvalidGreetingUnwrapped + Hi + setting + foo-id + + """, + bodyMediaType: "application/xml", + } +]) + +/// This error is thrown when a request is invalid. +@error("client") +@httpError(403) +structure ComplexErrorUnwrapped { + // Errors support HTTP bindings! + @httpHeader("X-Header") + Header: String, + + TopLevel: String, + + Nested: ComplexNestedErrorData, +} + +apply ComplexErrorUnwrapped @httpResponseTests([ + { + id: "ComplexErrorUnwrapped", + protocol: restXml, + params: { + Header: "Header", + TopLevel: "Top level", + Nested: { + Foo: "bar" + } + }, + code: 400, + headers: { + "Content-Type": "application/xml", + "X-Header": "Header", + }, + body: """ + + Sender + ComplexErrorUnwrapped + Hi + Top level + + bar + + foo-id + + """, + bodyMediaType: "application/xml", + } +]) + +structure ComplexNestedErrorData { + Foo: String, +} diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/parsers/XmlBindingTraitParserGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/parsers/XmlBindingTraitParserGenerator.kt index 9d791748c5cbab7c60da2bd01cea31088c9f21df..1c08409803836880b1e21b4eb826c7e1b7884464 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/parsers/XmlBindingTraitParserGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/parsers/XmlBindingTraitParserGenerator.kt @@ -135,6 +135,7 @@ class XmlBindingTraitParserGenerator(protocolConfig: ProtocolConfig, private val """ use std::convert::TryFrom; let mut doc = #{Document}::try_from(inp)?; + ##[allow(unused_mut)] let mut decoder = doc.root_element()?; let start_el = decoder.start_el(); if !(${shapeName.compareTo("start_el")}) { @@ -189,6 +190,7 @@ class XmlBindingTraitParserGenerator(protocolConfig: ProtocolConfig, private val """ use std::convert::TryFrom; let mut doc = #{Document}::try_from(inp)?; + ##[allow(unused_mut)] let mut decoder = doc.root_element()?; let start_el = decoder.start_el(); if !(${shapeName.compareTo("start_el")}) { @@ -249,6 +251,10 @@ class XmlBindingTraitParserGenerator(protocolConfig: ProtocolConfig, private val } rust("$builder.${symbolProvider.toMemberName(member)} = $temp;") } + // No need to generate a parse loop if there are no non-attribute members + if (members.dataMembers.isEmpty()) { + return + } parseLoop(outerCtx) { ctx -> members.dataMembers.forEach { member -> case(member) {