diff --git a/docs/source-2.0/spec/http-bindings.rst b/docs/source-2.0/spec/http-bindings.rst index a51a209755b..a31c63f235e 100644 --- a/docs/source-2.0/spec/http-bindings.rst +++ b/docs/source-2.0/spec/http-bindings.rst @@ -643,8 +643,8 @@ Conflicts with :ref:`httpPayload-trait`, :ref:`httpResponseCode-trait` -``httpHeader`` serialization rules: ------------------------------------ +Serialization rules +------------------- * When a :ref:`list ` shape is targeted, each member of the shape is serialized as a separate HTTP header either by concatenating the values @@ -792,8 +792,8 @@ Applying the ``httpLabel`` trait to members * If the corresponding URI label in the operation is greedy, then the ``httpLabel`` trait MUST target a member that targets a ``string`` shape. -``httpLabel`` serialization rules ---------------------------------- +Serialization rules +------------------- - ``boolean`` values are serialized as ``true`` or ``false``. - ``timestamp`` values are serialized as an :rfc:`3339` string by default @@ -935,7 +935,6 @@ Structurally exclusive Given the following Smithy model: - .. code-block:: smithy @readonly @@ -975,14 +974,61 @@ An example HTTP request would be serialized as: X-Foo-first: hi X-Foo-second: there +Given the following Smithy model that also uses the ``httpHeader`` trait: + +.. code-block:: smithy + + @readonly + @http(method: "GET", uri: "/myOperation") + operation MyOperation { + input: MyOperationInput + } + + @input + structure MyOperationInput { + @httpPrefixHeaders("X-Foo-") + headers: MapOfStrings + + @httpHeader("X-Foo-Value") + foo: String + } + + map MapOfStrings { + key: String + value: String + } + +And given the following input to ``MyOperation``: + +.. code-block:: json + + { + "headers": { + "Value": "not sent" + } + "foo": "resolved" + } + +An example HTTP request would be serialized as: + +:: + + GET /myOperation + Host: + X-Foo-Value: resolved + + Disambiguation of ``httpPrefixHeaders`` --------------------------------------- In order to differentiate ``httpPrefixHeaders`` from other headers, when -``httpPrefixHeaders`` are used, no other :ref:`httpHeader-trait` bindings can -start with the same prefix provided in ``httpPrefixHeaders`` trait. If -``httpPrefixHeaders`` is set to an empty string, then no other members can be -bound to ``headers``. +``httpPrefixHeaders`` are used with a non-empty string, no other +:ref:`httpHeader-trait` bindings can start with the same prefix provided in +``httpPrefixHeaders`` trait. + +If ``httpPrefixHeaders`` is set to an empty string, then other members can be +bound to ``headers``. However, this can lead to ambiguity on the source of +provided header values. .. note:: @@ -1134,7 +1180,7 @@ target input map as query string parameters in an HTTP request: @input structure ListThingsInput { - @httpQueryParams() + @httpQueryParams myParams: MapOfStrings } @@ -1143,6 +1189,50 @@ target input map as query string parameters in an HTTP request: value: String } + +Given the following Smithy model that also uses the ``httpQuery`` trait: + +.. code-block:: smithy + + @readonly + @http(method: "GET", uri: "/myOperation") + operation MyOperation { + input: MyOperationInput + } + + @input + structure MyOperationInput { + @httpQueryParams + query: MapOfStrings + + @httpQuery + foo: String + } + + map MapOfStrings { + key: String + value: String + } + +And given the following input to ``MyOperation``: + +.. code-block:: json + + { + "query": { + "foo": "not sent" + } + "foo": "resolved" + } + +An example HTTP request would be serialized as: + +:: + + GET /myOperation?foo=resolved + Host: + + Serialization rules ------------------- @@ -1378,6 +1468,7 @@ See output: PutSomethingOutput } +.. _serializing-http-messages: Serializing HTTP messages ========================= @@ -1393,14 +1484,14 @@ parameters: corresponding structure member by name: 1. If the member has the ``httpLabel`` trait, expand the value into the URI. - 2. If the member has the ``httpQuery`` trait, serialize the value into the - HTTP request as a query string parameter. - 3. If the member has the ``httpQueryParams`` trait, serialize the values into + 2. If the member has the ``httpQueryParams`` trait, serialize the values into the HTTP request as query string parameters. - 4. If the member has the ``httpHeader`` trait, serialize the value in an - HTTP header using the value of the ``httpHeader`` trait. - 5. If the member has the ``httpPrefixHeaders`` trait and the value is a map, + 3. If the member has the ``httpQuery`` trait, serialize the value into the + HTTP request as a query string parameter. + 4. If the member has the ``httpPrefixHeaders`` trait and the value is a map, serialize the map key value pairs as prefixed HTTP headers. + 5. If the member has the ``httpHeader`` trait, serialize the value in an + HTTP header using the value of the ``httpHeader`` trait. 6. If the member has the ``httpPayload`` trait, serialize the value as the body of the request. 7. If the member has no bindings, serialize the key-value pair as part of a @@ -1418,10 +1509,10 @@ parameters: 3. Iterate over all of the key-value pairs of the parameters and find the corresponding structure member by name: - 1. If the member has the ``httpHeader`` trait, serialize the value in an - HTTP header using the value of the ``httpHeader`` trait. - 2. If the member has the ``httpPrefixHeaders`` trait and the value is a map, + 1. If the member has the ``httpPrefixHeaders`` trait and the value is a map, serialize the map key value pairs as prefixed HTTP headers. + 2. If the member has the ``httpHeader`` trait, serialize the value in an + HTTP header using the value of the ``httpHeader`` trait. 3. If the member has the ``httpPayload`` trait, serialize the value as the body of the response. 4. If the member has no bindings, serialize the key-value pair as part of a diff --git a/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy index 482e8fa25cf..e105b048c77 100644 --- a/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy @@ -149,3 +149,105 @@ structure HttpPrefixHeadersInResponseOutput { @httpPrefixHeaders("") prefixHeaders: StringMap, } + +/// Clients that perform this test extract all headers from the response. +@readonly +@http(uri: "/HttpEmptyPrefixHeaders", method: "GET") +operation HttpEmptyPrefixHeaders { + input := { + @httpPrefixHeaders("") + prefixHeaders: StringMap + + @httpHeader("hello") + specificHeader: String + } + output := { + @httpPrefixHeaders("") + prefixHeaders: StringMap + + @httpHeader("hello") + specificHeader: String + } +} + +apply HttpEmptyPrefixHeaders @httpRequestTests([ + { + id: "RestJsonHttpEmptyPrefixHeadersRequestClient" + documentation: "Serializes all request headers, using specific when present" + protocol: restJson1 + method: "GET" + uri: "/HttpEmptyPrefixHeaders" + body: "" + headers: { + "x-foo": "Foo", + "hello": "There" + } + params: { + prefixHeaders: { + "x-foo": "Foo", + "hello": "Hello" + } + specificHeader: "There" + } + appliesTo: "client" + } + { + id: "RestJsonHttpEmptyPrefixHeadersRequestServer" + documentation: "Deserializes all request headers with the same for prefix and specific" + protocol: restJson1 + method: "GET" + uri: "/HttpEmptyPrefixHeaders" + body: "" + headers: { + "x-foo": "Foo", + "hello": "There" + } + params: { + prefixHeaders: { + "x-foo": "Foo", + "hello": "There" + } + specificHeader: "There" + } + appliesTo: "server" + } +]) + +apply HttpEmptyPrefixHeaders @httpResponseTests([ + { + id: "RestJsonHttpEmptyPrefixHeadersResponseClient" + documentation: "Deserializes all response headers with the same for prefix and specific" + protocol: restJson1 + code: 200 + headers: { + "x-foo": "Foo", + "hello": "There" + } + params: { + prefixHeaders: { + "x-foo": "Foo", + "hello": "There" + } + specificHeader: "There" + } + appliesTo: "client" + } + { + id: "RestJsonHttpEmptyPrefixHeadersResponseServer" + documentation: "Serializes all response headers, using specific when present" + protocol: restJson1 + code: 200 + headers: { + "x-foo": "Foo", + "hello": "There" + } + params: { + prefixHeaders: { + "x-foo": "Foo", + "hello": "Hello" + } + specificHeader: "There" + } + appliesTo: "server" + } +]) diff --git a/smithy-aws-protocol-tests/model/restJson1/main.smithy b/smithy-aws-protocol-tests/model/restJson1/main.smithy index aa0f1589ae3..33321eb69eb 100644 --- a/smithy-aws-protocol-tests/model/restJson1/main.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/main.smithy @@ -54,6 +54,7 @@ service RestJson { // @httpPrefixHeaders tests HttpPrefixHeaders, HttpPrefixHeadersInResponse, + HttpEmptyPrefixHeaders, // @httpPayload tests HttpPayloadTraits, diff --git a/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy index a69fc8d4f8a..09d1369699a 100644 --- a/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy +++ b/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy @@ -6,6 +6,7 @@ $version: "2.0" namespace aws.protocoltests.restxml use aws.protocols#restXml +use aws.protocoltests.shared#StringMap use smithy.test#httpRequestTests use smithy.test#httpResponseTests @@ -122,3 +123,105 @@ map FooPrefixHeaders { key: String, value: String, } + +/// Clients that perform this test extract all headers from the response. +@readonly +@http(uri: "/HttpEmptyPrefixHeaders", method: "GET") +operation HttpEmptyPrefixHeaders { + input := { + @httpPrefixHeaders("") + prefixHeaders: StringMap + + @httpHeader("hello") + specificHeader: String + } + output := { + @httpPrefixHeaders("") + prefixHeaders: StringMap + + @httpHeader("hello") + specificHeader: String + } +} + +apply HttpEmptyPrefixHeaders @httpRequestTests([ + { + id: "HttpEmptyPrefixHeadersRequestClient" + documentation: "Serializes all request headers, using specific when present" + protocol: restXml + method: "GET" + uri: "/HttpEmptyPrefixHeaders" + body: "" + headers: { + "x-foo": "Foo", + "hello": "There" + } + params: { + prefixHeaders: { + "x-foo": "Foo", + "hello": "Hello" + } + specificHeader: "There" + } + appliesTo: "client" + } + { + id: "HttpEmptyPrefixHeadersRequestServer" + documentation: "Deserializes all request headers with the same for prefix and specific" + protocol: restXml + method: "GET" + uri: "/HttpEmptyPrefixHeaders" + body: "" + headers: { + "x-foo": "Foo", + "hello": "There" + } + params: { + prefixHeaders: { + "x-foo": "Foo", + "hello": "There" + } + specificHeader: "There" + } + appliesTo: "server" + } +]) + +apply HttpEmptyPrefixHeaders @httpResponseTests([ + { + id: "HttpEmptyPrefixHeadersResponseClient" + documentation: "Deserializes all response headers with the same for prefix and specific" + protocol: restXml + code: 200 + headers: { + "x-foo": "Foo", + "hello": "There" + } + params: { + prefixHeaders: { + "x-foo": "Foo", + "hello": "There" + } + specificHeader: "There" + } + appliesTo: "client" + } + { + id: "HttpEmptyPrefixHeadersResponseServer" + documentation: "Serializes all response headers, using specific when present" + protocol: restXml + code: 200 + headers: { + "x-foo": "Foo", + "hello": "There" + } + params: { + prefixHeaders: { + "x-foo": "Foo", + "hello": "Hello" + } + specificHeader: "There" + } + appliesTo: "server" + } +]) diff --git a/smithy-aws-protocol-tests/model/restXml/main.smithy b/smithy-aws-protocol-tests/model/restXml/main.smithy index afe0928bd8d..75380f4a8c3 100644 --- a/smithy-aws-protocol-tests/model/restXml/main.smithy +++ b/smithy-aws-protocol-tests/model/restXml/main.smithy @@ -48,6 +48,7 @@ service RestXml { // @httpPrefixHeaders tests HttpPrefixHeaders, + HttpEmptyPrefixHeaders, // @httpPayload tests HttpPayloadTraits, diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPrefixHeadersTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPrefixHeadersTraitValidator.java index 32634ce5757..724273bf52a 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPrefixHeadersTraitValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPrefixHeadersTraitValidator.java @@ -14,7 +14,9 @@ import software.amazon.smithy.model.traits.HttpHeaderTrait; import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait; import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.Severity; import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.utils.StringUtils; /** * Validates that httpHeader traits do not case-insensitively start with an @@ -41,23 +43,37 @@ private List validateMember( ) { List events = new ArrayList<>(); String prefix = prefixTrait.getValue().toLowerCase(Locale.ENGLISH); + Severity severity = Severity.ERROR; + String detail = + "`httpHeader` bindings must not case-insensitively start with any `httpPrefixHeaders` bindings."; + if (StringUtils.isEmpty(prefix)) { + severity = Severity.NOTE; + detail = String.format( + "The service will not be able to disambiguate between header parameters intended for the `%s` " + + "member and those explicitly bound to the `httpHeader` members.", + member.getId()); + } // Find all structure members that case-insensitively start with the same prefix. for (MemberShape otherMember : structure.getAllMembers().values()) { - otherMember.getTrait(HttpHeaderTrait.class).ifPresent(httpHeaderTrait -> { + if (otherMember.hasTrait(HttpHeaderTrait.ID)) { + HttpHeaderTrait httpHeaderTrait = otherMember.expectTrait(HttpHeaderTrait.class); String lowerCaseHeader = httpHeaderTrait.getValue().toLowerCase(Locale.ENGLISH); + if (lowerCaseHeader.startsWith(prefix)) { - events.add(error(otherMember, + events.add(createEvent( + severity, + otherMember, httpHeaderTrait, String.format( "`httpHeader` binding of `%s` conflicts with the `httpPrefixHeaders` binding of `%s` " - + "to `%s`. `httpHeader` bindings must not case-insensitively start with any " - + "`httpPrefixHeaders` bindings.", + + "to `%s`. %s", lowerCaseHeader, member.getId(), - prefix))); + prefix, + detail))); } - }); + } } return events; diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors index ea6d0c6c123..af2345b4840 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors @@ -21,7 +21,8 @@ [DANGER] ns.foo#MInput$a: `Authorization` is not an allowed HTTP header binding | HttpHeaderTrait [ERROR] ns.foo#NInput$a: This `a` structure member is marked with the `httpLabel` trait, but no corresponding `http` URI label could be found when used as the input of the `ns.foo#N` operation. | HttpLabelTrait [ERROR] ns.foo#PInput$a: The `a` structure member corresponds to a greedy label when used as the input of the `ns.foo#P` operation. This member targets (integer: `ns.foo#Integer`), but greedy labels must target string shapes. | HttpLabelTrait -[ERROR] ns.foo#RInput$b: `httpHeader` binding of `x-foo` conflicts with the `httpPrefixHeaders` binding of `ns.foo#RInput$a` to ``. `httpHeader` bindings must not case-insensitively start with any `httpPrefixHeaders` bindings. | HttpPrefixHeadersTrait +[NOTE] ns.foo#RInput$b: `httpHeader` binding of `x-foo` conflicts with the `httpPrefixHeaders` binding of `ns.foo#RInput$a` to ``. The service will not be able to disambiguate between header parameters intended for the `ns.foo#RInput$a` member and those explicitly bound to the `httpHeader` members. | HttpPrefixHeadersTrait +[NOTE] ns.foo#ROutput$b: `httpHeader` binding of `x-foo` conflicts with the `httpPrefixHeaders` binding of `ns.foo#ROutput$a` to ``. The service will not be able to disambiguate between header parameters intended for the `ns.foo#ROutput$a` member and those explicitly bound to the `httpHeader` members. | HttpPrefixHeadersTrait [ERROR] ns.foo#GInput$b: Trait `httpHeader` cannot be applied to `ns.foo#GInput$b`. This trait may only be applied to shapes that match the following selector: structure > :test(member > :test(boolean, number, string, timestamp, list > member > :test(boolean, number, string, timestamp))) | TraitTarget [ERROR] ns.foo#GOutput$b: Trait `httpHeader` cannot be applied to `ns.foo#GOutput$b`. This trait may only be applied to shapes that match the following selector: structure > :test(member > :test(boolean, number, string, timestamp, list > member > :test(boolean, number, string, timestamp))) | TraitTarget [ERROR] ns.foo#HInput$a: Trait `httpHeader` cannot be applied to `ns.foo#HInput$a`. This trait may only be applied to shapes that match the following selector: structure > :test(member > :test(boolean, number, string, timestamp, list > member > :test(boolean, number, string, timestamp))) | TraitTarget diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.json deleted file mode 100644 index a783497740d..00000000000 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.json +++ /dev/null @@ -1,837 +0,0 @@ -{ - "smithy": "2.0", - "shapes": { - "ns.foo#MyService": { - "type": "service", - "version": "2017-01-17", - "operations": [ - { - "target": "ns.foo#A" - }, - { - "target": "ns.foo#B" - }, - { - "target": "ns.foo#C" - }, - { - "target": "ns.foo#D" - }, - { - "target": "ns.foo#E" - }, - { - "target": "ns.foo#F" - }, - { - "target": "ns.foo#G" - }, - { - "target": "ns.foo#H" - }, - { - "target": "ns.foo#J" - }, - { - "target": "ns.foo#K" - }, - { - "target": "ns.foo#L" - }, - { - "target": "ns.foo#M" - }, - { - "target": "ns.foo#N" - }, - { - "target": "ns.foo#O" - }, - { - "target": "ns.foo#P" - }, - { - "target": "ns.foo#R" - }, - { - "target": "ns.foo#Q" - }, - { - "target": "ns.foo#ListPayload" - }, - { - "target": "ns.foo#SetPayload" - }, - { - "target": "ns.foo#MapPayload" - }, - { - "target": "ns.foo#QueryParams" - } - ] - }, - "ns.foo#A": { - "type": "operation", - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/A/{foo}", - "code": 200 - } - } - }, - "ns.foo#B": { - "type": "operation", - "input": { - "target": "ns.foo#BInput" - }, - "output": { - "target": "ns.foo#BOutput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/b/{d}", - "code": 200 - } - } - }, - "ns.foo#BInput": { - "type": "structure" - }, - "ns.foo#BOutput": { - "type": "structure" - }, - "ns.foo#C": { - "type": "operation", - "input": { - "target": "ns.foo#CInput" - }, - "output": { - "target": "ns.foo#COutput" - }, - "traits": { - "smithy.api#idempotent": {}, - "smithy.api#http": { - "method": "PUT", - "uri": "/c/{a}", - "code": 200 - } - } - }, - "ns.foo#CInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpLabel": {}, - "smithy.api#required": {} - } - }, - "b": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-B" - } - }, - "c": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPrefixHeaders": "X-C-" - } - }, - "d": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpQuery": "d" - } - }, - "e": { - "target": "ns.foo#Blob", - "traits": { - "smithy.api#httpPayload": {} - } - }, - "otherHeader": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Bb" - } - }, - "otherQuery": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpQuery": "otherQuery" - } - }, - "headerList": { - "target": "ns.foo#StringList", - "traits": { - "smithy.api#httpHeader": "X-Plural" - } - }, - "queryList": { - "target": "ns.foo#StringList", - "traits": { - "smithy.api#httpQuery": "queryList" - } - } - } - }, - "ns.foo#COutput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-B" - } - }, - "b": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPrefixHeaders": "X-B-" - } - }, - "c": { - "target": "ns.foo#Blob", - "traits": { - "smithy.api#httpPayload": {} - } - } - } - }, - "ns.foo#D": { - "type": "operation", - "input": { - "target": "ns.foo#DInput" - }, - "output": { - "target": "ns.foo#DOutput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/d", - "code": 200 - } - } - }, - "ns.foo#DInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Foo" - } - }, - "b": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Foo" - } - }, - "c": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Baz" - } - }, - "d": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Baz" - } - }, - "e": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPrefixHeaders": "X-Foo" - } - } - } - }, - "ns.foo#DOutput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Foo" - } - }, - "b": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Foo" - } - }, - "c": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPrefixHeaders": "X-Foo" - } - } - } - }, - "ns.foo#E": { - "type": "operation", - "input": { - "target": "ns.foo#EInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/e/{label1}/{label2}" - } - } - }, - "ns.foo#EInput": { - "type": "structure", - "members": { - "label1": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpLabel": {}, - "smithy.api#required": {} - } - }, - "label2": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpLabel": {}, - "smithy.api#required": {} - } - } - } - }, - "ns.foo#F": { - "type": "operation", - "input": { - "target": "ns.foo#FInputOutput" - }, - "output": { - "target": "ns.foo#FInputOutput" - }, - "traits": { - "smithy.api#idempotent": {}, - "smithy.api#http": { - "method": "PUT", - "uri": "/f", - "code": 201 - } - } - }, - "ns.foo#FInputOutput": { - "type": "structure", - "members": { - "foo": { - "target": "ns.foo#String" - } - } - }, - "ns.foo#G": { - "type": "operation", - "input": { - "target": "ns.foo#GInput" - }, - "output": { - "target": "ns.foo#GOutput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/g/{a}", - "code": 200 - } - } - }, - "ns.foo#GInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Foo", - "smithy.api#httpQuery": "a", - "smithy.api#httpPayload": {}, - "smithy.api#httpLabel": {}, - "smithy.api#required": {} - } - }, - "b": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpHeader": "Map-", - "smithy.api#httpPrefixHeaders": "X-C-" - } - } - } - }, - "ns.foo#GOutput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-B", - "smithy.api#httpPayload": {} - } - }, - "b": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpHeader": "Map-", - "smithy.api#httpPrefixHeaders": "X-B-" - } - } - } - }, - "ns.foo#H": { - "type": "operation", - "input": { - "target": "ns.foo#HInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/g" - } - } - }, - "ns.foo#HInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#Structure", - "traits": { - "smithy.api#httpHeader": "X-Foo" - } - } - } - }, - "ns.foo#J": { - "type": "operation", - "input": { - "target": "ns.foo#JInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/j" - } - } - }, - "ns.foo#JInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPrefixHeaders": "X-Foo-" - } - }, - "b": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPrefixHeaders": "X-Baz-" - } - } - } - }, - "ns.foo#K": { - "type": "operation", - "input": { - "target": "ns.foo#KInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/k" - } - } - }, - "ns.foo#KInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Foo" - } - }, - "b": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "x-foo" - } - }, - "c": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpQuery": "foo" - } - }, - "d": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpQuery": "foo" - } - } - } - }, - "ns.foo#L": { - "type": "operation", - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/k" - } - } - }, - "ns.foo#M": { - "type": "operation", - "input": { - "target": "ns.foo#MInput" - }, - "traits": { - "smithy.api#idempotent": {}, - "smithy.api#http": { - "method": "PUT", - "uri": "/m" - } - } - }, - "ns.foo#MInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "Authorization" - } - } - } - }, - "ns.foo#N": { - "type": "operation", - "input": { - "target": "ns.foo#NInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/n" - } - } - }, - "ns.foo#NInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpLabel": {}, - "smithy.api#required": {} - } - } - } - }, - "ns.foo#O": { - "type": "operation", - "input": { - "target": "ns.foo#OInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/o/{a}" - } - } - }, - "ns.foo#OInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpLabel": {} - } - } - } - }, - "ns.foo#P": { - "type": "operation", - "input": { - "target": "ns.foo#PInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/p/{a+}" - } - } - }, - "ns.foo#PInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#Integer", - "traits": { - "smithy.api#httpLabel": {}, - "smithy.api#required": {} - } - } - } - }, - "ns.foo#Q": { - "type": "operation", - "input": { - "target": "ns.foo#QInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/q" - } - } - }, - "ns.foo#QInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPrefixHeaders": "" - } - } - } - }, - "ns.foo#R": { - "type": "operation", - "input": { - "target": "ns.foo#RInput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#http": { - "method": "GET", - "uri": "/r" - } - } - }, - "ns.foo#RInput": { - "type": "structure", - "members": { - "a": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPrefixHeaders": "" - } - }, - "b": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Foo" - } - } - } - }, - "ns.foo#ListPayload": { - "type": "operation", - "input": { - "target": "ns.foo#ListPayloadInputOutput" - }, - "output": { - "target": "ns.foo#ListPayloadInputOutput" - }, - "traits": { - "smithy.api#http": { - "method": "POST", - "uri": "/list-payload" - } - } - }, - "ns.foo#ListPayloadInputOutput": { - "type": "structure", - "members": { - "listPayload": { - "target": "ns.foo#StringList", - "traits": { - "smithy.api#httpPayload": {} - } - } - } - }, - "ns.foo#SetPayload": { - "type": "operation", - "input": { - "target": "ns.foo#SetPayloadInputOutput" - }, - "output": { - "target": "ns.foo#SetPayloadInputOutput" - }, - "traits": { - "smithy.api#http": { - "method": "POST", - "uri": "/set-payload" - } - } - }, - "ns.foo#SetPayloadInputOutput": { - "type": "structure", - "members": { - "setPayload": { - "target": "ns.foo#StringSet", - "traits": { - "smithy.api#httpPayload": {} - } - } - } - }, - "ns.foo#MapPayload": { - "type": "operation", - "input": { - "target": "ns.foo#MapPayloadInputOutput" - }, - "output": { - "target": "ns.foo#MapPayloadInputOutput" - }, - "traits": { - "smithy.api#http": { - "method": "POST", - "uri": "/map-payload" - } - } - }, - "ns.foo#MapPayloadInputOutput": { - "type": "structure", - "members": { - "mapPayload": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpPayload": {} - } - } - } - }, - "ns.foo#QueryParams": { - "type": "operation", - "input": { - "target": "ns.foo#QueryParamsInput" - }, - "output": { - "target": "ns.foo#QueryParamsOutput" - }, - "traits": { - "smithy.api#http": { - "method": "POST", - "uri": "/query-params" - } - } - }, - "ns.foo#QueryParamsInput": { - "type": "structure", - "members": { - "namedQuery": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpQuery": "named" - } - }, - "otherNamedQuery": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpQuery": "otherNamed" - } - }, - "queryParams": { - "target": "ns.foo#MapOfString", - "traits": { - "smithy.api#httpQueryParams": {} - } - } - } - }, - "ns.foo#QueryParamsOutput": { - "type": "structure", - "members": { - "foo": { - "target": "ns.foo#String" - } - } - }, - "ns.foo#Integer": { - "type": "integer" - }, - "ns.foo#Blob": { - "type": "blob" - }, - "ns.foo#String": { - "type": "string" - }, - "ns.foo#StringList": { - "type": "list", - "member": { - "target": "ns.foo#String" - } - }, - "ns.foo#StringSet": { - "type": "list", - "member": { - "target": "ns.foo#String" - }, - "traits": { - "smithy.api#uniqueItems": {} - } - }, - "ns.foo#Structure": { - "type": "structure" - }, - "ns.foo#MapOfString": { - "type": "map", - "key": { - "target": "ns.foo#String" - }, - "value": { - "target": "ns.foo#String" - } - }, - "ns.foo#BadError": { - "type": "structure", - "traits": { - "smithy.api#httpError": 404 - } - }, - "ns.foo#BadErrorMultipleBindings": { - "type": "structure", - "members": { - "foo": { - "target": "ns.foo#String", - "traits": { - "smithy.api#httpHeader": "X-Foo", - "smithy.api#httpPayload": {} - } - } - }, - "traits": { - "smithy.api#error": "client" - } - } - } -} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.smithy new file mode 100644 index 00000000000..19b48447b59 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.smithy @@ -0,0 +1,411 @@ +$version: "2.0" + +namespace ns.foo + +service MyService { + version: "2017-01-17" + operations: [ + A + B + C + D + E + F + G + H + J + K + L + ListPayload + M + MapPayload + N + O + P + Q + QueryParams + R + SetPayload + ] +} + +@http(method: "GET", uri: "/A/{foo}", code: 200) +@readonly +operation A { + input: Unit + output: Unit +} + +@http(method: "GET", uri: "/b/{d}", code: 200) +@readonly +operation B { + input: BInput + output: BOutput +} + +@http(method: "PUT", uri: "/c/{a}", code: 200) +@idempotent +operation C { + input: CInput + output: COutput +} + +@http(method: "GET", uri: "/d", code: 200) +@readonly +operation D { + input: DInput + output: DOutput +} + +@http(method: "GET", uri: "/e/{label1}/{label2}") +@readonly +operation E { + input: EInput + output: Unit +} + +@http(method: "PUT", uri: "/f", code: 201) +@idempotent +operation F { + input: FInputOutput + output: FInputOutput +} + +@http(method: "GET", uri: "/g/{a}", code: 200) +@readonly +operation G { + input: GInput + output: GOutput +} + +@http(method: "GET", uri: "/g") +@readonly +operation H { + input: HInput + output: Unit +} + +@http(method: "GET", uri: "/j") +@readonly +operation J { + input: JInput + output: Unit +} + +@http(method: "GET", uri: "/k") +@readonly +operation K { + input: KInput + output: Unit +} + +@http(method: "GET", uri: "/k") +@readonly +operation L { + input: Unit + output: Unit +} + +@http(method: "POST", uri: "/list-payload") +operation ListPayload { + input: ListPayloadInputOutput + output: ListPayloadInputOutput +} + +@http(method: "PUT", uri: "/m") +@idempotent +operation M { + input: MInput + output: Unit +} + +@http(method: "POST", uri: "/map-payload") +operation MapPayload { + input: MapPayloadInputOutput + output: MapPayloadInputOutput +} + +@http(method: "GET", uri: "/n") +@readonly +operation N { + input: NInput + output: Unit +} + +@http(method: "GET", uri: "/o/{a}") +@readonly +operation O { + input: OInput + output: Unit +} + +@http(method: "GET", uri: "/p/{a+}") +@readonly +operation P { + input: PInput + output: Unit +} + +@http(method: "GET", uri: "/q") +@readonly +operation Q { + input: QInput + output: Unit +} + +@http(method: "POST", uri: "/query-params") +operation QueryParams { + input: QueryParamsInput + output: QueryParamsOutput +} + +@http(method: "GET", uri: "/r") +@readonly +operation R { + input := { + @httpPrefixHeaders("") + a: MapOfString + + @httpHeader("X-Foo") + b: String + } + output := { + @httpPrefixHeaders("") + a: MapOfString + + @httpHeader("X-Foo") + b: String + } +} + +@http(method: "POST", uri: "/set-payload") +operation SetPayload { + input: SetPayloadInputOutput + output: SetPayloadInputOutput +} + +@httpError(404) +structure BadError {} + +@error("client") +structure BadErrorMultipleBindings { + @httpHeader("X-Foo") + @httpPayload + foo: String +} + +structure BInput {} + +structure BOutput {} + +structure CInput { + @httpLabel + @required + a: String + + @httpHeader("X-B") + b: String + + @httpPrefixHeaders("X-C-") + c: MapOfString + + @httpQuery("d") + d: String + + @httpPayload + e: Blob + + @httpHeader("X-Bb") + otherHeader: String + + @httpQuery("otherQuery") + otherQuery: String + + @httpHeader("X-Plural") + headerList: StringList + + @httpQuery("queryList") + queryList: StringList +} + +structure COutput { + @httpHeader("X-B") + a: String + + @httpPrefixHeaders("X-B-") + b: MapOfString + + @httpPayload + c: Blob +} + +structure DInput { + @httpHeader("X-Foo") + a: String + + @httpHeader("X-Foo") + b: String + + @httpHeader("X-Baz") + c: String + + @httpHeader("X-Baz") + d: String + + @httpPrefixHeaders("X-Foo") + e: MapOfString +} + +structure DOutput { + @httpHeader("X-Foo") + a: String + + @httpHeader("X-Foo") + b: String + + @httpPrefixHeaders("X-Foo") + c: MapOfString +} + +structure EInput { + @httpLabel + @required + label1: String + + @httpLabel + @required + label2: MapOfString +} + +structure FInputOutput { + foo: String +} + +structure GInput { + @httpHeader("X-Foo") + @httpLabel + @httpPayload + @httpQuery("a") + @required + a: String + + @httpHeader("Map-") + @httpPrefixHeaders("X-C-") + b: MapOfString +} + +structure GOutput { + @httpHeader("X-B") + @httpPayload + a: String + + @httpHeader("Map-") + @httpPrefixHeaders("X-B-") + b: MapOfString +} + +structure HInput { + @httpHeader("X-Foo") + a: Structure +} + +structure JInput { + @httpPrefixHeaders("X-Foo-") + a: MapOfString + + @httpPrefixHeaders("X-Baz-") + b: MapOfString +} + +structure KInput { + @httpHeader("X-Foo") + a: String + + @httpHeader("x-foo") + b: String + + @httpQuery("foo") + c: String + + @httpQuery("foo") + d: String +} + +structure ListPayloadInputOutput { + @httpPayload + listPayload: StringList +} + +structure MapPayloadInputOutput { + @httpPayload + mapPayload: MapOfString +} + +structure MInput { + @httpHeader("Authorization") + a: String +} + +structure NInput { + @httpLabel + @required + a: String +} + +structure OInput { + @httpLabel + a: String +} + +structure PInput { + @httpLabel + @required + a: Integer +} + +structure QInput { + @httpPrefixHeaders("") + a: MapOfString +} + +structure QueryParamsInput { + @httpQuery("named") + namedQuery: String + + @httpQuery("otherNamed") + otherNamedQuery: String + + @httpQueryParams + queryParams: MapOfString +} + +structure QueryParamsOutput { + foo: String +} + +structure SetPayloadInputOutput { + @httpPayload + setPayload: StringSet +} + +structure Structure {} + +list StringList { + member: String +} + +@uniqueItems +list StringSet { + member: String +} + +map MapOfString { + key: String + value: String +} + +blob Blob + +integer Integer + +string String