Skip to content

Commit 2b3567e

Browse files
committed
Relax httpPrefixHeaders constraint
This commit relaxes the constraint on the `@httpPrefixHeaders` trait when the prefix is set to an empty string, lowering the validation's severity to a NOTE. This case is usualy meant for proxying all headers through a request or response, where conflicts are well understood. This change comes with updated guidance to simplify the strategy for serializing HTTP messages, examples for these use cases, guidance on how to do this in a readable way, and other minor HTTP binding specification cleanup.
1 parent 84d3ccb commit 2b3567e

File tree

5 files changed

+550
-859
lines changed

5 files changed

+550
-859
lines changed

docs/source-2.0/spec/http-bindings.rst

Lines changed: 115 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -643,8 +643,8 @@ Conflicts with
643643
:ref:`httpPayload-trait`,
644644
:ref:`httpResponseCode-trait`
645645

646-
``httpHeader`` serialization rules:
647-
-----------------------------------
646+
Serialization rules
647+
-------------------
648648

649649
* When a :ref:`list <list>` shape is targeted, each member of the shape is
650650
serialized as a separate HTTP header either by concatenating the values
@@ -792,8 +792,8 @@ Applying the ``httpLabel`` trait to members
792792
* If the corresponding URI label in the operation is greedy, then the
793793
``httpLabel`` trait MUST target a member that targets a ``string`` shape.
794794

795-
``httpLabel`` serialization rules
796-
---------------------------------
795+
Serialization rules
796+
-------------------
797797

798798
- ``boolean`` values are serialized as ``true`` or ``false``.
799799
- ``timestamp`` values are serialized as an :rfc:`3339` string by default
@@ -935,7 +935,6 @@ Structurally exclusive
935935

936936
Given the following Smithy model:
937937

938-
939938
.. code-block:: smithy
940939
941940
@readonly
@@ -975,6 +974,56 @@ An example HTTP request would be serialized as:
975974
X-Foo-first: hi
976975
X-Foo-second: there
977976

977+
Given the following Smithy model that also uses the ``httpHeader`` trait:
978+
979+
.. code-block:: smithy
980+
981+
@readonly
982+
@http(method: "GET", uri: "/myOperation")
983+
operation MyOperation {
984+
input: MyOperationInput
985+
}
986+
987+
@input
988+
structure MyOperationInput {
989+
@httpPrefixHeaders("X-Foo-")
990+
headers: MapOfStrings
991+
992+
@httpHeader("X-Foo-Value")
993+
foo: String
994+
}
995+
996+
map MapOfStrings {
997+
key: String
998+
value: String
999+
}
1000+
1001+
And given the following input to ``MyOperation``:
1002+
1003+
.. code-block:: json
1004+
1005+
{
1006+
"headers": {
1007+
"Value": "not sent"
1008+
}
1009+
"foo": "resolved"
1010+
}
1011+
1012+
An example HTTP request would be serialized as:
1013+
1014+
::
1015+
1016+
GET /myOperation
1017+
Host: <server>
1018+
X-Foo-Value: resolved
1019+
1020+
.. note::
1021+
1022+
If using both ``httpPrefixHeaders`` and ``httpHeader``, placing the member
1023+
that uses ``httpPrefixHeaders`` ahead of those that use ``httpHeader`` can
1024+
help readers understand the precedence for
1025+
:ref:`serializing HTTP messages. <serializing-http-messages>`
1026+
9781027
Disambiguation of ``httpPrefixHeaders``
9791028
---------------------------------------
9801029

@@ -1134,7 +1183,7 @@ target input map as query string parameters in an HTTP request:
11341183
11351184
@input
11361185
structure ListThingsInput {
1137-
@httpQueryParams()
1186+
@httpQueryParams
11381187
myParams: MapOfStrings
11391188
}
11401189
@@ -1143,6 +1192,56 @@ target input map as query string parameters in an HTTP request:
11431192
value: String
11441193
}
11451194
1195+
1196+
Given the following Smithy model that also uses the ``httpQuery`` trait:
1197+
1198+
.. code-block:: smithy
1199+
1200+
@readonly
1201+
@http(method: "GET", uri: "/myOperation")
1202+
operation MyOperation {
1203+
input: MyOperationInput
1204+
}
1205+
1206+
@input
1207+
structure MyOperationInput {
1208+
@httpQueryParams
1209+
query: MapOfStrings
1210+
1211+
@httpQuery
1212+
foo: String
1213+
}
1214+
1215+
map MapOfStrings {
1216+
key: String
1217+
value: String
1218+
}
1219+
1220+
And given the following input to ``MyOperation``:
1221+
1222+
.. code-block:: json
1223+
1224+
{
1225+
"query": {
1226+
"foo": "not sent"
1227+
}
1228+
"foo": "resolved"
1229+
}
1230+
1231+
An example HTTP request would be serialized as:
1232+
1233+
::
1234+
1235+
GET /myOperation?foo=resolved
1236+
Host: <server>
1237+
1238+
.. note::
1239+
1240+
If using both ``httpQueryParams`` and ``httpQuery``, placing the member
1241+
that uses ``httpQueryParams`` ahead of those that use ``httpQuery`` can
1242+
help readers understand the precedence for
1243+
:ref:`serializing HTTP messages. <serializing-http-messages>`
1244+
11461245
Serialization rules
11471246
-------------------
11481247

