diff --git a/docs/source/spec/xml.rst b/docs/source/spec/xml.rst index fd8becfa156..a79e12ca549 100644 --- a/docs/source/spec/xml.rst +++ b/docs/source/spec/xml.rst @@ -221,8 +221,9 @@ following document: ``-``. Values for an ``xmlName`` adhere to the following ABNF. .. productionlist:: smithy - xml_name :(ALPHA / "_") + xml_identifier :(ALPHA / "_") :*(ALPHA / DIGIT / "-" / "_") + xml_name :xml_identifier / (xml_identifier ":" xml_identifier) .. _xmlNamespace-trait: @@ -252,6 +253,9 @@ The ``xmlNamespace`` trait is an object that contains the following properties: * - uri - ``string`` value containing a valid URI - **Required**. The namespace URI for scoping this XML element. + * - prefix + - ``string`` value + - The prefix for elements from this namespace. Given the following structure definition, @@ -306,6 +310,71 @@ following document: def +Given the following definition with a prefix: + +.. tabs:: + + .. code-tab:: smithy + + @xmlNamespace(uri: "http://foo.com", prefix: "bar") + structure MyStructure { + foo: String, + @xmlName("baz:bar") + bar: String, + } + + .. code-tab:: json + + { + "smithy": "0.4.0", + "smithy.example": { + "shapes": { + "MyStructure": { + "type": "structure", + "members": { + "foo": { + "target": "String" + }, + "bar": { + "target": "String", + "xmlName": "baz:bar" + } + }, + "xmlNamespace": { + "uri": "http://foo.com", + "prefix": "baz" + } + } + } + } + } + +and the following values provided for ``MyStructure``, + +:: + + "foo" = "abc" + "bar" = "def" + +the XML representation of the value would be serialized with the +following document: + +.. code-block:: xml + + + abc + def + + +.. note:: + + Values for the ``prefix`` option must start with a letter (lower/upper + case) or ``_``, followed by letters (lower/upper case), digits, ``_``, or + ``-``. Values for ``prefix`` adhere to the following ABNF. + +.. productionlist:: smithy + xml_prefix :(ALPHA / "_") + :*(ALPHA / DIGIT / "-" / "_") .. _xml-examples: diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/XmlNamespaceTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/XmlNamespaceTrait.java index 5b9b0e20eae..a98d7ebda21 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/XmlNamespaceTrait.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/XmlNamespaceTrait.java @@ -17,8 +17,10 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.MapUtils; @@ -31,23 +33,32 @@ public final class XmlNamespaceTrait extends AbstractTrait implements ToSmithyBuilder { public static final ShapeId ID = ShapeId.from("smithy.api#xmlNamespace"); - private static final List XML_NAMESPACE_PROPERTIES = ListUtils.of("uri"); + private static final String PREFIX = "prefix"; + private static final String URI = "uri"; + private static final List XML_NAMESPACE_PROPERTIES = ListUtils.of(URI, PREFIX); + private final String prefix; private final String uri; private XmlNamespaceTrait(Builder builder) { super(ID, builder.getSourceLocation()); uri = SmithyBuilder.requiredState("uri", builder.uri); + prefix = builder.prefix; } public String getUri() { return uri; } + public Optional getPrefix() { + return Optional.ofNullable(prefix); + } + @Override protected Node createNode() { return new ObjectNode(MapUtils.of(), getSourceLocation()) - .withMember("uri", Node.from(uri)); + .withMember(URI, Node.from(uri)) + .withOptionalMember(PREFIX, getPrefix().map(Node::from)); } /** @@ -59,7 +70,10 @@ public static Builder builder() { @Override public Builder toBuilder() { - return builder().sourceLocation(getSourceLocation()).uri(uri); + return builder() + .sourceLocation(getSourceLocation()) + .uri(uri) + .prefix(prefix); } /** @@ -67,6 +81,7 @@ public Builder toBuilder() { */ public static final class Builder extends AbstractTraitBuilder { private String uri; + private String prefix; private Builder() {} @@ -75,6 +90,11 @@ public Builder uri(String uri) { return this; } + public Builder prefix(String prefix) { + this.prefix = prefix; + return this; + } + @Override public XmlNamespaceTrait build() { return new XmlNamespaceTrait(this); @@ -92,7 +112,8 @@ public XmlNamespaceTrait createTrait(ShapeId target, Node value) { Builder builder = builder().sourceLocation(value); ObjectNode node = value.expectObjectNode(); node.warnIfAdditionalProperties(XML_NAMESPACE_PROPERTIES); - builder.uri(node.expectStringMember("uri").getValue()); + builder.uri(node.expectStringMember(URI).getValue()); + node.getStringMember(PREFIX).map(StringNode::getValue).ifPresent(builder::prefix); return builder.build(); } } diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy index 96a34fb0819..b235a68ddf2 100644 --- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy +++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy @@ -156,7 +156,7 @@ structure xmlFlattened {} /// used in the model. @trait @tags(["diff.error.const"]) -@pattern("^[a-zA-Z_][a-zA-Z_0-9-]*$") +@pattern("^[a-zA-Z_][a-zA-Z_0-9-]*(:[a-zA-Z_][a-zA-Z_0-9-]*)?$") string xmlName /// Adds an xmlns namespace definition URI to an XML element. @@ -166,6 +166,9 @@ structure xmlNamespace { /// The namespace URI for scoping this XML element. @required uri: NonEmptyString, + /// The prefix for the given namespace. + @pattern("^[a-zA-Z_][a-zA-Z_0-9-]*$") + prefix: NonEmptyString, } @private diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/traits/XmlNameTraitTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/traits/XmlNameTraitTest.java index 5df66bc78fb..8b27a20ac22 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/traits/XmlNameTraitTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/traits/XmlNameTraitTest.java @@ -38,4 +38,17 @@ public void loadsTraitWithString() { assertThat(xmlNameTrait.getValue(), equalTo("Text")); assertThat(xmlNameTrait.toNode(), equalTo(node)); } + + @Test + public void loadsWithColonInValue() { + Node node = Node.from("xsi:type"); + TraitFactory provider = TraitFactory.createServiceFactory(); + Optional trait = provider.createTrait(ShapeId.from("smithy.api#xmlName"), ShapeId.from("ns.qux#foo"), node); + + assertTrue(trait.isPresent()); + assertThat(trait.get(), instanceOf(XmlNameTrait.class)); + XmlNameTrait xmlNameTrait = (XmlNameTrait) trait.get(); + assertThat(xmlNameTrait.getValue(), equalTo("xsi:type")); + assertThat(xmlNameTrait.toNode(), equalTo(node)); + } } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/traits/XmlNamespaceTraitTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/traits/XmlNamespaceTraitTest.java index 0aeb55696b5..fa4b48177fa 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/traits/XmlNamespaceTraitTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/traits/XmlNamespaceTraitTest.java @@ -55,4 +55,21 @@ public void omitsEmptiesFromSerializedNode() { assertFalse(serialized.getMember("prefix").isPresent()); } + + @Test + public void loadsTraitWithPrefix() { + TraitFactory provider = TraitFactory.createServiceFactory(); + ObjectNode node = Node.objectNode() + .withMember("uri", Node.from("https://www.amazon.com")) + .withMember("prefix", Node.from("xsi")); + Optional trait = provider.createTrait( + ShapeId.from("smithy.api#xmlNamespace"), ShapeId.from("ns.qux#foo"), node); + assertTrue(trait.isPresent()); + assertThat(trait.get(), instanceOf(XmlNamespaceTrait.class)); + XmlNamespaceTrait xmlNamespace = (XmlNamespaceTrait) trait.get(); + + assertThat(xmlNamespace.getUri(), equalTo("https://www.amazon.com")); + assertTrue(xmlNamespace.getPrefix().isPresent()); + assertThat(xmlNamespace.getPrefix().get(), equalTo("xsi")); + } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-name.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-name.errors new file mode 100644 index 00000000000..fc037615958 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-name.errors @@ -0,0 +1 @@ +[ERROR] ns.foo#InvalidName$a: Error validating trait `xmlName`: String value provided for `smithy.api#xmlName` must match regular expression: ^[a-zA-Z_][a-zA-Z_0-9-]*(:[a-zA-Z_][a-zA-Z_0-9-]*)?$ | TraitValue \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-name.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-name.json new file mode 100644 index 00000000000..aee71cd1010 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-name.json @@ -0,0 +1,34 @@ +{ + "smithy": "0.4.0", + "ns.foo": { + "shapes": { + "ValidName1": { + "type": "structure", + "members": { + "a": { + "target": "smithy.api#String", + "smithy.api#xmlName": "customname" + } + } + }, + "ValidName2": { + "type": "structure", + "members": { + "a": { + "target": "smithy.api#String", + "smithy.api#xmlName": "customprefix:customname" + } + } + }, + "InvalidName": { + "type": "structure", + "members": { + "a": { + "target": "smithy.api#String", + "smithy.api#xmlName": "too:many:colons" + } + } + } + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-namespace.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-namespace.json index 110001f9222..321834ba165 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-namespace.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-namespace.json @@ -13,6 +13,18 @@ "uri": "http://foo.com" } }, + "ValidNamespace2": { + "type": "structure", + "members": { + "a": { + "target": "String" + } + }, + "smithy.api#xmlNamespace": { + "uri": "http://www.w3.org/2001/XMLSchema-instance", + "prefix": "xsi" + } + }, "InvalidNamespace": { "type": "structure", "members": {