diff --git a/docs/source/spec/aws/aws-cross-protocol.rst b/docs/source/spec/aws/aws-cross-protocol.rst new file mode 100644 index 00000000000..323f4e0a9ef --- /dev/null +++ b/docs/source/spec/aws/aws-cross-protocol.rst @@ -0,0 +1,46 @@ +.. _aws-cross-protocol: + +========================= +AWS cross-protocol traits +========================= + +This specification defines traits that are not restricted to use in a single +AWS protocol, but are specific to the AWS suite of protocols. + +.. contents:: Table of contents + :depth 2 + :local: + :backlinks: none + + +.. _aws.protocols#httpContentMd5-trait: + +-------------------------------------- +``aws.protocols#httpContentMd5`` trait +-------------------------------------- + +Summary + Indicates that an operation requires the Content-MD5 header set in its HTTP + request. +Trait selector + ``operation`` +Value type + Annotation trait. +See + :rfc:`1864` + +.. tabs:: + + .. code-tab:: smithy + + @httpContentMd5 + operation PutSomething { + input: PutSomethingInput, + output: PutSomethingOutput + } + +.. note:: + + While not specifically tied to a single AWS protocol, this trait is + currently only utilized by Amazon S3, which utilizes the + :ref:`aws-restxml-protocol`. diff --git a/docs/source/spec/aws/aws-restjson1-protocol.rst b/docs/source/spec/aws/aws-restjson1-protocol.rst index 8d8793c2913..2e4b165538d 100644 --- a/docs/source/spec/aws/aws-restjson1-protocol.rst +++ b/docs/source/spec/aws/aws-restjson1-protocol.rst @@ -187,7 +187,7 @@ Content-Type ------------ The ``aws.protocols#restJson1`` protocol uses a default Content-Type -of ``application/x-amz-json-1.1``. +of ``application/json``. Input or output shapes that apply the :ref:`httpPayload-trait` on one of their top-level members MUST use a Content-Type that is appropriate for @@ -203,15 +203,15 @@ with the ``httpPayload`` trait: - Content-Type * - Has :ref:`mediaType-trait` - Use the value of the ``mediaType`` trait if present. - * - string + * - ``string`` - ``text/plain`` - * - blob + * - ``blob`` - ``application/octet-stream`` - * - document + * - ``document`` - ``application/json`` - * - structure + * - ``structure`` - ``application/json`` - * - union + * - ``union`` - ``application/json`` @@ -225,55 +225,58 @@ JSON shape serialization * - Smithy type - JSON type - * - blob + * - ``blob`` - JSON ``string`` value that is base64 encoded. - * - boolean + * - ``boolean`` - JSON boolean - * - byte + * - ``byte`` - JSON number - * - short + * - ``short`` - JSON number - * - integer + * - ``integer`` - JSON number - * - long + * - ``long`` - JSON number - * - float + * - ``float`` - JSON number - * - double + * - ``double`` - JSON number - * - bigDecimal + * - ``bigDecimal`` - JSON number. Unfortunately, this protocol serializes bigDecimal shapes as a normal JSON number. Many JSON parsers will either truncate the value or be unable to parse numbers that exceed the size of a double. - * - bigInteger - - JSON number. Unfortunately, this protocol serializes digInteger + * - ``bigInteger`` + - JSON number. Unfortunately, this protocol serializes bigInteger shapes as a normal JSON number. Many JSON parsers will either truncate the value or be unable to parse numbers that exceed the size of a double. - * - string + * - ``string`` - JSON string - * - timestamp + * - ``timestamp`` - JSON number (default). This protocol uses ``epoch-seconds``, also known as Unix timestamps, in JSON payloads represented as a double. However, the :ref:`timestampFormat ` MAY be used to customize timestamp serialization. - * - document + * - ``document`` - Any JSON value - * - list + * - ``list`` - JSON array - * - map + * - ``set`` + - JSON array. A set is serialized identically as a ``list`` shape, + but only contains unique values. + * - ``map`` - JSON object - * - structure + * - ``structure`` - JSON object. Each member value provided for the structure is serialized as a JSON property where the property name is the same as the member name. The :ref:`jsonName-trait` can be used to serialize a property using a custom name. A null value MAY be provided or omitted for a :ref:`boxed ` member with no observable difference. - * - union - - JSON object. A union is serialized identically as a structure shape, - but only a single member can be set to a non-null value. + * - ``union`` + - JSON object. A union is serialized identically as a ``structure`` + shape, but only a single member can be set to a non-null value. -------------------------- @@ -281,19 +284,53 @@ HTTP binding serialization -------------------------- The ``aws.protocols#restJson1`` protocol supports all of the HTTP binding traits -defined in the `HTTP protocol bindings ` specification. The +defined in the :ref:`HTTP protocol bindings ` specification. The serialization formats and and behaviors described for each trait are supported as defined in the ``aws.protocols#restJson1`` protocol. +.. restJson1-errors: + +----------------------------- +Operation error serialization +----------------------------- + +Error responses in the ``restJson1`` protocol are serialized identically to +standard responses with one additional component to distinguish which error +is contained. The component MUST be one of the following: an additional header +with the name ``X-Amzn-Errortype``, a body field with the name ``code``, or a +body field named ``__type``. The value of this component SHOULD contain only +the :token:`shape name ` of the error's :ref:`shape-id`. + +Legacy server-side protocol implementations sometimes include additional +information in this value. New server-side protocol implementations SHOULD NOT +populate this value with anything but the shape name. All client-side +implementations SHOULD support sanitizing the value to retrieve the +disambiguated error type using the following steps: + +1. If a ``:`` character is present, then take only the contents **before** the + first ``:`` character in the value. +2. If a ``#`` character is present, then take only the contents **after** the + first ``#`` character in the value. + +All of the following error values resolve to ``FooError``: + +* ``FooError`` +* ``FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/`` +* ``aws.protocoltests.restjson#FooError`` +* ``aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/`` + + ------------------------- Protocol compliance tests ------------------------- A full compliance test suite is provided and SHALL be considered a normative -reference: https://github.com/awslabs/smithy/tree/master/smithy-aws-protocol-tests/model/rest-json +reference: https://github.com/awslabs/smithy/tree/master/smithy-aws-protocol-tests/model/awsJson1_1 These compliance tests define a model that is used to define test cases and the expected serialized HTTP requests and responses for each case. +*TODO: Add event stream handling specifications.* + .. _`Application-Layer Protocol Negotiation (ALPN) Protocol ID`: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids diff --git a/docs/source/spec/aws/aws-restxml-protocol.rst b/docs/source/spec/aws/aws-restxml-protocol.rst index 7c3c83504d4..cfecacb5ba6 100644 --- a/docs/source/spec/aws/aws-restxml-protocol.rst +++ b/docs/source/spec/aws/aws-restxml-protocol.rst @@ -24,9 +24,42 @@ Summary Trait selector ``service`` Value type - Annotation trait. -See - `Protocol tests `_ + Structure + +``aws.protocols#restXml`` is a structure that supports the following +members: + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - http + - ``[string]`` + - The priority ordered list of supported HTTP protocol versions. + * - eventStreamHttp + - ``[string]`` + - The priority ordered list of supported HTTP protocol versions + that are required when using :ref:`event streams ` + with the service. If not set, this value defaults to the value + of the ``http`` member. Any entry in ``eventStreamHttp`` MUST + also appear in ``http``. + * - noErrorWrapping + - ``boolean`` + - Disables the serialization wrapping of error properties in an + 'Error' XML element. See :ref:`operation error serialization ` + for more information. + +Each entry in ``http`` and ``eventStreamHttp`` SHOULD be a valid +`Application-Layer Protocol Negotiation (ALPN) Protocol ID`_ (for example, +``http/1.1``, ``h2``, etc). Clients SHOULD pick the first protocol in the +list they understand when connecting to a service. A client SHOULD assume +that a service supports ``http/1.1`` when no ``http`` or ``eventStreamHttp`` +values are provided. + +The following example defines a service that uses ``aws.protocols#restXml``. .. tabs:: @@ -38,7 +71,7 @@ See @restXml service MyService { - version: "2020-02-05" + version: "2020-04-02" } .. code-tab:: json @@ -48,7 +81,7 @@ See "shapes": { "smithy.example#MyService": { "type": "service", - "version": "2020-02-05", + "version": "2020-04-02", "traits": { "aws.protocols#restXml": {} } @@ -56,31 +89,291 @@ See } } +The following example defines a service that requires the use of +``h2`` when using event streams. -.. _aws.protocols#httpContentMd5-trait: +.. code-block:: smithy --------------------------------------- -``aws.protocols#httpContentMd5`` trait --------------------------------------- + namespace smithy.example -Summary - Indicates that an operation requires the Content-MD5 header set in its HTTP - request. -Trait selector - ``operation`` -Value type - Annotation trait. -See - :rfc:`1864` + use aws.protocols#restXml -.. tabs:: + @restXml(eventStreamHttp: ["h2"]) + service MyService { + version: "2020-04-02" + } - .. code-tab:: smithy +The following example defines a service that requires the use of +``h2`` or ``http/1.1`` when using event streams, where ``h2`` is +preferred over ``http/1.1``. - @httpContentMd5 - operation PutSomething { - input: PutSomethingInput, - output: PutSomethingOutput - } +.. code-block:: smithy + + namespace smithy.example + + use aws.protocols#restXml + + @restXml(eventStreamHttp: ["h2", "http/1.1"]) + service MyService { + version: "2020-04-02" + } + +The following example defines a service that requires the use of +``h2`` for all requests, including event streams. + +.. code-block:: smithy + + namespace smithy.example + + use aws.protocols#restXml + + @restXml(http: ["h2"]) + service MyService { + version: "2020-04-02" + } + + +---------------- +Supported traits +---------------- + +The ``aws.protocols#restXml`` protocol supports the following traits +that affect serialization: + +.. list-table:: + :header-rows: 1 + :widths: 20 80 + + * - Trait + - Description + * - :ref:`cors ` + - Indicates that the service supports CORS. + * - :ref:`endpoint ` + - Configures a custom operation endpoint. + * - :ref:`hostLabel ` + - Binds a top-level operation input structure member to a label in + the hostPrefix of an endpoint trait. + * - :ref:`http ` + - Configures the HTTP bindings of an operation. An operation that + does not define the ``http`` trait is ineligible for use with + this protocol. + * - :ref:`httpError ` + - A ``client`` error has a default status code of ``400``, and a + ``server`` error has a default status code of ``500``. The + ``httpError`` trait is used to define a custom status code. + * - :ref:`httpHeader ` + - Binds a top-level input, output, or error structure member to + an HTTP header instead of the payload. + * - :ref:`httpLabel ` + - Binds a top-level input structure member to a URI label instead + of the payload. + * - :ref:`httpPayload ` + - Binds a top-level input or output structure member as the payload + of a request or response. + * - :ref:`httpPrefixHeaders ` + - Binds a top-level input, output, or error member to a map of + prefixed HTTP headers. + * - :ref:`httpQuery ` + - Binds a top-level input structure member to a query string parameter. + * - :ref:`xmlAttrubute ` + - Serializes an object property as an XML attribute rather than a nested + XML element. + * - :ref:`xmlFlattened ` + - By default, entries in lists, sets, and maps have values serialized in + nested XML elements specific to their type. The ``xmlFlattened`` trait + unwraps these elements into the containing structure. + * - :ref:`xmlName ` + - By default, the XML element names used in serialized structures are + the same as a structure member name. The ``xmlName`` trait changes + the XML element name to a custom value. + * - :ref:`xmlNamespace ` + - Adds an xmlns namespace definition URI to XML element(s) generated + for the targeted shape. + * - :ref:`timestampFormat ` + - Defines a custom timestamp serialization format. + + +------------ +Content-Type +------------ + +The ``aws.protocols#restXml`` protocol uses a default Content-Type +of ``application/xml``. + +Input or output shapes that apply the :ref:`httpPayload-trait` on one of +their top-level members MUST use a Content-Type that is appropriate for +the payload. The following table defines the expected Content-Type header +for requests and responses based on the shape targeted by the member marked +with the ``httpPayload`` trait: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Targeted shape + - Content-Type + * - Has :ref:`mediaType-trait` + - Use the value of the ``mediaType`` trait if present. + * - ``string`` + - ``text/plain`` + * - ``blob`` + - ``application/octet-stream`` + * - ``document`` + - Undefined. Document shapes are not recommended for use in XML based + protocols. + * - ``structure`` + - ``application/xml`` + * - ``union`` + - ``application/xml`` + + +----------------------- +XML shape serialization +----------------------- + +XML requests and responses are serialized within an XML root node with the +name of the operation's input, output, or error shape that is being serialized. + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Smithy type + - XML entity + * - ``blob`` + - XML text node with a value that is base64 encoded. + * - ``boolean`` + - XML text node with a value either "true" or "false". + * - ``byte`` + - XML text node with a value of the number. + * - ``short`` + - XML text node with a value of the number. + * - ``integer`` + - XML text node with a value of the number. + * - ``long`` + - XML text node with a value of the number. + * - ``float`` + - XML text node with a value of the number. + * - ``double`` + - XML text node with a value of the number. + * - ``bigDecimal`` + - XML text node with a value of the number, using scientific notation if + an exponent is needed. Unfortunately, many XML parsers will either + truncate the value or be unable to parse numbers that exceed the size + of a double. + * - ``bigInteger`` + - XML text node with a value of the number, using scientific notation if + an exponent is needed. Unfortunately, many XML parsers will either + truncate the value or be unable to parse numbers that exceed the size + of a double. + * - ``string`` + - XML text node with an XML-safe, UTF-8 value of the string. + * - ``timestamp`` + - XML text node with a value of the timestamp. This protocol uses + ``date-time`` as the default serialization. However, the + :ref:`timestampFormat ` MAY be used to + customize timestamp serialization. + * - ``document`` + - Undefined. Document shapes are not recommended for use in XML based + protocols. + * - ``list`` + - XML element. Each value provided in the list is serialized as a nested + XML element with the name ``member``. The :ref:`xmlName-trait` can be + used to serialize a property using a custom name. The + :ref:`xmlFlattened-trait` can be used to unwrap the values into a + containing structure or union, with the value XML element using the + structure or union member name. + * - ``set`` + - XML element. A set is serialized identically as a ``list`` shape, + but only contains unique values. + * - ``map`` + - XML element. Each key-value pair provided in the map is serialized in + a nested XML element with the name ``entry`` that contains nested + elements ``key`` and ``value`` for the pair. The :ref:`xmlName-trait` + can be used to serialize key or value properties using a custom name, + it cannot be used to influence the ``entry`` name. The + :ref:`xmlFlattened-trait` can be used to unwrap the entries into a + containing structure or union, with the entry XML element using the + structure or union member name. + * - ``structure`` + - XML element. Each member value provided for the structure is + serialized as a nested XML element where the element name is the + same as the member name. The :ref:`xmlName-trait` can be used to + serialize a property using a custom name. The :ref:`xmlAttribute-trait` + can be used to serialize a property in an attribute of the containing + element. + * - ``union`` + - XML element. A union is serialized identically as a ``structure`` + shape, but only a single member can be set to a non-null value. + +.. important:: + + See :ref:`serializing-xml-shapes` for comprehensive documentation, + including examples and behaviors when using multiple XML traits. + + +-------------------------- +HTTP binding serialization +-------------------------- + +The ``aws.protocols#restXml`` protocol supports all of the HTTP binding traits +defined in the :ref:`HTTP protocol bindings ` specification. The +serialization formats and and behaviors described for each trait are supported +as defined in the ``aws.protocols#restXml`` protocol. + + +.. _restXml-errors: + +----------------------------- +Operation error serialization +----------------------------- + +Error responses in the ``restXml`` protocol are wrapped in one additional +nested XML element with the name ``Error`` by default. All error structure +members are serialized within this element, unless bound to another location +with HTTP protocol bindings. + +Serialized error shapes MUST also contain an additional child element ``Code`` +that contains only the :token:`shape name ` of the error's +:ref:`shape-id`. This can be used to distinguish which specific error has been +serialized in the response. + +.. code-block:: xml + + + + Sender + InvalidGreeting + Hi + setting + + foo-id + + +The ``noErrorWrapping`` setting on the ``restXml`` protocol trait disables +using this additional nested XML element. + +.. code-block:: xml + + + Sender + InvalidGreeting + Hi + setting + foo-id + + + +------------------------- +Protocol compliance tests +------------------------- + +A full compliance test suite is provided and SHALL be considered a normative +reference: https://github.com/awslabs/smithy/tree/master/smithy-aws-protocol-tests/model/restXml + +These compliance tests define a model that is used to define test cases and +the expected serialized HTTP requests and responses for each case. + +*TODO: Add event stream handling specifications.* -*TODO: Add specifications, protocol examples, etc.* +.. _`Application-Layer Protocol Negotiation (ALPN) Protocol ID`: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids diff --git a/docs/source/spec/aws/index.rst b/docs/source/spec/aws/index.rst index 419d61c4c07..197ee67147a 100644 --- a/docs/source/spec/aws/index.rst +++ b/docs/source/spec/aws/index.rst @@ -27,3 +27,4 @@ AWS Protocols aws-restxml-protocol aws-query-protocol aws-ec2-query-protocol + aws-cross-protocol diff --git a/docs/source/spec/core/xml-traits.rst b/docs/source/spec/core/xml-traits.rst index ce3c98e9b20..c31ff866c1b 100644 --- a/docs/source/spec/core/xml-traits.rst +++ b/docs/source/spec/core/xml-traits.rst @@ -14,6 +14,8 @@ shapes with XML based protocols. :backlinks: none +.. _serializing-xml-shapes: + ---------------------- Serializing XML shapes ---------------------- diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/protocols/RestXmlTrait.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/protocols/RestXmlTrait.java index 22e2ec791e1..1f7d178b050 100644 --- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/protocols/RestXmlTrait.java +++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/protocols/RestXmlTrait.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package software.amazon.smithy.aws.traits.protocols; import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.AbstractTrait; @@ -26,17 +27,55 @@ public final class RestXmlTrait extends AwsProtocolTrait { public static final ShapeId ID = ShapeId.from("aws.protocols#restXml"); + private final boolean noErrorWrapping; + private RestXmlTrait(Builder builder) { super(ID, builder); + this.noErrorWrapping = builder.noErrorWrapping; } public static Builder builder() { return new Builder(); } + /** + * @return Returns the noErrorWrapping setting. + */ + public boolean isNoErrorWrapping() { + return noErrorWrapping; + } + + @Override + protected Node createNode() { + ObjectNode node = super.createNode().expectObjectNode(); + + if (isNoErrorWrapping()) { + node = node.withMember("noErrorWrapping", Node.from(noErrorWrapping)); + } + + return node; + } + public static final class Builder extends AwsProtocolTrait.Builder { + private boolean noErrorWrapping = false; + private Builder() {} + public Builder noErrorWrapping(boolean noErrorWrapping) { + this.noErrorWrapping = noErrorWrapping; + return this; + } + + @Override + public Builder fromNode(Node node) { + Builder builder = super.fromNode(node); + + ObjectNode objectNode = node.expectObjectNode(); + builder.noErrorWrapping(objectNode.getBooleanMemberOrDefault("noErrorWrapping")); + + return builder; + } + @Override public RestXmlTrait build() { return new RestXmlTrait(this); 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 111e5aa2364..4fdf001e3a9 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 @@ -42,6 +42,13 @@ }, "eventStreamHttp": { "target": "aws.protocols#StringList" + }, + "noErrorWrapping": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "Disables the serialization wrapping of error properties in an 'Error' XML element.", + "smithy.api#deprecated": {} + } } }, "traits": { diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/protocols/RestXmlTraitTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/protocols/RestXmlTraitTest.java new file mode 100644 index 00000000000..d717c051313 --- /dev/null +++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/protocols/RestXmlTraitTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.aws.traits.protocols; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.model.traits.TraitFactory; + +public class RestXmlTraitTest { + + @Test + public void loadsTraitWithDefaults() { + Node node = Node.objectNode(); + TraitFactory provider = TraitFactory.createServiceFactory(); + Optional trait = provider.createTrait(RestXmlTrait.ID, ShapeId.from("ns.foo#foo"), node); + + assertTrue(trait.isPresent()); + assertThat(trait.get(), instanceOf(RestXmlTrait.class)); + RestXmlTrait restXmlTrait = (RestXmlTrait) trait.get(); + assertTrue(restXmlTrait.getHttp().isEmpty()); + assertTrue(restXmlTrait.getEventStreamHttp().isEmpty()); + assertFalse(restXmlTrait.isNoErrorWrapping()); + assertThat(restXmlTrait.createNode(), equalTo(node)); + } + + @Test + public void canSetNoErrorWrapping() { + Node node = Node.parse("{\"noErrorWrapping\":true}"); + TraitFactory provider = TraitFactory.createServiceFactory(); + Optional trait = provider.createTrait(RestXmlTrait.ID, ShapeId.from("ns.foo#foo"), node); + + assertTrue(trait.isPresent()); + RestXmlTrait restXmlTrait = (RestXmlTrait) trait.get(); + assertTrue(restXmlTrait.isNoErrorWrapping()); + assertThat(restXmlTrait.createNode(), equalTo(node)); + } +}