@@ -1378,6 +1477,7 @@ See
13781477
output: PutSomethingOutput
13791478
}
13801479
1480+
.. _serializing-http-messages:
13811481

13821482
Serializing HTTP messages
13831483
=========================
@@ -1393,14 +1493,14 @@ parameters:
13931493
corresponding structure member by name:
13941494

13951495
1. If the member has the ``httpLabel`` trait, expand the value into the URI.
1396-
2. If the member has the ``httpQuery`` trait, serialize the value into the
1397-
HTTP request as a query string parameter.
1398-
3. If the member has the ``httpQueryParams`` trait, serialize the values into
1496+
2. If the member has the ``httpQueryParams`` trait, serialize the values into
13991497
the HTTP request as query string parameters.
1400-
4. If the member has the ``httpHeader`` trait, serialize the value in an
1401-
HTTP header using the value of the ``httpHeader`` trait.
1402-
5. If the member has the ``httpPrefixHeaders`` trait and the value is a map,
1498+
3. If the member has the ``httpQuery`` trait, serialize the value into the
1499+
HTTP request as a query string parameter.
1500+
4. If the member has the ``httpPrefixHeaders`` trait and the value is a map,
14031501
serialize the map key value pairs as prefixed HTTP headers.
1502+
5. If the member has the ``httpHeader`` trait, serialize the value in an
1503+
HTTP header using the value of the ``httpHeader`` trait.
14041504
6. If the member has the ``httpPayload`` trait, serialize the value as the
14051505
body of the request.
14061506
7. If the member has no bindings, serialize the key-value pair as part of a
@@ -1418,10 +1518,10 @@ parameters:
14181518
3. Iterate over all of the key-value pairs of the parameters and find the
14191519
corresponding structure member by name:
14201520

1421-
1. If the member has the ``httpHeader`` trait, serialize the value in an
1422-
HTTP header using the value of the ``httpHeader`` trait.
1423-
2. If the member has the ``httpPrefixHeaders`` trait and the value is a map,
1521+
1. If the member has the ``httpPrefixHeaders`` trait and the value is a map,
14241522
serialize the map key value pairs as prefixed HTTP headers.
1523+
2. If the member has the ``httpHeader`` trait, serialize the value in an
1524+
HTTP header using the value of the ``httpHeader`` trait.
14251525
3. If the member has the ``httpPayload`` trait, serialize the value as the
14261526
body of the response.
14271527
4. If the member has no bindings, serialize the key-value pair as part of a

smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPrefixHeadersTraitValidator.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
import software.amazon.smithy.model.traits.HttpHeaderTrait;
1515
import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait;
1616
import software.amazon.smithy.model.validation.AbstractValidator;
17+
import software.amazon.smithy.model.validation.Severity;
1718
import software.amazon.smithy.model.validation.ValidationEvent;
19+
import software.amazon.smithy.utils.StringUtils;
1820

1921
/**
2022
* Validates that httpHeader traits do not case-insensitively start with an
@@ -41,23 +43,37 @@ private List<ValidationEvent> validateMember(
4143
) {
4244
List<ValidationEvent> events = new ArrayList<>();
4345
String prefix = prefixTrait.getValue().toLowerCase(Locale.ENGLISH);
46+
Severity severity = Severity.ERROR;
47+
String detail =
48+
"`httpHeader` bindings must not case-insensitively start with any `httpPrefixHeaders` bindings.";
49+
if (StringUtils.isEmpty(prefix)) {
50+
severity = Severity.NOTE;
51+
detail = String.format(
52+
"The service will not be able to disambiguate between header parameters intended for the `%s` "
53+
+ "member and those explicitly bound to the `httpHeader` members.",
54+
member.getId());
55+
}
4456

4557
// Find all structure members that case-insensitively start with the same prefix.
4658
for (MemberShape otherMember : structure.getAllMembers().values()) {
47-
otherMember.getTrait(HttpHeaderTrait.class).ifPresent(httpHeaderTrait -> {
59+
if (otherMember.hasTrait(HttpHeaderTrait.ID)) {
60+
HttpHeaderTrait httpHeaderTrait = otherMember.expectTrait(HttpHeaderTrait.class);
4861
String lowerCaseHeader = httpHeaderTrait.getValue().toLowerCase(Locale.ENGLISH);
62+
4963
if (lowerCaseHeader.startsWith(prefix)) {
50-
events.add(error(otherMember,
64+
events.add(createEvent(
65+
severity,
66+
otherMember,
5167
httpHeaderTrait,
5268
String.format(
5369
"`httpHeader` binding of `%s` conflicts with the `httpPrefixHeaders` binding of `%s` "
54-
+ "to `%s`. `httpHeader` bindings must not case-insensitively start with any "
55-
+ "`httpPrefixHeaders` bindings.",
70+
+ "to `%s`. %s",
5671
lowerCaseHeader,
5772
member.getId(),
58-
prefix)));
73+
prefix,
74+
detail)));
5975
}
60-
});
76+
}
6177
}
6278

6379
return events;

smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
[DANGER] ns.foo#MInput$a: `Authorization` is not an allowed HTTP header binding | HttpHeaderTrait
2222
[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
2323
[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
24-
[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
24+
[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
25+
[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
2526
[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
2627
[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
2728
[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

0 commit comments

Comments
 (0)