diff --git a/.gitignore b/.gitignore index 4b608569e63..ed1c10be16a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,5 +35,8 @@ build/ */*/out/ /wrapper +# jdt-ls +.metadata + # Smithy .smithy.lsp.log diff --git a/docs/source-2.0/additional-specs/index.rst b/docs/source-2.0/additional-specs/index.rst index cf3743324a3..e9c8638b4a6 100644 --- a/docs/source-2.0/additional-specs/index.rst +++ b/docs/source-2.0/additional-specs/index.rst @@ -15,6 +15,7 @@ start with ``smithy.*`` where "*" is anything other than ``api``. waiters mqtt rules-engine/index + protocols/index .. seealso:: diff --git a/docs/source-2.0/additional-specs/protocols/index.rst b/docs/source-2.0/additional-specs/protocols/index.rst new file mode 100644 index 00000000000..9d41bef0040 --- /dev/null +++ b/docs/source-2.0/additional-specs/protocols/index.rst @@ -0,0 +1,10 @@ +.. _protocols: + +========= +Protocols +========= + +.. toctree:: + :maxdepth: 1 + + smithy-rpc-v2 diff --git a/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst new file mode 100644 index 00000000000..2b2e4664bda --- /dev/null +++ b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst @@ -0,0 +1,484 @@ +.. _smithy-rpc-v2-cbor: + +=========================== +Smithy RPC v2 CBOR protocol +=========================== + +This specification defines the ``smithy.protocols#rpcv2Cbor`` protocol. This +protocol is used to expose services that serialize RPC payloads as CBOR. + +.. smithy-trait:: smithy.protocols#rpcv2Cbor +.. _smithy.protocols#rpcv2Cbor-trait: + + +------------------------------------ +``smithy.protocols#rpcv2Cbor`` trait +------------------------------------ + +Summary + Adds support for an RPC-based protocol over HTTP that sends requests and + responses with CBOR payloads. +Trait selector + ``service`` +Value type + Structure +See + `Protocol tests `_ + +``smithy.protocols#rpcv2Cbor`` 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``. + +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 +``smithy.protocols#rpcv2Cbor``. + +.. code-block:: smithy + + $version: "2" + + namespace smithy.example + + use smithy.protocols#rpcv2Cbor + + @rpcv2Cbor + service MyService { + version: "2020-07-02" + } + +The following example defines a service that requires the use of +``h2`` when using event streams. + +.. code-block:: smithy + + $version: "2" + + namespace smithy.example + + use smithy.protocols#rpcv2Cbor + + @rpcv2Cbor( + http: ["h2", "http/1.1"], + eventStreamHttp: ["h2"] + ) + service MyService { + version: "2020-02-05" + } + +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``. + +.. code-block:: smithy + + $version: "2" + + namespace smithy.example + + use smithy.protocols#rpcv2Cbor + + @rpcv2Cbor( + http: ["h2", "http/1.1"], + eventStreamHttp: ["h2", "http/1.1"] + ) + service MyService { + version: "2020-02-05" + } + +The following example defines a service that requires the use of +``h2`` for all requests, including event streams. + +.. code-block:: smithy + + $version: "2" + + namespace smithy.example + + use smithy.protocols#rpcv2Cbor + + @rpcv2Cbor(http: ["h2"]) + service MyService { + version: "2020-02-05" + } + + +---------------- +Supported traits +---------------- + +The ``smithy.protocols#rpcv2Cbor`` 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:`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:`requestCompression ` + - Indicates that an operation supports compressing requests from clients + to services. + + +------------------ +Protocol behaviors +------------------ + +Implementations of the ``smithy.protocols#rpcv2Cbor`` protocol comply with the +following rules: + +~~~~~~~~ +Requests +~~~~~~~~ + +Every request for the ``rpcv2Cbor`` protocol MUST contain a ``Smithy-Protocol`` +header with the value of ``rpc-v2-cbor``. + +Every request for the ``rpcv2Cbor`` protocol MUST be sent using the HTTP +``POST`` method. :ref:`HTTP binding traits ` MUST be ignored when +building ``rpcv2Cbor`` requests if they are present. + +Every request for the ``rpcv2Cbor`` protocol MUST be sent to a URL with the +following form: ``{prefix?}/service/{serviceName}/operation/{operationName}`` + +* The Smithy RPCv2 CBOR protocol will only use the last four segments of the + URL when routing requests. For example, a service could use a ``v1`` prefix + in the URL path, which would not affect the operation a request is routed + to: ``v1/service/FooService/operation/BarOperation`` +* The ``serviceName`` segment MUST be replaced by the :token:`shape name ` + of the service's :ref:`shape-id` in the Smithy model. The ``serviceName`` + produced by client implementations MUST NOT contain the namespace of the + ``service`` shape. Service implementations SHOULD accept an absolute shape + ID as the content of this segment with the ``#`` character replaced with a + ``.`` character, routing it the same as if only the name was specified. For + example, if the ``service``'s absolute shape ID is + ``com.example#TheService``, a service should accept both ``TheService`` and + ``com.example.TheService`` as values for the ``serviceName`` segment. +* The ``operationName`` segment MUST be replaced by the name of the ``operation`` + shape in the Smithy. The ``operationName`` produced by client + implementations MUST NOT contain the namespace of the ``operation`` shape. + Service implementations MUST NOT accept an absolute shape ID as the content + of this segment. + +Requests for the ``rpcv2Cbor`` protocol MUST use the following behavior for +setting a ``Content-Type`` header: + +* Buffered RPC requests: the value of the ``Content-Type`` header MUST be + ``application/cbor``. +* Event streaming requests: the value of the ``Content-Type`` header MUST be + ``application/vnd.amazon.eventstream``. +* Requests for operations with no defined input type (as in, they target the + ``Unit`` shape) MUST NOT contain bodies in their HTTP requests. The + ``Content-Type`` for the serialization format MUST NOT be set. + +Requests for the ``rpcv2Cbor`` protocol MUST NOT contain an ``X-Amz-Target`` or +``X-Amzn-Target`` header. An ``rpcv2Cbor`` request is malformed if it contains +either of these headers. Server-side implementations MUST reject such requests +for security reasons. + +Buffered RPC requests for the ``rpcv2Cbor`` protocol SHOULD include a +``Content-Length`` header. Event streaming requests MUST NOT specify a content +length (instead using ``Transfer-Encoding: chunked`` on HTTP/1.1). + +Event streaming requests for the ``rpcv2Cbor`` protocol MUST include an +``Accept`` header set to the value ``application/vnd.amazon.eventstream``. +Other forms of content streaming MAY be added in the future, utilizing +different values for ``Accept``. + +In summary, the ``rpcv2Cbor`` protocol defines behavior for the following +headers for requests: + +.. list-table:: + :header-rows: 1 + :widths: 18 18 64 + + * - Header + - Status + - Description + * - ``Smithy-Protocol`` + - Required + - The value of ``rpc-v2-cbor``. + * - ``Content-Type`` + - Required with request bodies + - The value of ``application/cbor``. For event streaming requests, this + is ``application/vnd.amazon.eventstream``. + * - ``Content-Length`` + - Conditional + - The standard ``Content-Length`` header defined by :rfc:`7230#section-3.3.2`. + For event streaming requests, this MUST NOT be set. + * - ``Accept`` + - Conditional + - For event streaming requests, to the value ``application/vnd.amazon.eventstream``. + + +~~~~~~~~~ +Responses +~~~~~~~~~ + +The status code for successful ``rpcv2Cbor`` responses MUST be ``200``. + +The status code for error ``rpcv2Cbor`` MUST be determined by the following +steps: + +1. If the :ref:`httpError ` trait is set on the error shape, + its value is used. +2. If the :ref:`error ` trait is set to ``server``, the value + MUST be ``500``. +3. Otherwise, the value MUST be ``400``. + +Every response for the ``rpcv2Cbor`` protocol MUST contain a ``Smithy-Protocol`` +header with the value of ``rpc-v2-cbor``. The response MUST match the value of +the ``Smithy-Protocol`` header from the request. + +Responses for the ``rpcv2Cbor`` protocol MUST use the following behavior for +setting a ``Content-Type`` header: + +* Buffered RPC responses: the value of the ``Content-Type`` header MUST be + ``application/cbor``. +* Event streaming responses: the value of the ``Content-Type`` header MUST be + ``application/vnd.amazon.eventstream``. +* Responses for operations with no defined output type (as in, they target the + ``Unit`` shape) MUST NOT contain bodies in their HTTP responses. The + ``Content-Type`` for the serialization format MUST NOT be set. + +Buffered RPC responses for the ``rpcv2Cbor`` protocol SHOULD include a +``Content-Length`` header. Event streaming responses SHOULD NOT specify a +content length (instead using ``Transfer-Encoding: chunked`` on HTTP/1.1). + +Responses for the ``rpcv2Cbor`` protocol SHOULD NOT contain the +``X-Amzn-ErrorType`` header. Type information is always serialized in the +payload. Clients MUST ignore this header. See `Operation error serialization`_ +for information on the serialization of error responses. + +In summary, the ``rpcv2Cbor`` protocol defines behavior for the following +headers for responses: + +.. list-table:: + :header-rows: 1 + :widths: 18 18 64 + + * - Header + - Status + - Description + * - ``Smithy-Protocol`` + - Required + - The value of ``rpc-v2-cbor``. + * - ``Content-Type`` + - Required with request bodies + - The value of ``application/cbor``. For event streaming requests, this + is ``application/vnd.amazon.eventstream``. + * - ``Content-Length`` + - Conditional + - The standard ``Content-Length`` header defined by :rfc:`7230#section-3.3.2`. + For event streaming requests, this SHOULD NOT be set. + + +------------------- +Shape serialization +------------------- + +The ``smithy.protocols#rpcv2Cbor`` protocol serializes all shapes into a CBOR +document body with no HTTP bindings supported. The following table shows how +to convert each shape type: + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Smithy type + - CBOR representation + * - ``blob`` + - :rfc:`Major type 2 (byte string) <8949#section-3.1-2.5>` + * - ``boolean`` + - :rfc:`Major type 7 (simple) <8949#section-3.1-2.15>` + * - ``byte`` + - :rfc:`Major type 0 (unsigned integer) <8949#section-3.1-2.1>` or + :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>` + * - ``short`` + - :rfc:`Major type 0 (unsigned integer) <8949#section-3.1-2.1>` or + :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>` + * - ``integer`` + - :rfc:`Major type 0 (unsigned integer) <8949#section-3.1-2.1>` or + :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>` + * - ``long`` + - :rfc:`Major type 0 (unsigned integer) <8949#section-3.1-2.1>` or + :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>` + * - ``float`` + - :rfc:`Major type 7 (float) <8949#section-3.1-2.15>` + * - ``double`` + - :rfc:`Major type 7 (float) <8949#section-3.1-2.15>` + * - ``bigDecimal`` + - :rfc:`Major type 6 <8949#section-3.1-2.13>`, value + :rfc:`tags 2 (unsigned bignum) or 3 (negative bignum) <8949#section-3.4>` + * - ``bigInteger`` + - :rfc:`Major type 6 <8949#section-3.1-2.13>`, value + :rfc:`tag 4 (decimal fraction) <8949#section-3.4>` + * - ``string`` + - :rfc:`Major type 3 (text string) <8949#section-3.1-2.7>` + * - ``timestamp`` + - See `Timestamp type serialization`_. + * - ``document`` + - Document types are not currently supported in this protocol. + * - ``list`` + - :rfc:`Major type 4 (array of data items) <8949#section-3.1-2.9>` + * - ``map`` + - :rfc:`Major type 5 (map of pairs of data items) <8949#section-3.1-2.11>`, + each key/value pair is a pair in the type + * - ``structure`` + - :rfc:`Major type 5 (map of pairs of data items) <8949#section-3.1-2.11>`, + each member is the key to a pair in the type + * - ``union`` + - :rfc:`Major type 5 (map of pairs of data items) <8949#section-3.1-2.11>`, + each member is the key to a pair in the type. + + A union is serialized identically as a ``structure`` + shape, but only a single member can be set to a non-null value. + Deserializers MUST ignore an unrecognized ``__type`` member if present. + +Values that are ``null`` MUST be omitted from wire contents where not subject +to `default value serialization`_ rules. + +If an implementation does not support arbitrary precision (``bigInteger`` and +``bigDecimal`` Smithy types), it MUST fail when attempting to deserialize a +value of that type. + +.. note:: + The ``undefined`` CBOR value, CBOR :rfc:`major type 7 <8949#section-3.1-2.15>` + :rfc:`value 23 <8949#section-3.3>`, is not supported. Clients SHOULD NOT + serialize to this CBOR value and SHOULD deserialize this CBOR value to + ``null``. Servers MUST NOT serialize to this CBOR value and MUST + deserialize this CBOR value to ``null``. + +~~~~~~~~~~~~~~~~~~~~~~~~~~ +Numeric type serialization +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Numeric types (``byte``, ``short``, ``integer``, ``long``, ``float``, and ``double``) +SHOULD be serialized into the smallest possible CBOR type representation that +can contain its value. For example, a field modeled as a ``long`` with a value of +1 should be sent in a single byte. + +Floating-point numeric types (``float`` and ``double``) MAY be serialized into +non-floating-point numeric types (``byte``, ``short``, ``integer``, and ``long``) if +and only if the conversion would not cause a loss of precision. For example, a +field modeled as a ``float`` with a value of 256 may be sent as an integer. + +As support for half-precision floating-point values is inconsistent across +implementations, floating-point numeric types SHOULD NOT serialize into a +half-precision (16 bit) numeric CBOR values. Implementations MUST be capable of +deserializing half-precision numeric CBOR values (including -Infinity, +Infinity, and NAN) into their Smithy type representation. + +Numeric types MUST be deserialized into their Smithy type representation. For +example, a field modeled as a ``long`` with a single byte value of 1 must be +deserialized into a long. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Timestamp type serialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Timestamps are serialized as :rfc:`major type 6 <8949#section-3.1-2.13>`, value +:rfc:`tag 1 (epoch-based date/time) <8949#section-3.4>` to indicate the tagged +value is an epoch timestamp. Values are either :rfc:`major type 0 (unsigned integer) <8949#section-3.1-2.1>` +or :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>` for integer +values or :rfc:`major type 7 <8949#section-3.1-2.15>`, +:rfc:`value 25 (half-precision float), 26 (single-precision float), or 27 (double-precision float) <8949#section-3.3>` +for floating-point values. + +As support for half-precision floating-point values is inconsistent across +implementations, timestamp types SHOULD NOT serialize into a half-precision +(16 bit) numeric CBOR value for the tagged value. + +This protocol uses ``epoch-seconds``, also known as Unix timestamps, with +millisecond (1/1000th of a second) resolution. The :ref:`timestampFormat ` +MUST NOT be respected to customize timestamp serialization. + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Default value serialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To avoid information disclosure, service serializers SHOULD omit the default +value of structure members that are marked with the :ref:`internal trait `. + +Client deserializers SHOULD attempt to error correct structures that omit a +:ref:`required ` member by filling in a default zero value for +the member. Error correction allows clients to continue to function while the +server is in error. + +---------------------------------- +Operation response deserialization +---------------------------------- + +Clients MUST use the following rules to interpret responses and determine how +to deserialize them: + +1. If the response does not have the same ``Smithy-Protocol`` header as the + request, it MUST be considered malformed. Clients who receive a malformed + response MUST handle it (i.e. throw a reasonable exception) based solely on + the HTTP response code. No attempt should be made to interpret the response + body or headers. +2. If the response code is ``200``, the request is successful and the response + payload SHALL be deserialized as the ``output`` shape defined on the + operation. +3. Finally, if the response code is not ``200``, the response payload is an + exception and SHALL be deserialized according to `Operation error serialization`_ + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Operation error serialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Error responses in the ``rpcv2Cbor`` protocol MUST be serialized identically to +standard responses with one additional component to distinguish which error is +contained: a body field named ``__type``. This value of this component MUST +contain the error’s absolute :ref:`shape-id`. + +By default, all error shapes have a ``message`` field containing an +error-specific message meant for human consumers of API errors. Services MUST +support generating this field and serializing it when responding. Clients MUST +support generating this field and deserializing it from responses. + +The ``Code`` response body field and ``code`` response body field MUST NOT be +used to distinguish which error is contained in a response. + +Clients who receive a malformed error response MUST handle it based solely on +the HTTP response code (i.e. throw a reasonable exception). No attempt should +be made to interpret the response body or headers. + +.. include:: ../../aws/protocols/error-rename-simple.rst.template + +.. _`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-2.0/aws/protocols/aws-query-protocol.rst b/docs/source-2.0/aws/protocols/aws-query-protocol.rst index 2ccc4574ce3..b917034423a 100644 --- a/docs/source-2.0/aws/protocols/aws-query-protocol.rst +++ b/docs/source-2.0/aws/protocols/aws-query-protocol.rst @@ -549,19 +549,20 @@ The following example defines an error that uses a custom "Code" of Summary When using the :ref:`awsQuery ` protocol, - custom ``Code`` and ``HTTP response code`` values can be defined for an error response via - the :ref:`awsQueryError ` trait. - - The ``awsQueryCompatible`` trait allows services to backward compatibly migrate from ``awsQuery`` to - :ref:`awsJson1_0 ` without removing values defined in the ``awsQueryError`` trait. - - This trait adds the ``x-amzn-query-error`` header in the form of ``Code;Fault`` to error responses. - ``Code`` is the value defined in the :ref:`awsQueryError `, - and ``Fault`` is one of ``Sender`` or ``Receiver``. - + custom ``Code`` and ``HTTP response code`` values can be defined for an + error response via the :ref:`awsQueryError ` + trait. + + The ``awsQueryCompatible`` trait allows services to backward compatibly + migrate from ``awsQuery`` to :ref:`awsJson1_0 ` + without removing values defined in the ``awsQueryError`` trait. + + This trait adds the ``x-amzn-query-error`` header in the form of + ``Code;Fault`` to error responses. ``Code`` is the value defined in the + :ref:`awsQueryError `, and ``Fault`` is + one of ``Sender`` or ``Receiver``. Trait selector ``service [trait|awsJson1_0]`` - Value type Annotation trait @@ -587,6 +588,11 @@ Value type message: String } +.. important:: + + AWS client implementations of the ``smithy.protocols#rpcv2Cbor`` protocol + MUST support the ``aws.protocols#awsQueryCompatible`` trait. + .. _awsQuery-compliance-tests: diff --git a/settings.gradle b/settings.gradle index bc0abdddc09..9605f4c7e0d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,3 +34,5 @@ include ":smithy-smoke-test-traits" include ":smithy-syntax" include ":smithy-aws-endpoints" include ":smithy-aws-smoke-test-model" +include ":smithy-protocol-traits" +include ":smithy-protocol-tests" diff --git a/smithy-aws-protocol-tests/build.gradle b/smithy-aws-protocol-tests/build.gradle index 2a4b95bb3ab..279f4b8a122 100644 --- a/smithy-aws-protocol-tests/build.gradle +++ b/smithy-aws-protocol-tests/build.gradle @@ -27,6 +27,7 @@ ext { dependencies { implementation project(path: ":smithy-cli", configuration: "shadow") implementation project(":smithy-protocol-test-traits") + implementation project(":smithy-protocol-traits") implementation project(":smithy-aws-traits") api project(":smithy-validation-model") } diff --git a/smithy-aws-protocol-tests/model/shared-types.smithy b/smithy-aws-protocol-tests/model/shared-types.smithy index 4e88e464e81..b4e31ba4d69 100644 --- a/smithy-aws-protocol-tests/model/shared-types.smithy +++ b/smithy-aws-protocol-tests/model/shared-types.smithy @@ -55,6 +55,10 @@ list NestedStringList { member: StringList, } +list ShortList { + member: Short, +} + list IntegerList { member: Integer, } @@ -64,6 +68,10 @@ list IntegerSet { member: Integer, } +list FloatList { + member: Float, +} + list DoubleList { member: Double, } @@ -81,11 +89,19 @@ list TimestampList { member: Timestamp, } +list BlobList { + member: Blob, +} + @uniqueItems list BlobSet { member: Blob, } +list ByteList { + member: Byte, +} + @uniqueItems list ByteSet { member: Byte, @@ -95,6 +111,11 @@ list ShortSet { member: Short, } +@uniqueItems +list LongList { + member: Long, +} + @uniqueItems list LongSet { member: Long, @@ -105,6 +126,10 @@ list TimestampSet { member: Timestamp, } +list DateTimeList { + member: DateTime, +} + @uniqueItems list DateTimeSet { member: DateTime, @@ -192,10 +217,10 @@ list IntegerEnumList { @uniqueItems list IntegerEnumSet { - member: IntegerEnum + member: IntegerEnum } map IntegerEnumMap { - key: String, - value: IntegerEnum + key: String, + value: IntegerEnum } 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 0c526122766..f4cc38035ea 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 @@ -309,6 +309,7 @@ structure httpApiKeyAuth { /// /// `traitValidators` is a map of validation event IDs to validators to apply to a shape. /// Selectors are used to identify shapes that are incompatible with a constrained trait. +/// The shape with the targeted trait applied MUST be a valid entry point for the selector. /// /// The following example defines a protocol that does not support document types. Each matching member found in the /// closure of an attached shape emits a validation event: @@ -317,7 +318,7 @@ structure httpApiKeyAuth { /// @trait(selector: "service") /// @traitValidators( /// "myCustomProtocol.NoDocuments": { -/// selector: "member :test(> document)" +/// selector: "~> member :test(> document)" /// message: "This protocol does not support document types" /// } /// ) diff --git a/smithy-protocol-tests/build.gradle b/smithy-protocol-tests/build.gradle new file mode 100644 index 00000000000..fb711a704e4 --- /dev/null +++ b/smithy-protocol-tests/build.gradle @@ -0,0 +1,28 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + id "software.amazon.smithy.gradle.smithy-jar" version "$smithyGradleVersion" +} + +description = "Defines protocol tests for Smithy HTTP protocols." + +ext { + displayName = "Smithy :: Protocol Tests" + moduleName = "software.amazon.smithy.protocoltests" +} + +dependencies { + implementation project(path: ":smithy-cli", configuration: "shadow") + implementation project(":smithy-protocol-test-traits") + implementation project(":smithy-protocol-traits") + api project(":smithy-validation-model") +} + +tasks["sourcesJar"].dependsOn("smithyJarStaging") + +smithy { + format.set(false) +} diff --git a/smithy-protocol-tests/model/rpcv2Cbor/cbor-lists.smithy b/smithy-protocol-tests/model/rpcv2Cbor/cbor-lists.smithy new file mode 100644 index 00000000000..c87ebb3bbba --- /dev/null +++ b/smithy-protocol-tests/model/rpcv2Cbor/cbor-lists.smithy @@ -0,0 +1,412 @@ +// This file defines test cases that serialize lists in JSON documents. + +$version: "2.0" + +namespace smithy.protocoltests.rpcv2Cbor + +use smithy.protocoltests.shared#BooleanList +use smithy.protocoltests.shared#BlobList +use smithy.protocoltests.shared#FooEnumList +use smithy.protocoltests.shared#IntegerEnumList +use smithy.protocoltests.shared#IntegerList +use smithy.protocoltests.shared#NestedStringList +use smithy.protocoltests.shared#SparseStringList +use smithy.protocoltests.shared#SparseStringMap +use smithy.protocoltests.shared#StringList +use smithy.protocoltests.shared#StringSet +use smithy.protocoltests.shared#TimestampList +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests +use smithy.framework#ValidationException + +/// This test case serializes JSON lists for the following cases for both +/// input and output: +/// +/// 1. Normal lists. +/// 2. Normal sets. +/// 3. Lists of lists. +/// 4. Lists of structures. +@idempotent +operation RpcV2CborLists { + input: RpcV2CborListInputOutput, + output: RpcV2CborListInputOutput, + errors: [ValidationException] +} + +apply RpcV2CborLists @httpRequestTests([ + { + id: "RpcV2CborLists", + documentation: "Serializes RpcV2 Cbor lists", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborLists", + body: "v2pzdHJpbmdMaXN0gmNmb29jYmFyaXN0cmluZ1NldIJjZm9vY2JhcmtpbnRlZ2VyTGlzdIIBAmtib29sZWFuTGlzdIL19G10aW1lc3RhbXBMaXN0gsH7QdTX+/OAAADB+0HU1/vzgAAAaGVudW1MaXN0gmNGb29hMGtpbnRFbnVtTGlzdIIBAnBuZXN0ZWRTdHJpbmdMaXN0goJjZm9vY2JhcoJjYmF6Y3F1eG1zdHJ1Y3R1cmVMaXN0gqJhYWExYWJhMqJhYWEzYWJhNGhibG9iTGlzdIJDZm9vQ2Jhcv8=" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "stringList": [ + "foo", + "bar" + ], + "stringSet": [ + "foo", + "bar" + ], + "integerList": [ + 1, + 2 + ], + "booleanList": [ + true, + false + ], + "timestampList": [ + 1398796238, + 1398796238 + ], + "enumList": [ + "Foo", + "0" + ], + "intEnumList": [ + 1, + 2 + ], + "nestedStringList": [ + [ + "foo", + "bar" + ], + [ + "baz", + "qux" + ] + ], + "structureList": [ + { + "a": "1", + "b": "2" + }, + { + "a": "3", + "b": "4" + } + ], + "blobList" : [ + "foo", + "bar" + ] + } + }, + { + id: "RpcV2CborListsEmpty", + documentation: "Serializes empty JSON lists", + tags: ["client-indefinite"] + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborLists", + body: "v2pzdHJpbmdMaXN0n///", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: [] + } + }, + { + id: "RpcV2CborListsEmptyUsingDefiniteLength", + documentation: "Serializes empty JSON definite length lists", + tags: ["client-definite"] + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborLists", + body: "oWpzdHJpbmdMaXN0gA==" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: [] + } + }, + { + id: "RpcV2CborIndefiniteStringInsideIndefiniteList", + documentation: "Can deserialize indefinite length text strings inside an indefinite length list", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborLists", + body: "v2pzdHJpbmdMaXN0n394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n//8=" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: ["An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string"] + } + appliesTo: "server" + }, + { + id: "RpcV2CborIndefiniteStringInsideDefiniteList", + documentation: "Can deserialize indefinite length text strings inside a definite length list", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborLists", + body: "oWpzdHJpbmdMaXN0g394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: ["An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string"] + }, + appliesTo: "server" + } +]) + +apply RpcV2CborLists @httpResponseTests([ + { + id: "RpcV2CborLists", + documentation: "Serializes RpcV2 Cbor lists", + protocol: rpcv2Cbor, + code: 200, + body: "v2pzdHJpbmdMaXN0n2Nmb29jYmFy/2lzdHJpbmdTZXSfY2Zvb2NiYXL/a2ludGVnZXJMaXN0nwEC/2tib29sZWFuTGlzdJ/19P9tdGltZXN0YW1wTGlzdJ/B+0HU1/vzgAAAwftB1Nf784AAAP9oZW51bUxpc3SfY0Zvb2Ew/2tpbnRFbnVtTGlzdJ8BAv9wbmVzdGVkU3RyaW5nTGlzdJ+fY2Zvb2NiYXL/n2NiYXpjcXV4//9tc3RydWN0dXJlTGlzdJ+/YWFhMWFiYTL/v2FhYTNhYmE0//9oYmxvYkxpc3SfQ2Zvb0NiYXL//w==" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "stringList": [ + "foo", + "bar" + ], + "stringSet": [ + "foo", + "bar" + ], + "integerList": [ + 1, + 2 + ], + "booleanList": [ + true, + false + ], + "timestampList": [ + 1398796238, + 1398796238 + ], + "enumList": [ + "Foo", + "0" + ], + "intEnumList": [ + 1, + 2 + ], + "nestedStringList": [ + [ + "foo", + "bar" + ], + [ + "baz", + "qux" + ] + ], + "structureList": [ + { + "a": "1", + "b": "2" + }, + { + "a": "3", + "b": "4" + } + ], + "blobList": [ + "foo", + "bar" + ] + } + }, + { + id: "RpcV2CborListsEmpty", + documentation: "Serializes empty RpcV2 Cbor lists", + protocol: rpcv2Cbor, + code: 200, + body: "v2pzdHJpbmdMaXN0n///", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: [] + } + }, + { + id: "RpcV2CborIndefiniteStringInsideIndefiniteListCanDeserialize", + documentation: "Can deserialize indefinite length text strings inside an indefinite length list", + protocol: rpcv2Cbor, + code: 200, + body: "v2pzdHJpbmdMaXN0n394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n//8=" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: ["An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string"] + } + appliesTo: "client" + }, + { + id: "RpcV2CborIndefiniteStringInsideDefiniteListCanDeserialize", + documentation: "Can deserialize indefinite length text strings inside a definite length list", + protocol: rpcv2Cbor, + code: 200, + body: "oWpzdHJpbmdMaXN0g394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: ["An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string"] + }, + appliesTo: "client" + } +]) + +structure RpcV2CborListInputOutput { + stringList: StringList, + + stringSet: StringSet, + + integerList: IntegerList, + + booleanList: BooleanList, + + timestampList: TimestampList, + + enumList: FooEnumList, + + intEnumList: IntegerEnumList, + + nestedStringList: NestedStringList, + + structureList: StructureList + + blobList: BlobList +} + +list StructureList { + member: StructureListMember, +} + +structure StructureListMember { + a: String, + b: String, +} + + +@httpRequestTests([ + { + id: "RpcV2CborSparseMapsSerializeNullValues" + documentation: "Serializes null values in maps" + protocol: rpcv2Cbor + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + body: "v29zcGFyc2VTdHJpbmdNYXC/Y2Zvb/b//w==" + params: { + "sparseStringMap": { + "foo": null + } + } + method: "POST" + uri: "/service/RpcV2Protocol/operation/SparseNullsOperation", + }, + { + id: "RpcV2CborSparseListsSerializeNull" + documentation: "Serializes null values in lists" + protocol: rpcv2Cbor + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + body: "v3BzcGFyc2VTdHJpbmdMaXN0n/b//w==" + params: { + "sparseStringList": [ + null + ] + } + method: "POST" + uri: "/service/RpcV2Protocol/operation/SparseNullsOperation", + } +]) +@httpResponseTests([ + { + id: "RpcV2CborSparseMapsDeserializeNullValues" + documentation: "Deserializes null values in maps" + protocol: rpcv2Cbor, + code: 200, + body: "v29zcGFyc2VTdHJpbmdNYXC/Y2Zvb/b//w==" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseStringMap": { + "foo": null + } + } + } + { + id: "RpcV2CborSparseListsDeserializeNull" + documentation: "Deserializes null values in lists" + protocol: rpcv2Cbor, + code: 200, + body: "v3BzcGFyc2VTdHJpbmdMaXN0n/b//w==" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseStringList": [ + null + ] + } + } +]) +operation SparseNullsOperation { + input: SparseNullsOperationInputOutput + output: SparseNullsOperationInputOutput +} + +structure SparseNullsOperationInputOutput { + sparseStringList: SparseStringList + sparseStringMap: SparseStringMap +} diff --git a/smithy-protocol-tests/model/rpcv2Cbor/cbor-maps.smithy b/smithy-protocol-tests/model/rpcv2Cbor/cbor-maps.smithy new file mode 100644 index 00000000000..ba21ff62a12 --- /dev/null +++ b/smithy-protocol-tests/model/rpcv2Cbor/cbor-maps.smithy @@ -0,0 +1,471 @@ +$version: "2.0" + +namespace smithy.protocoltests.rpcv2Cbor + +use smithy.protocoltests.shared#FooEnumMap +use smithy.protocoltests.shared#GreetingStruct +use smithy.protocoltests.shared#SparseStringMap +use smithy.protocoltests.shared#StringSet +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests +use smithy.protocols#rpcv2Cbor +use smithy.framework#ValidationException + +/// The example tests basic map serialization. +operation RpcV2CborDenseMaps { + input: RpcV2CborDenseMapsInputOutput, + output: RpcV2CborDenseMapsInputOutput, + errors: [ValidationException] +} + +apply RpcV2CborDenseMaps @httpRequestTests([ + { + id: "RpcV2CborMaps", + documentation: "Serializes maps", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborDenseMaps", + body: "oW5kZW5zZVN0cnVjdE1hcKJjZm9voWJoaWV0aGVyZWNiYXqhYmhpY2J5ZQ==" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseStructMap": { + "foo": { + "hi": "there" + }, + "baz": { + "hi": "bye" + } + } + } + }, + { + id: "RpcV2CborSerializesZeroValuesInMaps", + documentation: "Ensure that 0 and false are sent over the wire in all maps and lists", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborDenseMaps", + body: "om5kZW5zZU51bWJlck1hcKFheABvZGVuc2VCb29sZWFuTWFwoWF49A==", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseNumberMap": { + "x": 0 + }, + "denseBooleanMap": { + "x": false + } + } + }, + { + id: "RpcV2CborSerializesDenseSetMap", + documentation: "A request that contains a dense map of sets.", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborDenseMaps", + body: "oWtkZW5zZVNldE1hcKJheIBheYJhYWFi", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + }, +]) + +apply RpcV2CborDenseMaps @httpResponseTests([ + { + id: "RpcV2CborMaps", + documentation: "Deserializes maps", + protocol: rpcv2Cbor, + code: 200, + body: "oW5kZW5zZVN0cnVjdE1hcKJjZm9voWJoaWV0aGVyZWNiYXqhYmhpY2J5ZQ==", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseStructMap": { + "foo": { + "hi": "there" + }, + "baz": { + "hi": "bye" + } + } + } + }, + { + id: "RpcV2CborDeserializesZeroValuesInMaps", + documentation: "Ensure that 0 and false are sent over the wire in all maps and lists", + protocol: rpcv2Cbor, + code: 200, + body: "om5kZW5zZU51bWJlck1hcKFheABvZGVuc2VCb29sZWFuTWFwoWF49A==", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseNumberMap": { + "x": 0 + }, + "denseBooleanMap": { + "x": false + } + } + }, + { + id: "RpcV2CborDeserializesDenseSetMap", + documentation: "A response that contains a dense map of sets", + protocol: rpcv2Cbor, + code: 200, + body: "oWtkZW5zZVNldE1hcKJheIBheYJhYWFi", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + }, + { + id: "RpcV2CborDeserializesDenseSetMapAndSkipsNull", + documentation: """ + Clients SHOULD tolerate seeing a null value in a dense map, and they SHOULD + drop the null key-value pair.""", + protocol: rpcv2Cbor, + appliesTo: "client", + code: 200, + body: "oWtkZW5zZVNldE1hcKNheIBheYJhYWFiYXr2", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseSetMap": { + "x": [], + "y": ["a", "b"], + "z": null + } + } + } +]) + +structure RpcV2CborDenseMapsInputOutput { + denseStructMap: DenseStructMap, + denseNumberMap: DenseNumberMap, + denseBooleanMap: DenseBooleanMap, + denseStringMap: DenseStringMap, + denseSetMap: DenseSetMap, +} + +map DenseStructMap { + key: String, + value: GreetingStruct +} + +map DenseBooleanMap { + key: String, + value: Boolean +} + +map DenseNumberMap { + key: String, + value: Integer +} + +map DenseStringMap { + key: String, + value: String +} + +map DenseSetMap { + key: String, + value: StringSet +} + + +apply RpcV2CborSparseMaps @httpRequestTests([ + { + id: "RpcV2CborSparseMaps", + documentation: "Serializes sparse maps", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps", + body: "v29zcGFyc2VTdHJ1Y3RNYXC/Y2Zvb79iaGlldGhlcmX/Y2Jher9iaGljYnll////", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseStructMap": { + "foo": { + "hi": "there" + }, + "baz": { + "hi": "bye" + } + } + } + }, + { + id: "RpcV2CborSerializesNullMapValues", + documentation: "Serializes null map values in sparse maps", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps", + body: "v3BzcGFyc2VCb29sZWFuTWFwv2F49v9vc3BhcnNlTnVtYmVyTWFwv2F49v9vc3BhcnNlU3RyaW5nTWFwv2F49v9vc3BhcnNlU3RydWN0TWFwv2F49v//" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseBooleanMap": { + "x": null + }, + "sparseNumberMap": { + "x": null + }, + "sparseStringMap": { + "x": null + }, + "sparseStructMap": { + "x": null + } + } + }, + { + id: "RpcV2CborSerializesSparseSetMap", + documentation: "A request that contains a sparse map of sets", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps", + body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL///8=" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + }, + { + id: "RpcV2CborSerializesSparseSetMapAndRetainsNull", + documentation: "A request that contains a sparse map of sets.", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps", + body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL/YXr2//8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseSetMap": { + "x": [], + "y": ["a", "b"], + "z": null + } + } + }, + { + id: "RpcV2CborSerializesZeroValuesInSparseMaps", + documentation: "Ensure that 0 and false are sent over the wire in all maps and lists", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps", + body: "v29zcGFyc2VOdW1iZXJNYXC/YXgA/3BzcGFyc2VCb29sZWFuTWFwv2F49P//" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseNumberMap": { + "x": 0 + }, + "sparseBooleanMap": { + "x": false + } + } + } +]) + +apply RpcV2CborSparseMaps @httpResponseTests([ + { + id: "RpcV2CborSparseJsonMaps", + documentation: "Deserializes sparse maps", + protocol: rpcv2Cbor, + code: 200, + body: "v29zcGFyc2VTdHJ1Y3RNYXC/Y2Zvb79iaGlldGhlcmX/Y2Jher9iaGljYnll////", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseStructMap": { + "foo": { + "hi": "there" + }, + "baz": { + "hi": "bye" + } + } + } + }, + { + id: "RpcV2CborDeserializesNullMapValues", + documentation: "Deserializes null map values", + protocol: rpcv2Cbor, + code: 200, + body: "v3BzcGFyc2VCb29sZWFuTWFwv2F49v9vc3BhcnNlTnVtYmVyTWFwv2F49v9vc3BhcnNlU3RyaW5nTWFwv2F49v9vc3BhcnNlU3RydWN0TWFwv2F49v//" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseBooleanMap": { + "x": null + }, + "sparseNumberMap": { + "x": null + }, + "sparseStringMap": { + "x": null + }, + "sparseStructMap": { + "x": null + } + } + }, + { + id: "RpcV2CborDeserializesSparseSetMap", + documentation: "A response that contains a sparse map of sets", + protocol: rpcv2Cbor, + code: 200, + body: "v2xzcGFyc2VTZXRNYXC/YXmfYWFhYv9heJ////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + }, + { + id: "RpcV2CborDeserializesSparseSetMapAndRetainsNull", + documentation: "A response that contains a sparse map of sets with a null", + protocol: rpcv2Cbor, + code: 200, + body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL/YXr2//8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseSetMap": { + "x": [], + "y": ["a", "b"], + "z": null + } + } + }, + { + id: "RpcV2CborDeserializesZeroValuesInSparseMaps", + documentation: "Ensure that 0 and false are sent over the wire in all maps and lists", + protocol: rpcv2Cbor, + code: 200, + body: "v29zcGFyc2VOdW1iZXJNYXC/YXgA/3BzcGFyc2VCb29sZWFuTWFwv2F49P//" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseNumberMap": { + "x": 0 + }, + "sparseBooleanMap": { + "x": false + } + } + } +]) + +operation RpcV2CborSparseMaps { + input: RpcV2CborSparseMapsInputOutput + output: RpcV2CborSparseMapsInputOutput + errors: [ValidationException] +} + +structure RpcV2CborSparseMapsInputOutput { + sparseStructMap: SparseStructMap, + sparseNumberMap: SparseNumberMap, + sparseBooleanMap: SparseBooleanMap, + sparseStringMap: SparseStringMap, + sparseSetMap: SparseSetMap, +} + +@sparse +map SparseStructMap { + key: String, + value: GreetingStruct +} + +@sparse +map SparseBooleanMap { + key: String, + value: Boolean +} + +@sparse +map SparseNumberMap { + key: String, + value: Integer +} + +@sparse +map SparseSetMap { + key: String, + value: StringSet +} diff --git a/smithy-protocol-tests/model/rpcv2Cbor/cbor-structs.smithy b/smithy-protocol-tests/model/rpcv2Cbor/cbor-structs.smithy new file mode 100644 index 00000000000..effaefc0016 --- /dev/null +++ b/smithy-protocol-tests/model/rpcv2Cbor/cbor-structs.smithy @@ -0,0 +1,540 @@ +$version: "2.0" + +namespace smithy.protocoltests.rpcv2Cbor + +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +@httpRequestTests([ + { + id: "RpcV2CborSimpleScalarProperties", + protocol: rpcv2Cbor, + documentation: "Serializes simple scalar properties", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kDz989saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9WlibG9iVmFsdWVDZm9v/w==" + params: { + byteValue: 5, + doubleValue: 1.889, + falseBooleanValue: false, + floatValue: 7.624, + integerValue: 256, + longValue: 9873, + shortValue: 9898, + stringValue: "simple", + trueBooleanValue: true, + blobValue: "foo" + } + }, + { + id: "RpcV2CborSimpleScalarPropertiesUsingIndefiniteLength", + protocol: rpcv2Cbor, + documentation: """ + The server should be capable of deserializing simple scalar properties + encoded using a map with a definite length. The server should also be able to parse + a key encoded using an indefinite length string.""", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "qmlieXRlVmFsdWUFf2Zkb3VibGVlVmFsdWX/+z/+OVgQYk3Tf2VmYWxzZWdCb29sZWFuZVZhbHVl//RqZmxvYXRWYWx1ZfpA8/fPbGludGVnZXJWYWx1ZRkBAGlsb25nVmFsdWUZJpFqc2hvcnRWYWx1ZRkmqn9mc3RyaW5nZVZhbHVl/2ZzaW1wbGVwdHJ1ZUJvb2xlYW5WYWx1ZfVpYmxvYlZhbHVlQ2Zvbw==" + params: { + byteValue: 5, + doubleValue: 1.889, + falseBooleanValue: false, + floatValue: 7.624, + integerValue: 256, + longValue: 9873, + shortValue: 9898, + stringValue: "simple", + trueBooleanValue: true, + blobValue: "foo" + }, + appliesTo: "server" + }, + { + id: "RpcV2CborClientDoesntSerializeNullStructureValues", + documentation: "RpcV2 Cbor should not serialize null structure values", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v/8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + params: { + stringValue: null + }, + appliesTo: "client" + }, + { + id: "RpcV2CborServerDoesntDeSerializeNullStructureValues", + documentation: "RpcV2 Cbor should not deserialize null structure values", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tzdHJpbmdWYWx1Zfb/", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + params: {}, + appliesTo: "server" + }, + { + id: "RpcV2CborSupportsNaNFloatInputs", + protocol: rpcv2Cbor, + documentation: "Supports handling NaN float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tkb3VibGVWYWx1Zft/+AAAAAAAAGpmbG9hdFZhbHVl+n/AAAD/" + params: { + doubleValue: "NaN", + floatValue: "NaN" + } + }, + { + id: "RpcV2CborSupportsInfinityFloatInputs", + protocol: rpcv2Cbor, + documentation: "Supports handling Infinity float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tkb3VibGVWYWx1Zft/8AAAAAAAAGpmbG9hdFZhbHVl+n+AAAD/" + params: { + doubleValue: "Infinity", + floatValue: "Infinity" + } + }, + { + id: "RpcV2CborSupportsNegativeInfinityFloatInputs", + protocol: rpcv2Cbor, + documentation: "Supports handling Infinity float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tkb3VibGVWYWx1Zfv/8AAAAAAAAGpmbG9hdFZhbHVl+v+AAAD/" + params: { + doubleValue: "-Infinity", + floatValue: "-Infinity" + } + }, + { + id: "RpcV2CborIndefiniteLengthStringsCanBeDeserialized", + protocol: rpcv2Cbor, + documentation: "The server should be capable of deserializing indefinite length text strings.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "oWtzdHJpbmdWYWx1ZX94HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcscSBjaHVua2VkIG9uIGNvbW1h/w==" + params: { + stringValue: "An example indefinite string, chunked on comma" + }, + appliesTo: "server" + }, + { + id: "RpcV2CborIndefiniteLengthByteStringsCanBeDeserialized", + protocol: rpcv2Cbor, + documentation: "The server should be capable of deserializing indefinite length byte strings.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "oWlibG9iVmFsdWVfWCJBbiBleGFtcGxlIGluZGVmaW5pdGUtYnl0ZSBzdHJpbmcsUSBjaHVua2VkIG9uIGNvbW1h/w==" + params: { + blobValue: "An example indefinite-byte string, chunked on comma" + }, + appliesTo: "server" + }, + { + id: "RpcV2CborSupportsUpcastingData", + protocol: rpcv2Cbor, + documentation: "Supports upcasting from a smaller byte representation of the same data type.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tkb3VibGVWYWx1Zfk+AGpmbG9hdFZhbHVl+UegbGludGVnZXJWYWx1ZRg4aWxvbmdWYWx1ZRkBAGpzaG9ydFZhbHVlCv8=" + params: { + doubleValue: 1.5, + floatValue: 7.625, + integerValue: 56, + longValue: 256, + shortValue: 10 + }, + appliesTo: "server" + }, + { + id: "RpcV2CborExtraFieldsInTheBodyShouldBeSkippedByServers", + protocol: rpcv2Cbor, + documentation: """ + The server should skip over additional fields that are not part of the structure. This allows a + client generated against a newer Smithy model to be able to communicate with a server that is + generated against an older Smithy model.""", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kDz989rZXh0cmFPYmplY3S/c2luZGVmaW5pdGVMZW5ndGhNYXC/a3dpdGhBbkFycmF5nwECA///cWRlZmluaXRlTGVuZ3RoTWFwo3J3aXRoQURlZmluaXRlQXJyYXmDAQIDeB1hbmRTb21lSW5kZWZpbml0ZUxlbmd0aFN0cmluZ3gfdGhhdCBoYXMsIGJlZW4gY2h1bmtlZCBvbiBjb21tYWxub3JtYWxTdHJpbmdjZm9vanNob3J0VmFsdWUZJw9uc29tZU90aGVyRmllbGR2dGhpcyBzaG91bGQgYmUgc2tpcHBlZP9saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9WlibG9iVmFsdWVDZm9v/w==", + params: { + byteValue: 5, + doubleValue: 1.889, + falseBooleanValue: false, + floatValue: 7.624, + integerValue: 256, + longValue: 9873, + shortValue: 9898, + stringValue: "simple", + trueBooleanValue: true, + blobValue: "foo" + }, + appliesTo: "server" + } +]) +@httpResponseTests([ + { + id: "RpcV2CborSimpleScalarProperties", + protocol: rpcv2Cbor, + documentation: "Serializes simple scalar properties", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + bodyMediaType: "application/cbor", + body: "v3B0cnVlQm9vbGVhblZhbHVl9XFmYWxzZUJvb2xlYW5WYWx1ZfRpYnl0ZVZhbHVlBWtkb3VibGVWYWx1Zfs//jlYEGJN02pmbG9hdFZhbHVl+kDz989saW50ZWdlclZhbHVlGQEAanNob3J0VmFsdWUZJqprc3RyaW5nVmFsdWVmc2ltcGxlaWJsb2JWYWx1ZUNmb2//" + code: 200, + params: { + trueBooleanValue: true, + falseBooleanValue: false, + byteValue: 5, + doubleValue: 1.889, + floatValue: 7.624, + integerValue: 256, + shortValue: 9898, + stringValue: "simple", + blobValue: "foo" + } + }, + { + id: "RpcV2CborSimpleScalarPropertiesUsingDefiniteLength", + protocol: rpcv2Cbor, + documentation: "Deserializes simple scalar properties encoded using a map with definite length", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + bodyMediaType: "application/cbor", + body: "qXB0cnVlQm9vbGVhblZhbHVl9XFmYWxzZUJvb2xlYW5WYWx1ZfRpYnl0ZVZhbHVlBWtkb3VibGVWYWx1Zfs//jlYEGJN02pmbG9hdFZhbHVl+kDz989saW50ZWdlclZhbHVlGQEAanNob3J0VmFsdWUZJqprc3RyaW5nVmFsdWVmc2ltcGxlaWJsb2JWYWx1ZUNmb28=" + code: 200, + params: { + trueBooleanValue: true, + falseBooleanValue: false, + byteValue: 5, + doubleValue: 1.889, + floatValue: 7.624, + integerValue: 256, + shortValue: 9898, + stringValue: "simple", + blobValue: "foo" + }, + appliesTo: "client" + }, + { + id: "RpcV2CborClientDoesntDeserializeNullStructureValues", + documentation: "RpcV2 Cbor should not deserialize null structure values", + protocol: rpcv2Cbor, + body: "v2tzdHJpbmdWYWx1Zfb/", + code: 200, + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + params: {} + appliesTo: "client" + }, + { + id: "RpcV2CborServerDoesntSerializeNullStructureValues", + documentation: "RpcV2 Cbor should not serialize null structure values", + protocol: rpcv2Cbor, + body: "v/8=", + code: 200, + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + params: { + stringValue: null + }, + appliesTo: "server" + }, + { + id: "RpcV2CborSupportsNaNFloatOutputs", + protocol: rpcv2Cbor, + documentation: "Supports handling NaN float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + code: 200, + bodyMediaType: "application/cbor", + body: "v2tkb3VibGVWYWx1Zft/+AAAAAAAAGpmbG9hdFZhbHVl+n/AAAD/" + params: { + doubleValue: "NaN", + floatValue: "NaN" + } + }, + { + id: "RpcV2CborSupportsInfinityFloatOutputs", + protocol: rpcv2Cbor, + documentation: "Supports handling Infinity float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + code: 200, + bodyMediaType: "application/cbor", + body: "v2tkb3VibGVWYWx1Zft/8AAAAAAAAGpmbG9hdFZhbHVl+n+AAAD/" + params: { + doubleValue: "Infinity", + floatValue: "Infinity" + } + }, + { + id: "RpcV2CborSupportsNegativeInfinityFloatOutputs", + protocol: rpcv2Cbor, + documentation: "Supports handling Negative Infinity float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + code: 200, + bodyMediaType: "application/cbor", + body: "v2tkb3VibGVWYWx1Zfv/8AAAAAAAAGpmbG9hdFZhbHVl+v+AAAD/" + params: { + doubleValue: "-Infinity", + floatValue: "-Infinity" + } + }, + { + id: "RpcV2CborSupportsUpcastingDataOnDeserialize", + protocol: rpcv2Cbor, + documentation: "Supports upcasting from a smaller byte representation of the same data type.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + code: 200, + bodyMediaType: "application/cbor", + body: "v2tkb3VibGVWYWx1Zfk+AGpmbG9hdFZhbHVl+UegbGludGVnZXJWYWx1ZRg4aWxvbmdWYWx1ZRkBAGpzaG9ydFZhbHVlCv8=" + params: { + doubleValue: 1.5, + floatValue: 7.625, + integerValue: 56, + longValue: 256, + shortValue: 10 + }, + appliesTo: "client" + }, + { + id: "RpcV2CborExtraFieldsInTheBodyShouldBeSkippedByClients", + protocol: rpcv2Cbor, + documentation: """ + The client should skip over additional fields that are not part of the structure. This allows a + client generated against an older Smithy model to be able to communicate with a server that is + generated against a newer Smithy model.""", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + code: 200, + bodyMediaType: "application/cbor", + body: "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kDz989rZXh0cmFPYmplY3S/c2luZGVmaW5pdGVMZW5ndGhNYXC/a3dpdGhBbkFycmF5nwECA///cWRlZmluaXRlTGVuZ3RoTWFwo3J3aXRoQURlZmluaXRlQXJyYXmDAQIDeB1hbmRTb21lSW5kZWZpbml0ZUxlbmd0aFN0cmluZ3gfdGhhdCBoYXMsIGJlZW4gY2h1bmtlZCBvbiBjb21tYWxub3JtYWxTdHJpbmdjZm9vanNob3J0VmFsdWUZJw9uc29tZU90aGVyRmllbGR2dGhpcyBzaG91bGQgYmUgc2tpcHBlZP9saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9WlibG9iVmFsdWVDZm9v/w==", + params: { + byteValue: 5, + doubleValue: 1.889, + falseBooleanValue: false, + floatValue: 7.624, + integerValue: 256, + longValue: 9873, + shortValue: 9898, + stringValue: "simple", + trueBooleanValue: true, + blobValue: "foo" + }, + appliesTo: "client" + } +]) +operation SimpleScalarProperties { + input: SimpleScalarStructure, + output: SimpleScalarStructure +} + +apply RecursiveShapes @httpRequestTests([ + { + id: "RpcV2CborRecursiveShapes", + documentation: "Serializes recursive structures", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RecursiveShapes", + body: "v2ZuZXN0ZWS/Y2Zvb2RGb28xZm5lc3RlZL9jYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyv2Nmb29kRm9vMmZuZXN0ZWS/Y2JhcmRCYXIy//////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + nested: { + foo: "Foo1", + nested: { + bar: "Bar1", + recursiveMember: { + foo: "Foo2", + nested: { + bar: "Bar2" + } + } + } + } + } + } +]) + +apply RecursiveShapes @httpResponseTests([ + { + id: "RpcV2CborRecursiveShapes", + documentation: "Serializes recursive structures", + protocol: rpcv2Cbor, + code: 200, + body: "v2ZuZXN0ZWS/Y2Zvb2RGb28xZm5lc3RlZL9jYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyv2Nmb29kRm9vMmZuZXN0ZWS/Y2JhcmRCYXIy//////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + nested: { + foo: "Foo1", + nested: { + bar: "Bar1", + recursiveMember: { + foo: "Foo2", + nested: { + bar: "Bar2" + } + } + } + } + } + }, + { + id: "RpcV2CborRecursiveShapesUsingDefiniteLength", + documentation: "Deserializes recursive structures encoded using a map with definite length", + protocol: rpcv2Cbor, + code: 200, + body: "oWZuZXN0ZWSiY2Zvb2RGb28xZm5lc3RlZKJjYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyomNmb29kRm9vMmZuZXN0ZWShY2JhcmRCYXIy" + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + nested: { + foo: "Foo1", + nested: { + bar: "Bar1", + recursiveMember: { + foo: "Foo2", + nested: { + bar: "Bar2" + } + } + } + } + }, + appliesTo: "client" + } +]) + +operation RecursiveShapes { + input: RecursiveShapesInputOutput, + output: RecursiveShapesInputOutput +} + +structure RecursiveShapesInputOutput { + nested: RecursiveShapesInputOutputNested1 +} + +structure RecursiveShapesInputOutputNested1 { + foo: String, + nested: RecursiveShapesInputOutputNested2 +} + +structure RecursiveShapesInputOutputNested2 { + bar: String, + recursiveMember: RecursiveShapesInputOutputNested1, +} + +structure SimpleScalarStructure { + trueBooleanValue: Boolean, + falseBooleanValue: Boolean, + byteValue: Byte, + doubleValue: Double, + floatValue: Float, + integerValue: Integer, + longValue: Long, + shortValue: Short, + stringValue: String, + blobValue: Blob +} diff --git a/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy b/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy new file mode 100644 index 00000000000..8d22964423d --- /dev/null +++ b/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy @@ -0,0 +1,338 @@ +$version: "2.0" + +namespace smithy.protocoltests.rpcv2Cbor + +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests +use smithy.framework#ValidationException + +apply OperationWithDefaults @httpRequestTests([ + { + id: "RpcV2CborClientPopulatesDefaultValuesInInput" + documentation: "Client populates default values in input." + protocol: rpcv2Cbor + appliesTo: "client" + tags: ["defaults"] + method: "POST" + uri: "/service/RpcV2Protocol/operation/OperationWithDefaults", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + bodyMediaType: "application/cbor" + body: "v21kZWZhdWx0U3RyaW5nYmhpbmRlZmF1bHRCb29sZWFu9WtkZWZhdWx0TGlzdIBwZGVmYXVsdFRpbWVzdGFtcMH7AAAAAAAAAABrZGVmYXVsdEJsb2JDYWJja2RlZmF1bHRCeXRlAWxkZWZhdWx0U2hvcnQBbmRlZmF1bHRJbnRlZ2VyCmtkZWZhdWx0TG9uZxhkbGRlZmF1bHRGbG9hdPo/gAAAbWRlZmF1bHREb3VibGX7P/AAAAAAAABqZGVmYXVsdE1hcKBrZGVmYXVsdEVudW1jRk9PbmRlZmF1bHRJbnRFbnVtAWtlbXB0eVN0cmluZ2BsZmFsc2VCb29sZWFu9GllbXB0eUJsb2JAaHplcm9CeXRlAGl6ZXJvU2hvcnQAa3plcm9JbnRlZ2VyAGh6ZXJvTG9uZwBpemVyb0Zsb2F0+gAAAABqemVyb0RvdWJsZfsAAAAAAAAAAP8=" + params: { + defaults: {} + } + } + { + id: "RpcV2CborClientSkipsTopLevelDefaultValuesInInput" + documentation: "Client skips top level default values in input." + appliesTo: "client" + tags: ["defaults"] + protocol: rpcv2Cbor + method: "POST" + bodyMediaType: "application/cbor" + uri: "/service/RpcV2Protocol/operation/OperationWithDefaults", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + body: "v/8=" + params: { + } + } + { + id: "RpcV2CborClientUsesExplicitlyProvidedMemberValuesOverDefaults" + documentation: "Client uses explicitly provided member values over defaults" + appliesTo: "client" + tags: ["defaults"] + protocol: rpcv2Cbor + method: "POST" + bodyMediaType: "application/cbor" + uri: "/service/RpcV2Protocol/operation/OperationWithDefaults", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + body: "v2hkZWZhdWx0c7dtZGVmYXVsdFN0cmluZ2NieWVuZGVmYXVsdEJvb2xlYW71a2RlZmF1bHRMaXN0gWFhcGRlZmF1bHRUaW1lc3RhbXDB+z/wAAAAAAAAa2RlZmF1bHRCbG9iQmhpa2RlZmF1bHRCeXRlAmxkZWZhdWx0U2hvcnQCbmRlZmF1bHRJbnRlZ2VyFGtkZWZhdWx0TG9uZxjIbGRlZmF1bHRGbG9hdPpAAAAAbWRlZmF1bHREb3VibGX7QAAAAAAAAABqZGVmYXVsdE1hcKFkbmFtZWRKYWNra2RlZmF1bHRFbnVtY0JBUm5kZWZhdWx0SW50RW51bQJrZW1wdHlTdHJpbmdjZm9vbGZhbHNlQm9vbGVhbvVpZW1wdHlCbG9iQmhpaHplcm9CeXRlAWl6ZXJvU2hvcnQBa3plcm9JbnRlZ2VyAWh6ZXJvTG9uZwFpemVyb0Zsb2F0+j+AAABqemVyb0RvdWJsZfs/8AAAAAAAAP8=" + params: { + defaults: { + defaultString: "bye", + defaultBoolean: true, + defaultList: ["a"], + defaultTimestamp: 1, + defaultBlob: "hi", + defaultByte: 2, + defaultShort: 2, + defaultInteger: 20, + defaultLong: 200, + defaultFloat: 2.0, + defaultDouble: 2.0, + defaultMap: {name: "Jack"}, + defaultEnum: "BAR", + defaultIntEnum: 2, + emptyString: "foo", + falseBoolean: true, + emptyBlob: "hi", + zeroByte: 1, + zeroShort: 1, + zeroInteger: 1, + zeroLong: 1, + zeroFloat: 1.0, + zeroDouble: 1.0 + } + } + } + { + id: "RpcV2CborServerPopulatesDefaultsWhenMissingInRequestBody" + documentation: "Server populates default values when missing in request body." + appliesTo: "server" + tags: ["defaults"] + protocol: rpcv2Cbor + method: "POST" + bodyMediaType: "application/cbor" + uri: "/service/RpcV2Protocol/operation/OperationWithDefaults", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + body: "v2hkZWZhdWx0c6D/" + params: { + defaults: { + defaultString: "hi", + defaultBoolean: true, + defaultList: [], + defaultTimestamp: 0, + defaultBlob: "abc", + defaultByte: 1, + defaultShort: 1, + defaultInteger: 10, + defaultLong: 100, + defaultFloat: 1.0, + defaultDouble: 1.0, + defaultMap: {}, + defaultEnum: "FOO", + defaultIntEnum: 1, + emptyString: "", + falseBoolean: false, + emptyBlob: "", + zeroByte: 0, + zeroShort: 0, + zeroInteger: 0, + zeroLong: 0, + zeroFloat: 0.0, + zeroDouble: 0.0 + }, + topLevelDefault: "hi", + otherTopLevelDefault: 0 + } + } + { + id: "RpcV2CborClientUsesExplicitlyProvidedValuesInTopLevel" + documentation: "Any time a value is provided for a member in the top level of input, it is used, regardless of if its the default." + appliesTo: "client" + tags: ["defaults"] + protocol: rpcv2Cbor + method: "POST" + bodyMediaType: "application/cbor" + uri: "/service/RpcV2Protocol/operation/OperationWithDefaults", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + body: "v290b3BMZXZlbERlZmF1bHRiaGl0b3RoZXJUb3BMZXZlbERlZmF1bHQA/w==" + params: { + topLevelDefault: "hi", + otherTopLevelDefault: 0 + } + } + { + id: "RpcV2CborClientIgnoresNonTopLevelDefaultsOnMembersWithClientOptional" + documentation: "Typically, non top-level members would have defaults filled in, but if they have the clientOptional trait, the defaults should be ignored." + appliesTo: "client" + tags: ["defaults"] + protocol: rpcv2Cbor + method: "POST" + bodyMediaType: "application/cbor" + uri: "/service/RpcV2Protocol/operation/OperationWithDefaults", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + body: "v3ZjbGllbnRPcHRpb25hbERlZmF1bHRzoP8=" + params: { + clientOptionalDefaults: {} + } + } +]) + +apply OperationWithDefaults @httpResponseTests([ + { + id: "RpcV2CborClientPopulatesDefaultsValuesWhenMissingInResponse" + documentation: "Client populates default values when missing in response." + appliesTo: "client" + tags: ["defaults"] + protocol: rpcv2Cbor + code: 200 + bodyMediaType: "application/cbor" + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + body: "v/8=" + params: { + defaultString: "hi" + defaultBoolean: true + defaultList: [] + defaultTimestamp: 0 + defaultBlob: "abc" + defaultByte: 1 + defaultShort: 1 + defaultInteger: 10 + defaultLong: 100 + defaultFloat: 1.0 + defaultDouble: 1.0 + defaultMap: {} + defaultEnum: "FOO" + defaultIntEnum: 1 + emptyString: "" + falseBoolean: false + emptyBlob: "" + zeroByte: 0 + zeroShort: 0 + zeroInteger: 0 + zeroLong: 0 + zeroFloat: 0.0 + zeroDouble: 0.0 + } + } + { + id: "RpcV2CborClientIgnoresDefaultValuesIfMemberValuesArePresentInResponse" + documentation: "Client ignores default values if member values are present in the response." + appliesTo: "client" + tags: ["defaults"] + protocol: rpcv2Cbor + code: 200 + bodyMediaType: "application/cbor" + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + body: "v21kZWZhdWx0U3RyaW5nY2J5ZW5kZWZhdWx0Qm9vbGVhbvRrZGVmYXVsdExpc3SBYWFwZGVmYXVsdFRpbWVzdGFtcMH7QAAAAAAAAABrZGVmYXVsdEJsb2JCaGlrZGVmYXVsdEJ5dGUCbGRlZmF1bHRTaG9ydAJuZGVmYXVsdEludGVnZXIUa2RlZmF1bHRMb25nGMhsZGVmYXVsdEZsb2F0+kAAAABtZGVmYXVsdERvdWJsZftAAAAAAAAAAGpkZWZhdWx0TWFwoWRuYW1lZEphY2trZGVmYXVsdEVudW1jQkFSbmRlZmF1bHRJbnRFbnVtAmtlbXB0eVN0cmluZ2Nmb29sZmFsc2VCb29sZWFu9WllbXB0eUJsb2JCaGloemVyb0J5dGUBaXplcm9TaG9ydAFremVyb0ludGVnZXIBaHplcm9Mb25nAWl6ZXJvRmxvYXT6P4AAAGp6ZXJvRG91Ymxl+z/wAAAAAAAA/w==" + params: { + defaultString: "bye", + defaultBoolean: false, + defaultList: ["a"], + defaultTimestamp: 2, + defaultBlob: "hi", + defaultByte: 2, + defaultShort: 2, + defaultInteger: 20, + defaultLong: 200, + defaultFloat: 2.0, + defaultDouble: 2.0, + defaultMap: {name: "Jack"}, + defaultEnum: "BAR", + defaultIntEnum: 2, + emptyString: "foo", + falseBoolean: true, + emptyBlob: "hi", + zeroByte: 1, + zeroShort: 1, + zeroInteger: 1, + zeroLong: 1, + zeroFloat: 1.0, + zeroDouble: 1.0 + } + } + { + id: "RpcV2CborServerPopulatesDefaultsInResponseWhenMissingInParams" + documentation: "Server populates default values in response when missing in params." + appliesTo: "server" + tags: ["defaults"] + protocol: rpcv2Cbor + code: 200 + bodyMediaType: "application/cbor" + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + body: "v21kZWZhdWx0U3RyaW5nYmhpbmRlZmF1bHRCb29sZWFu9WtkZWZhdWx0TGlzdIBwZGVmYXVsdFRpbWVzdGFtcMH7AAAAAAAAAABrZGVmYXVsdEJsb2JDYWJja2RlZmF1bHRCeXRlAWxkZWZhdWx0U2hvcnQBbmRlZmF1bHRJbnRlZ2VyCmtkZWZhdWx0TG9uZxhkbGRlZmF1bHRGbG9hdPo/gAAAbWRlZmF1bHREb3VibGX7P/AAAAAAAABqZGVmYXVsdE1hcKBrZGVmYXVsdEVudW1jRk9PbmRlZmF1bHRJbnRFbnVtAWtlbXB0eVN0cmluZ2BsZmFsc2VCb29sZWFu9GllbXB0eUJsb2JAaHplcm9CeXRlAGl6ZXJvU2hvcnQAa3plcm9JbnRlZ2VyAGh6ZXJvTG9uZwBpemVyb0Zsb2F0+gAAAABqemVyb0RvdWJsZfsAAAAAAAAAAP8=" + params: {} + } +]) + +operation OperationWithDefaults { + input := { + defaults: Defaults + clientOptionalDefaults: ClientOptionalDefaults + topLevelDefault: String = "hi" // Client should ignore default values in input shape + otherTopLevelDefault: Integer = 0 + } + + output := with [DefaultsMixin] {} + + errors: [ValidationException] +} + +structure Defaults with [DefaultsMixin] {} + +structure ClientOptionalDefaults { + @clientOptional + member: Integer = 0 +} + +@mixin +structure DefaultsMixin { + defaultString: String = "hi" + defaultBoolean: Boolean = true + defaultList: TestStringList = [] + defaultTimestamp: Timestamp = 0 + defaultBlob: Blob = "abc" + defaultByte: Byte = 1 + defaultShort: Short = 1 + defaultInteger: Integer = 10 + defaultLong: Long = 100 + defaultFloat: Float = 1.0 + defaultDouble: Double = 1.0 + defaultMap: TestStringMap = {} + defaultEnum: TestEnum = "FOO" + defaultIntEnum: TestIntEnum = 1 + emptyString: String = "" + falseBoolean: Boolean = false + emptyBlob: Blob = "" + zeroByte: Byte = 0 + zeroShort: Short = 0 + zeroInteger: Integer = 0 + zeroLong: Long = 0 + zeroFloat: Float = 0.0 + zeroDouble: Double = 0.0 +} + +list TestStringList { + member: String +} + +map TestStringMap { + key: String + value: String +} + +enum TestEnum { + FOO + BAR + BAZ +} + +intEnum TestIntEnum { + ONE = 1 + TWO = 2 +} diff --git a/smithy-protocol-tests/model/rpcv2Cbor/empty-input-output.smithy b/smithy-protocol-tests/model/rpcv2Cbor/empty-input-output.smithy new file mode 100644 index 00000000000..f178b9ee0b3 --- /dev/null +++ b/smithy-protocol-tests/model/rpcv2Cbor/empty-input-output.smithy @@ -0,0 +1,207 @@ +$version: "2.0" + +namespace smithy.protocoltests.rpcv2Cbor + +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + + +@httpRequestTests([ + { + id: "no_input", + protocol: rpcv2Cbor, + documentation: "Body is empty and no Content-Type header if no input", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + }, + forbidHeaders: [ + "Content-Type", + "X-Amz-Target" + ] + method: "POST", + uri: "/service/RpcV2Protocol/operation/NoInputOutput", + body: "" + }, + { + id: "NoInputServerAllowsEmptyCbor", + protocol: rpcv2Cbor, + documentation: "Servers should accept CBOR empty struct if no input.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + uri: "/service/RpcV2Protocol/operation/NoInputOutput", + body: "v/8=", + appliesTo: "server" + }, + { + id: "NoInputServerAllowsEmptyBody", + protocol: rpcv2Cbor, + documentation: """ + Servers should accept an empty body if there is no input. Additionally, + they should not raise an error if the `Accept` header is set.""" + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + uri: "/service/RpcV2Protocol/operation/NoInputOutput", + body: "", + appliesTo: "server" + } +]) +@httpResponseTests([ + { + id: "no_output", + protocol: rpcv2Cbor, + documentation: "A `Content-Type` header should not be set if the response body is empty.", + body: "", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + }, + forbidHeaders: [ + "Content-Type" + ] + code: 200, + }, + { + id: "NoOutputClientAllowsEmptyCbor", + protocol: rpcv2Cbor, + documentation: "Clients should accept a CBOR empty struct if there is no output.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + code: 200, + bodyMediaType: "application/cbor", + body: "v/8=", + appliesTo: "client", + }, + { + id: "NoOutputClientAllowsEmptyBody", + protocol: rpcv2Cbor, + documentation: """ + Clients should accept an empty body if there is no output and + should not raise an error if the `Content-Type` header is set.""", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + code: 200, + bodyMediaType: "application/cbor", + body: "", + appliesTo: "client", + } +]) +operation NoInputOutput {} + + +@httpRequestTests([ + { + id: "empty_input", + protocol: rpcv2Cbor, + documentation: "When Input structure is empty we write CBOR equivalent of {}", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + forbidHeaders: [ + "X-Amz-Target" + ] + method: "POST", + uri: "/service/RpcV2Protocol/operation/EmptyInputOutput", + bodyMediaType: "application/cbor", + body: "v/8=", + }, + { + id: "empty_input_no_body", + protocol: rpcv2Cbor, + documentation: "When Input structure is empty the server should accept an empty body", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + method: "POST", + uri: "/service/RpcV2Protocol/operation/EmptyInputOutput", + bodyMediaType: "application/cbor", + body: "", + appliesTo: "server" + }, +]) +@httpResponseTests([ + { + id: "empty_output", + protocol: rpcv2Cbor, + documentation: "When output structure is empty we write CBOR equivalent of {}", + body: "v/8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + code: 200, + }, + { + id: "empty_output_no_body", + protocol: rpcv2Cbor, + documentation: "When output structure is empty the client should accept an empty body", + body: "", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + code: 200, + appliesTo: "client" + }, +]) +operation EmptyInputOutput { + input: EmptyStructure, + output: EmptyStructure +} + +@httpRequestTests([ + { + id: "optional_input", + protocol: rpcv2Cbor, + documentation: "When input is empty we write CBOR equivalent of {}", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + forbidHeaders: [ + "X-Amz-Target" + ] + method: "POST", + uri: "/service/RpcV2Protocol/operation/OptionalInputOutput", + bodyMediaType: "application/cbor", + body: "v/8=", + }, +]) +@httpResponseTests([ + { + id: "optional_output", + protocol: rpcv2Cbor, + documentation: "When output is empty we write CBOR equivalent of {}", + body: "v/8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + code: 200, + }, +]) +operation OptionalInputOutput { + input: SimpleStructure, + output: SimpleStructure +} diff --git a/smithy-protocol-tests/model/rpcv2Cbor/errors.smithy b/smithy-protocol-tests/model/rpcv2Cbor/errors.smithy new file mode 100644 index 00000000000..d9f0c17832d --- /dev/null +++ b/smithy-protocol-tests/model/rpcv2Cbor/errors.smithy @@ -0,0 +1,92 @@ +$version: "2.0" + +namespace smithy.protocoltests.rpcv2Cbor + +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests +use smithy.protocols#rpcv2Cbor + +/// This operation has three possible return values: +/// +/// 1. A successful response in the form of GreetingWithErrorsOutput +/// 2. An InvalidGreeting error. +/// 3. A ComplexError error. +/// +/// Implementations must be able to successfully take a response and +/// properly deserialize successful and error responses. +@idempotent +operation GreetingWithErrors { + output: GreetingWithErrorsOutput, + errors: [InvalidGreeting, ComplexError] +} + +structure GreetingWithErrorsOutput { + greeting: String, +} + +/// This error is thrown when an invalid greeting value is provided. +@error("client") +structure InvalidGreeting { + Message: String, +} + +apply InvalidGreeting @httpResponseTests([ + { + id: "RpcV2CborInvalidGreetingError", + documentation: "Parses simple RpcV2 Cbor errors", + protocol: rpcv2Cbor, + params: { + Message: "Hi" + }, + code: 400, + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + body: "v2ZfX3R5cGV4LnNtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNJbnZhbGlkR3JlZXRpbmdnTWVzc2FnZWJIaf8=", + bodyMediaType: "application/cbor", + }, +]) + +/// This error is thrown when a request is invalid. +@error("client") +structure ComplexError { + TopLevel: String, + Nested: ComplexNestedErrorData, +} + +structure ComplexNestedErrorData { + Foo: String, +} + +apply ComplexError @httpResponseTests([ + { + id: "RpcV2CborComplexError", + documentation: "Parses a complex error with no message member", + protocol: rpcv2Cbor, + params: { + TopLevel: "Top level", + Nested: { + Foo: "bar" + } + }, + code: 400, + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + body: "v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3JoVG9wTGV2ZWxpVG9wIGxldmVsZk5lc3RlZL9jRm9vY2Jhcv//", + bodyMediaType: "application/cbor" + }, + { + id: "RpcV2CborEmptyComplexError", + protocol: rpcv2Cbor, + code: 400, + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + body: "v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3L/", + bodyMediaType: "application/cbor" + }, +]) diff --git a/smithy-protocol-tests/model/rpcv2Cbor/fractional-seconds.smithy b/smithy-protocol-tests/model/rpcv2Cbor/fractional-seconds.smithy new file mode 100644 index 00000000000..faad52e9015 --- /dev/null +++ b/smithy-protocol-tests/model/rpcv2Cbor/fractional-seconds.smithy @@ -0,0 +1,34 @@ +$version: "2.0" + +namespace smithy.protocoltests.rpcv2Cbor + +use smithy.protocols#rpcv2Cbor +use smithy.protocoltests.shared#DateTime +use smithy.test#httpResponseTests + +// These tests verify that clients can parse `DateTime` timestamps with fractional seconds. +@tags(["client-only"]) +operation FractionalSeconds { + output: FractionalSecondsOutput +} + +apply FractionalSeconds @httpResponseTests([ + { + id: "RpcV2CborDateTimeWithFractionalSeconds", + documentation: "Ensures that clients can correctly parse timestamps with fractional seconds", + protocol: rpcv2Cbor, + code: 200, + body: "v2hkYXRldGltZcH7Qcw32zgPvnf/", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { datetime: 946845296.123 } + bodyMediaType: "application/cbor", + appliesTo: "client" + } +]) + +structure FractionalSecondsOutput { + datetime: DateTime +} diff --git a/smithy-protocol-tests/model/rpcv2Cbor/main.smithy b/smithy-protocol-tests/model/rpcv2Cbor/main.smithy new file mode 100644 index 00000000000..9d10e723e56 --- /dev/null +++ b/smithy-protocol-tests/model/rpcv2Cbor/main.smithy @@ -0,0 +1,35 @@ +$version: "2.0" + +namespace smithy.protocoltests.rpcv2Cbor + +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +@rpcv2Cbor +@title("RpcV2 Protocol Service") +service RpcV2Protocol { + version: "2020-07-14", + operations: [ + NoInputOutput, + EmptyInputOutput, + OptionalInputOutput, + SimpleScalarProperties, + RpcV2CborLists, + RpcV2CborDenseMaps, + RpcV2CborSparseMaps, + RecursiveShapes, + GreetingWithErrors, + FractionalSeconds, + OperationWithDefaults, + SparseNullsOperation + ] +} + +structure EmptyStructure { + +} + +structure SimpleStructure { + value: String, +} diff --git a/smithy-protocol-tests/model/shared-types.smithy b/smithy-protocol-tests/model/shared-types.smithy new file mode 100644 index 00000000000..5340018ccd6 --- /dev/null +++ b/smithy-protocol-tests/model/shared-types.smithy @@ -0,0 +1,215 @@ +// This file contains shared types that are used throughout the test cases. +// +// Anything that is generic enough that it could potentially be reused +// should be defined in this file. However, things like input or output +// structures or other test-case specific shapes should be defined closer to +// the test case and in its same file. + +$version: "2.0" + +namespace smithy.protocoltests.shared + +list StringList { + member: String, +} + +@sparse +list SparseStringList { + member: String +} + +@uniqueItems +list StringSet { + member: String, +} + +map StringMap { + key: String, + value: String, +} + +map StringListMap { + key: String, + value: StringList +} + +@sparse +map SparseStringMap { + key: String, + value: String, +} + +/// A list of lists of strings. +list NestedStringList { + member: StringList, +} + +list ShortList { + member: Short, +} + +list IntegerList { + member: Integer, +} + +@uniqueItems +list IntegerSet { + member: Integer, +} + +list FloatList { + member: Float, +} + +list DoubleList { + member: Double, +} + +list BooleanList { + member: Boolean, +} + +@uniqueItems +list BooleanSet { + member: Boolean, +} + +list TimestampList { + member: Timestamp, +} + +list BlobList { + member: Blob, +} + +@uniqueItems +list BlobSet { + member: Blob, +} + +list ByteList { + member: Byte, +} + +@uniqueItems +list ByteSet { + member: Byte, +} +@uniqueItems +list ShortSet { + member: Short, +} + +@uniqueItems +list LongList { + member: Long, +} + +@uniqueItems +list LongSet { + member: Long, +} + +@uniqueItems +list TimestampSet { + member: Timestamp, +} + +list DateTimeList { + member: DateTime, +} + +@uniqueItems +list DateTimeSet { + member: DateTime, +} + +@uniqueItems +list HttpDateSet { + member: HttpDate, +} + +@uniqueItems +list ListSet { + member: StringList +} + +@uniqueItems +list StructureSet { + member: GreetingStruct +} + +@uniqueItems +list UnionSet { + member: FooUnion +} + +union FooUnion { + string: String + integer: Integer +} + +enum FooEnum { + FOO = "Foo" + BAZ = "Baz" + BAR = "Bar" + ONE = "1" + ZERO = "0" +} + +list FooEnumList { + member: FooEnum, +} + +@uniqueItems +list FooEnumSet { + member: FooEnum, +} + +map FooEnumMap { + key: String, + value: FooEnum, +} + +@timestampFormat("date-time") +timestamp DateTime + +@timestampFormat("epoch-seconds") +timestamp EpochSeconds + +@timestampFormat("http-date") +timestamp HttpDate + +@mediaType("text/plain") +blob TextPlainBlob + +@mediaType("image/jpeg") +blob JpegBlob + +structure GreetingStruct { + hi: String +} + +list GreetingList { + member: GreetingStruct +} + +intEnum IntegerEnum { + A = 1 + B = 2 + C = 3 +} + +list IntegerEnumList { + member: IntegerEnum +} + +@uniqueItems +list IntegerEnumSet { + member: IntegerEnum +} + +map IntegerEnumMap { + key: String, + value: IntegerEnum +} diff --git a/smithy-protocol-traits/README.md b/smithy-protocol-traits/README.md new file mode 100644 index 00000000000..51d00fe62b1 --- /dev/null +++ b/smithy-protocol-traits/README.md @@ -0,0 +1,7 @@ +# Smithy protocols traits + +This module provides the implementation of protocols traits for Smithy. + +Protocols: + +* RPC v2 - An RPC protocol with support for multiple wire formats. diff --git a/smithy-protocol-traits/build.gradle b/smithy-protocol-traits/build.gradle new file mode 100644 index 00000000000..c269a7e007a --- /dev/null +++ b/smithy-protocol-traits/build.gradle @@ -0,0 +1,16 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +description = "This module provides the implementation of protocol traits for Smithy." + +ext { + displayName = "Smithy :: Protocol Traits" + moduleName = "software.amazon.smithy.protocol.traits" +} + +dependencies { + api project(":smithy-utils") + api project(":smithy-model") +} diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java new file mode 100644 index 00000000000..af9a5ee8907 --- /dev/null +++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java @@ -0,0 +1,148 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.protocol.traits; + +import java.util.ArrayList; +import java.util.List; +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; +import software.amazon.smithy.model.traits.AbstractTraitBuilder; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.utils.ListUtils; +import software.amazon.smithy.utils.ToSmithyBuilder; + +public final class Rpcv2CborTrait extends AbstractTrait implements ToSmithyBuilder { + + public static final ShapeId ID = ShapeId.from("smithy.protocols#rpcv2Cbor"); + + private static final String HTTP = "http"; + private static final String EVENT_STREAM_HTTP = "eventStreamHttp"; + + private final List http; + private final List eventStreamHttp; + + private Rpcv2CborTrait(Builder builder) { + super(ID, builder.getSourceLocation()); + http = ListUtils.copyOf(builder.http); + eventStreamHttp = ListUtils.copyOf(builder.eventStreamHttp); + } + + /** + * Creates a new {@code Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Updates the builder from a Node. + * + * @param node Node object that must be a valid {@code ObjectNode}. + * @return Returns the updated builder. + */ + public static Rpcv2CborTrait fromNode(Node node) { + Builder builder = builder().sourceLocation(node); + ObjectNode objectNode = node.expectObjectNode(); + objectNode.getArrayMember(HTTP).map(values -> Node.loadArrayOfString(HTTP, values)) + .ifPresent(builder::http); + objectNode.getArrayMember(EVENT_STREAM_HTTP).map(values -> Node.loadArrayOfString(EVENT_STREAM_HTTP, values)) + .ifPresent(builder::eventStreamHttp); + return builder.build(); + } + + /** + * Gets the priority ordered list of supported HTTP protocol versions. + * + * @return Returns the supported HTTP protocol versions. + */ + public List getHttp() { + return http; + } + + /** + * Gets the priority ordered list of supported HTTP protocol versions that are required when + * using event streams. + * + * @return Returns the supported event stream HTTP protocol versions. + */ + public List getEventStreamHttp() { + return eventStreamHttp; + } + + @Override + protected Node createNode() { + ObjectNode.Builder builder = Node.objectNodeBuilder().sourceLocation(getSourceLocation()); + if (!getHttp().isEmpty()) { + builder.withMember(HTTP, Node.fromStrings(getHttp())); + } + if (!getEventStreamHttp().isEmpty()) { + builder.withMember(EVENT_STREAM_HTTP, Node.fromStrings(getEventStreamHttp())); + } + return builder.build(); + } + + @Override + public Builder toBuilder() { + return builder().http(http).eventStreamHttp(eventStreamHttp); + } + + /** + * Builder for creating a {@code Rpcv2CborTrait}. + */ + public static final class Builder extends AbstractTraitBuilder { + + private final List http = new ArrayList<>(); + private final List eventStreamHttp = new ArrayList<>(); + + @Override + public Rpcv2CborTrait build() { + return new Rpcv2CborTrait(this); + } + + /** + * Sets the list of supported HTTP protocols. + * + * @param http HTTP protocols to set and replace. + * @return Returns the builder. + */ + public Builder http(List http) { + this.http.clear(); + this.http.addAll(http); + return this; + } + + /** + * Sets the list of supported event stream HTTP protocols. + * + * @param eventStreamHttp Event stream HTTP protocols to set and replace. + * @return Returns the builder. + */ + public Builder eventStreamHttp(List eventStreamHttp) { + this.eventStreamHttp.clear(); + this.eventStreamHttp.addAll(eventStreamHttp); + return this; + } + } + + /** + * Implements the {@code AbstractTrait.Provider}. + */ + public static final class Provider extends AbstractTrait.Provider { + + public Provider() { + super(ID); + } + + @Override + public Trait createTrait(ShapeId target, Node value) { + Rpcv2CborTrait result = fromNode(value); + result.setNodeCache(value); + return result; + } + } +} diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java new file mode 100644 index 00000000000..e8049bd959a --- /dev/null +++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.protocol.traits; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * Validates models implementing the {@code Rpcv2CborTrait} against its constraints by: + * + * - Ensuring that every entry in {@code eventStreamHttp} also appears in the {@code http} property + * of a protocol trait. + */ +@SmithyInternalApi +public final class Rpcv2CborTraitValidator extends AbstractValidator { + + @Override + public List validate(Model model) { + List events = new ArrayList<>(); + for (ServiceShape serviceShape : model.getServiceShapesWithTrait(Rpcv2CborTrait.class)) { + Rpcv2CborTrait protocolTrait = serviceShape.expectTrait(Rpcv2CborTrait.class); + + List invalid = new ArrayList<>(protocolTrait.getEventStreamHttp()); + invalid.removeAll(protocolTrait.getHttp()); + if (!invalid.isEmpty()) { + events.add(error(serviceShape, protocolTrait, + String.format("The following values of the `eventStreamHttp` property do " + + "not also appear in the `http` property of the %s protocol " + + "trait: %s", protocolTrait.toShapeId(), invalid))); + } + } + return events; + } +} diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java new file mode 100644 index 00000000000..38e4b7cf8b1 --- /dev/null +++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java @@ -0,0 +1,12 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Defines protocols for Smithy. + */ +@SmithyUnstableApi +package software.amazon.smithy.protocol.traits; + +import software.amazon.smithy.utils.SmithyUnstableApi; diff --git a/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService new file mode 100644 index 00000000000..807dd17ce19 --- /dev/null +++ b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService @@ -0,0 +1 @@ +software.amazon.smithy.protocol.traits.Rpcv2CborTrait$Provider diff --git a/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator new file mode 100644 index 00000000000..d764d47b993 --- /dev/null +++ b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator @@ -0,0 +1 @@ +software.amazon.smithy.protocol.traits.Rpcv2CborTraitValidator diff --git a/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest b/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest new file mode 100644 index 00000000000..d3ce2c739ec --- /dev/null +++ b/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest @@ -0,0 +1 @@ +smithy.protocols.rpcv2.smithy diff --git a/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy b/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy new file mode 100644 index 00000000000..68c672b7b27 --- /dev/null +++ b/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy @@ -0,0 +1,37 @@ +$version: "2.0" + +namespace smithy.protocols + +use smithy.api#cors +use smithy.api#endpoint +use smithy.api#hostLabel +use smithy.api#httpError + +/// An RPC-based protocol that serializes CBOR payloads. +@trait(selector: "service") +@protocolDefinition(traits: [ + cors + endpoint + hostLabel + httpError +]) +@traitValidators( + "rpcv2Cbor.NoDocuments": { + selector: "service ~> member :test(> document)" + message: "This protocol does not support document types" + } +) +structure rpcv2Cbor { + /// Priority ordered list of supported HTTP protocol versions. + http: StringList + + /// Priority ordered list of supported HTTP protocol versions + /// that are required when using event streams. + eventStreamHttp: StringList +} + +/// A list of String shapes. +@private +list StringList { + member: String +} diff --git a/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java new file mode 100644 index 00000000000..5897863b2bd --- /dev/null +++ b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.protocol.traits; + +import org.junit.jupiter.api.Assertions; +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; +import java.util.Optional; + +public class Rpcv2CborTraitTest { + + @Test + public void loadsTraitWithDefaults() { + Node node = Node.objectNode(); + TraitFactory provider = TraitFactory.createServiceFactory(); + Optional trait = provider.createTrait(Rpcv2CborTrait.ID, ShapeId.from("ns.foo#foo"), node); + + Assertions.assertTrue(trait.isPresent()); + Assertions.assertTrue(trait.get() instanceof Rpcv2CborTrait); + Rpcv2CborTrait smithyRpcV2Trait = (Rpcv2CborTrait) trait.get(); + Assertions.assertEquals(smithyRpcV2Trait.toNode(), node); + } +} diff --git a/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java new file mode 100644 index 00000000000..8c29bfe12c8 --- /dev/null +++ b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.protocol.traits; + +import java.util.concurrent.Callable; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.model.validation.testrunner.SmithyTestCase; +import software.amazon.smithy.model.validation.testrunner.SmithyTestSuite; + +public class TestRunnerTest { + @ParameterizedTest(name = "{0}") + @MethodSource("source") + public void testRunner(String filename, Callable callable) + throws Exception { + callable.call(); + } + + public static Stream source() { + return SmithyTestSuite.defaultParameterizedTestSource(TestRunnerTest.class); + } +} diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors new file mode 100644 index 00000000000..5f569df65f3 --- /dev/null +++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors @@ -0,0 +1,3 @@ +[ERROR] smithy.example#InvalidService1: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1] | Rpcv2CborTrait +[ERROR] smithy.example#InvalidService2: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1] | Rpcv2CborTrait +[ERROR] smithy.example#InvalidService3: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1, h2c] | Rpcv2CborTrait diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy new file mode 100644 index 00000000000..5f0968710e5 --- /dev/null +++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy @@ -0,0 +1,40 @@ +$version: "2.0" + +namespace smithy.example + +use smithy.protocols#rpcv2Cbor + +@rpcv2Cbor(http: ["h2", "http/1.1"], eventStreamHttp: ["h2"]) +service ValidService1 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["h2"]) +service ValidService2 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: [], eventStreamHttp: []) +service ValidService3 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: ["http/1.1"], eventStreamHttp: []) +service ValidService4 { + version: "2023-02-10" +} + +@rpcv2Cbor(eventStreamHttp: ["http/1.1"]) +service InvalidService1 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["http/1.1"]) +service InvalidService2 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["h2", "http/1.1", "h2c"]) +service InvalidService3 { + version: "2023-02-10" +} diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.errors b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.errors new file mode 100644 index 00000000000..1062699e1ef --- /dev/null +++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.errors @@ -0,0 +1 @@ +[ERROR] smithy.example#DocumentOperationInput$document: Found an incompatible shape when validating the constraints of the `smithy.protocols#rpcv2Cbor` trait attached to `smithy.example#DocumentService`: This protocol does not support document types | rpcv2Cbor.NoDocuments diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.smithy b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.smithy new file mode 100644 index 00000000000..10ed73af30b --- /dev/null +++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.smithy @@ -0,0 +1,19 @@ +$version: "2.0" + +namespace smithy.example + +use smithy.protocols#rpcv2Cbor + +@rpcv2Cbor +service DocumentService { + version: "2023-02-10" + operations: [ + DocumentOperation + ] +} + +operation DocumentOperation { + input := { + document: Document + } +}