Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying xml namespace prefix #195

Merged
merged 6 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion docs/source/spec/xml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,

Expand Down Expand Up @@ -306,6 +310,71 @@ following document:
<bar>def</bar>
</MyStructure>

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

<MyStructure xmlns:baz="http//foo.com">
<foo>abc</foo>
<baz:bar>def</baz:bar>
</MyStructure>

.. 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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,23 +33,32 @@
public final class XmlNamespaceTrait extends AbstractTrait implements ToSmithyBuilder<XmlNamespaceTrait> {
public static final ShapeId ID = ShapeId.from("smithy.api#xmlNamespace");

private static final List<String> XML_NAMESPACE_PROPERTIES = ListUtils.of("uri");
private static final String PREFIX = "prefix";
private static final String URI = "uri";
private static final List<String> 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<String> 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));
}

/**
Expand All @@ -59,14 +70,18 @@ public static Builder builder() {

@Override
public Builder toBuilder() {
return builder().sourceLocation(getSourceLocation()).uri(uri);
return builder()
.sourceLocation(getSourceLocation())
.uri(uri)
.prefix(prefix);
}

/**
* Builder used to create an XmlNamespace trait.
*/
public static final class Builder extends AbstractTraitBuilder<XmlNamespaceTrait, Builder> {
private String uri;
private String prefix;

private Builder() {}

Expand All @@ -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);
Expand All @@ -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();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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-]*)?$")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern update should be reflected in the xmlName trait spec's ABNF.

string xmlName

/// Adds an xmlns namespace definition URI to an XML element.
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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> 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"));
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down