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": {