From 930ac2392ce0f8042d466efdca88b1d17cc5366a Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 27 Nov 2020 16:25:13 -0800 Subject: [PATCH] Clarify various aspects of xmlNamespace traits * The xmlNamespace trait can only be applied to simple types, list, map, set, structure, union, member, and service shapes. We previously allowed "*", but that includes resource and operation shapes where the trait isn't actually used. * aws.protocols#awsQuery and aws.protocols#ec2Query protocol traits both now require that an xmlNamespace is set on the top-level service shape they are applied to so that the namespace is guaranteed to be known. * Added various examples for how the xmlNamespace trait interacts with flattened lists and maps. * Add a protocol test suite named "restXmlWithNamespace" to test that implementations add an xmlns to restXml services when necessary, closing #616 --- .../1.0/spec/aws/aws-ec2-query-protocol.rst | 4 +- .../1.0/spec/aws/aws-query-protocol.rst | 4 +- docs/source/1.0/spec/core/xml-traits.rst | 4 +- .../model/awsQuery/main.smithy | 1 + .../model/awsQuery/xml-lists.smithy | 28 ++++ .../model/awsQuery/xml-maps.smithy | 54 ++++++++ .../model/ec2Query/xml-lists.smithy | 31 ++++- .../model/restXml/document-lists.smithy | 31 ++++- .../model/restXml/document-maps.smithy | 53 +++++++ .../model/restXml/main.smithy | 1 + .../model/restXmlWithNamespace/main.smithy | 131 ++++++++++++++++++ .../META-INF/smithy/aws.protocols.json | 4 +- ...-protocols-do-not-support-documents.smithy | 1 + .../amazon/smithy/model/loader/prelude.smithy | 3 +- 14 files changed, 342 insertions(+), 8 deletions(-) create mode 100644 smithy-aws-protocol-tests/model/restXmlWithNamespace/main.smithy diff --git a/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst b/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst index 52de2616157..8002f8320fb 100644 --- a/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst @@ -23,7 +23,9 @@ Summary OR in a ``x-form-url-encoded`` body and responses in XML documents. This protocol is an Amazon EC2-specific extension of the ``awsQuery`` protocol. Trait selector - ``service`` + ``service [trait|xmlNamespace]`` + + *Service shapes with the xmlNamespace trait* Value type Annotation trait. diff --git a/docs/source/1.0/spec/aws/aws-query-protocol.rst b/docs/source/1.0/spec/aws/aws-query-protocol.rst index d2dbe30761c..4689c052d4d 100644 --- a/docs/source/1.0/spec/aws/aws-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-query-protocol.rst @@ -22,7 +22,9 @@ Summary Adds support for an HTTP protocol that sends requests in the query string and responses in XML documents. Trait selector - ``service`` + ``service [trait|xmlNamespace]`` + + *Service shapes with the xmlNamespace trait* Value type Annotation trait. See diff --git a/docs/source/1.0/spec/core/xml-traits.rst b/docs/source/1.0/spec/core/xml-traits.rst index 1475b7b179f..6c5ce50fc02 100644 --- a/docs/source/1.0/spec/core/xml-traits.rst +++ b/docs/source/1.0/spec/core/xml-traits.rst @@ -937,7 +937,9 @@ The XML serialization is: Summary Adds an `XML namespace`_ to an XML element. Trait selector - ``*`` + ``:is(service, member, simpleType, collection, map, structure, union)`` + + *Service, simple types, list, map, set, structure, or union* Value type ``structure`` Conflicts with diff --git a/smithy-aws-protocol-tests/model/awsQuery/main.smithy b/smithy-aws-protocol-tests/model/awsQuery/main.smithy index 7046a0742fc..0a430261fbe 100644 --- a/smithy-aws-protocol-tests/model/awsQuery/main.smithy +++ b/smithy-aws-protocol-tests/model/awsQuery/main.smithy @@ -35,6 +35,7 @@ service AwsQuery { XmlMapsXmlName, FlattenedXmlMap, FlattenedXmlMapWithXmlName, + FlattenedXmlMapWithXmlNamespace, XmlEmptyMaps, // Output XML list tests diff --git a/smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy b/smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy index e91e49183d2..970941f6de2 100644 --- a/smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy +++ b/smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy @@ -79,6 +79,10 @@ apply XmlLists @httpResponseTests([ bye yep nope + a + b + a + b 1 @@ -107,6 +111,8 @@ apply XmlLists @httpResponseTests([ renamedListMembers: ["foo", "bar"], flattenedList: ["hi", "bye"], flattenedList2: ["yep", "nope"], + flattenedListWithMemberNamespace: ["a", "b"], + flattenedListWithNamespace: ["a", "b"], structureList: [ { a: "1", @@ -180,6 +186,17 @@ structure XmlListsOutput { // serializing flattened lists in structures. flattenedList2: RenamedListMembers, + // The XML namespace of the flattened list's member is used, and + // list's XML namespace is disregarded. + @xmlFlattened + flattenedListWithMemberNamespace: ListWithMemberNamespace, + + // Again, the XML namespace of the flattened list is ignored. + // The namespace of the member is used, which is empty, so + // no xmlns attribute appears on the serialized XML. + @xmlFlattened + flattenedListWithNamespace: ListWithNamespace, + @xmlName("myStructureList") structureList: StructureList } @@ -201,3 +218,14 @@ structure StructureListMember { @xmlName("other") b: String, } + +@xmlNamespace(uri: "https://xml-list.example.com") +list ListWithMemberNamespace { + @xmlNamespace(uri: "https://xml-member.example.com") + member: String, +} + +@xmlNamespace(uri: "https://xml-list.example.com") +list ListWithNamespace { + member: String, +} diff --git a/smithy-aws-protocol-tests/model/awsQuery/xml-maps.smithy b/smithy-aws-protocol-tests/model/awsQuery/xml-maps.smithy index 0d3ff2006a6..7c686615d19 100644 --- a/smithy-aws-protocol-tests/model/awsQuery/xml-maps.smithy +++ b/smithy-aws-protocol-tests/model/awsQuery/xml-maps.smithy @@ -269,3 +269,57 @@ map FlattenedXmlMapWithXmlNameOutputMap { @xmlName("V") value: String, } + +/// Flattened maps with @xmlNamespace and @xmlName +operation FlattenedXmlMapWithXmlNamespace { + output: FlattenedXmlMapWithXmlNamespaceOutput +} + +apply FlattenedXmlMapWithXmlNamespace @httpResponseTests([ + { + id: "QueryQueryFlattenedXmlMapWithXmlNamespace", + documentation: "Serializes flattened XML maps in responses that have xmlNamespace and xmlName on members", + protocol: awsQuery, + code: 200, + body: """ + + + + a + A + + + b + B + + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "text/xml" + }, + params: { + myMap: { + a: "A", + b: "B", + } + } + } +]) + +structure FlattenedXmlMapWithXmlNamespaceOutput { + @xmlFlattened + @xmlName("KVP") + @xmlNamespace(uri: "https://the-member.example.com") + myMap: FlattenedXmlMapWithXmlNamespaceOutputMap, +} + +map FlattenedXmlMapWithXmlNamespaceOutputMap { + @xmlName("K") + @xmlNamespace(uri: "https://the-key.example.com") + key: String, + + @xmlName("V") + @xmlNamespace(uri: "https://the-value.example.com") + value: String, +} diff --git a/smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy b/smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy index 1ea6f16dfd8..0e9beab5e94 100644 --- a/smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy +++ b/smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy @@ -23,7 +23,8 @@ use smithy.test#httpResponseTests /// 4. XML lists with @xmlName on its members /// 5. Flattened XML lists. /// 6. Flattened XML lists with @xmlName. -/// 7. Lists of structures. +/// 7. Flattened XML lists with @xmlNamespace. +/// 8. Lists of structures. operation XmlLists { output: XmlListsOutput } @@ -78,6 +79,10 @@ apply XmlLists @httpResponseTests([ bye yep nope + a + b + a + b 1 @@ -106,6 +111,8 @@ apply XmlLists @httpResponseTests([ renamedListMembers: ["foo", "bar"], flattenedList: ["hi", "bye"], flattenedList2: ["yep", "nope"], + flattenedListWithMemberNamespace: ["a", "b"], + flattenedListWithNamespace: ["a", "b"], structureList: [ { a: "1", @@ -177,6 +184,17 @@ structure XmlListsOutput { // serializing flattened lists in structures. flattenedList2: RenamedListMembers, + // The XML namespace of the flattened list's member is used, and + // list's XML namespace is disregarded. + @xmlFlattened + flattenedListWithMemberNamespace: ListWithMemberNamespace, + + // Again, the XML namespace of the flattened list is ignored. + // The namespace of the member is used, which is empty, so + // no xmlns attribute appears on the serialized XML. + @xmlFlattened + flattenedListWithNamespace: ListWithNamespace, + @xmlName("myStructureList") structureList: StructureList } @@ -198,3 +216,14 @@ structure StructureListMember { @xmlName("other") b: String, } + +@xmlNamespace(uri: "https://xml-list.example.com") +list ListWithMemberNamespace { + @xmlNamespace(uri: "https://xml-member.example.com") + member: String, +} + +@xmlNamespace(uri: "https://xml-list.example.com") +list ListWithNamespace { + member: String, +} diff --git a/smithy-aws-protocol-tests/model/restXml/document-lists.smithy b/smithy-aws-protocol-tests/model/restXml/document-lists.smithy index 7743210874d..288cf4c4eb3 100644 --- a/smithy-aws-protocol-tests/model/restXml/document-lists.smithy +++ b/smithy-aws-protocol-tests/model/restXml/document-lists.smithy @@ -26,7 +26,8 @@ use smithy.test#httpResponseTests /// 4. XML lists with @xmlName on its members /// 5. Flattened XML lists. /// 6. Flattened XML lists with @xmlName. -/// 7. Lists of structures. +/// 7. Flattened XML lists with @xmlNamespace. +/// 8. Lists of structures. @idempotent @http(uri: "/XmlLists", method: "PUT") operation XmlLists { @@ -176,6 +177,10 @@ apply XmlLists @httpResponseTests([ bye yep nope + a + b + a + b 1 @@ -203,6 +208,8 @@ apply XmlLists @httpResponseTests([ renamedListMembers: ["foo", "bar"], flattenedList: ["hi", "bye"], flattenedList2: ["yep", "nope"], + flattenedListWithMemberNamespace: ["a", "b"], + flattenedListWithNamespace: ["a", "b"], structureList: [ { a: "1", @@ -300,6 +307,17 @@ structure XmlListsInputOutput { // serializing flattened lists in structures. flattenedList2: RenamedListMembers, + // The XML namespace of the flattened list's member is used, and + // list's XML namespace is disregarded. + @xmlFlattened + flattenedListWithMemberNamespace: ListWithMemberNamespace, + + // Again, the XML namespace of the flattened list is ignored. + // The namespace of the member is used, which is empty, so + // no xmlns attribute appears on the serialized XML. + @xmlFlattened + flattenedListWithNamespace: ListWithNamespace, + @xmlName("myStructureList") structureList: StructureList } @@ -321,3 +339,14 @@ structure StructureListMember { @xmlName("other") b: String, } + +@xmlNamespace(uri: "https://xml-list.example.com") +list ListWithMemberNamespace { + @xmlNamespace(uri: "https://xml-member.example.com") + member: String, +} + +@xmlNamespace(uri: "https://xml-list.example.com") +list ListWithNamespace { + member: String, +} diff --git a/smithy-aws-protocol-tests/model/restXml/document-maps.smithy b/smithy-aws-protocol-tests/model/restXml/document-maps.smithy index d6c240f1e0e..ec078390a65 100644 --- a/smithy-aws-protocol-tests/model/restXml/document-maps.smithy +++ b/smithy-aws-protocol-tests/model/restXml/document-maps.smithy @@ -433,3 +433,56 @@ map FlattenedXmlMapWithXmlNameInputOutputMap { @xmlName("V") value: String, } + +/// Flattened maps with @xmlNamespace and @xmlName +@http(uri: "/FlattenedXmlMapWithXmlNamespace", method: "POST") +operation FlattenedXmlMapWithXmlNamespace { + output: FlattenedXmlMapWithXmlNamespaceOutput +} + +apply FlattenedXmlMapWithXmlNamespace @httpResponseTests([ + { + id: "RestXmlFlattenedXmlMapWithXmlNamespace", + documentation: "Serializes flattened XML maps in responses that have xmlNamespace and xmlName on members", + protocol: restXml, + code: 200, + body: """ + + + a + A + + + b + B + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml" + }, + params: { + myMap: { + a: "A", + b: "B", + } + } + } +]) + +structure FlattenedXmlMapWithXmlNamespaceOutput { + @xmlFlattened + @xmlName("KVP") + @xmlNamespace(uri: "https://the-member.example.com") + myMap: FlattenedXmlMapWithXmlNamespaceOutputMap, +} + +map FlattenedXmlMapWithXmlNamespaceOutputMap { + @xmlName("K") + @xmlNamespace(uri: "https://the-key.example.com") + key: String, + + @xmlName("V") + @xmlNamespace(uri: "https://the-value.example.com") + value: String, +} diff --git a/smithy-aws-protocol-tests/model/restXml/main.smithy b/smithy-aws-protocol-tests/model/restXml/main.smithy index 51ec85e57b1..1829415ce69 100644 --- a/smithy-aws-protocol-tests/model/restXml/main.smithy +++ b/smithy-aws-protocol-tests/model/restXml/main.smithy @@ -71,6 +71,7 @@ service RestXml { XmlMapsXmlName, FlattenedXmlMap, FlattenedXmlMapWithXmlName, + FlattenedXmlMapWithXmlNamespace, // @xmlAttribute tests XmlAttributes, diff --git a/smithy-aws-protocol-tests/model/restXmlWithNamespace/main.smithy b/smithy-aws-protocol-tests/model/restXmlWithNamespace/main.smithy new file mode 100644 index 00000000000..0cb5c062bcd --- /dev/null +++ b/smithy-aws-protocol-tests/model/restXmlWithNamespace/main.smithy @@ -0,0 +1,131 @@ +$version: "1.0" + +namespace aws.protocoltests.restxml.xmlns + +use aws.api#service +use aws.protocols#restXml +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +/// A REST XML service that sends XML requests and responses. +/// +/// This service and test case is complementary to the test cases +/// in the `restXml` directory, but the service under test here has +/// the `xmlNamespace` trait applied to it. +/// +/// See https://github.com/awslabs/smithy/issues/616 +@service(sdkId: "Rest Xml Protocol Namespace") +@xmlNamespace(uri: "https://example.com") +@restXml +service RestXmlWithNamespace { + version: "2019-12-16", + operations: [SimpleScalarProperties] +} + +// This example serializes simple scalar types in the top level XML document. +// Note that headers are not serialized in the payload. +// +// This is a partial copy of aws.protocoltests.restxml#SimpleScalarProperties, +// but only includes enough test cases to ensure a namespace is serialized. +@idempotent +@http(uri: "/SimpleScalarProperties", method: "PUT") +operation SimpleScalarProperties { + input: SimpleScalarPropertiesInputOutput, + output: SimpleScalarPropertiesInputOutput +} + +apply SimpleScalarProperties @httpRequestTests([ + { + id: "XmlNamespaceSimpleScalarProperties", + documentation: "Serializes simple scalar properties", + protocol: restXml, + method: "PUT", + uri: "/SimpleScalarProperties", + body: """ + + string + true + false + 1 + 2 + 3 + 4 + 5.5 + 6.5 + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml", + "X-Foo": "Foo", + }, + params: { + foo: "Foo", + stringValue: "string", + trueBooleanValue: true, + falseBooleanValue: false, + byteValue: 1, + shortValue: 2, + integerValue: 3, + longValue: 4, + floatValue: 5.5, + doubleValue: 6.5, + } + } +]) + +apply SimpleScalarProperties @httpResponseTests([ + { + id: "XmlNamespaceSimpleScalarProperties", + documentation: "Serializes simple scalar properties", + protocol: restXml, + code: 200, + body: """ + + string + true + false + 1 + 2 + 3 + 4 + 5.5 + 6.5 + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml", + "X-Foo": "Foo", + }, + params: { + foo: "Foo", + stringValue: "string", + trueBooleanValue: true, + falseBooleanValue: false, + byteValue: 1, + shortValue: 2, + integerValue: 3, + longValue: 4, + floatValue: 5.5, + doubleValue: 6.5, + } + } +]) + +structure SimpleScalarPropertiesInputOutput { + @httpHeader("X-Foo") + foo: String, + + stringValue: String, + trueBooleanValue: Boolean, + falseBooleanValue: Boolean, + byteValue: Byte, + shortValue: Short, + integerValue: Integer, + longValue: Long, + floatValue: Float, + + @xmlName("DoubleDribble") + doubleValue: Double, +} diff --git a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json index 9669251129d..0a1506679cf 100644 --- a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json +++ b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json @@ -124,7 +124,7 @@ "type": "structure", "traits": { "smithy.api#trait": { - "selector": "service" + "selector": "service [trait|xmlNamespace]" }, "smithy.api#protocolDefinition": { "noInlineDocumentSupport": true, @@ -143,7 +143,7 @@ "type": "structure", "traits": { "smithy.api#trait": { - "selector": "service" + "selector": "service [trait|xmlNamespace]" }, "smithy.api#protocolDefinition": { "noInlineDocumentSupport": true, diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy index 2852cba7e2e..3d1b24ad37f 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy @@ -7,6 +7,7 @@ use aws.protocols#awsQuery @awsQuery @suppress(["DeprecatedTrait"]) // ignore the fact that the awsQuery trait is deprecated +@xmlNamespace(uri: "https://example.com") service InvalidExample { version: "2020-06-15", operations: [Operation1] diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy index b48215dec1e..66182540152 100644 --- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy +++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy @@ -299,7 +299,8 @@ structure xmlFlattened {} string xmlName /// Adds an xmlns namespace definition URI to an XML element. -@trait(conflicts: [xmlAttribute]) +@trait(selector: ":is(service, member, simpleType, collection, map, structure, union)", + conflicts: [xmlAttribute]) @tags(["diff.error.const"]) structure xmlNamespace { /// The namespace URI for scoping this XML element.