Skip to content

Commit cbbae6b

Browse files
Split singleton headers
Header lists may be serialized either as the same header multiple times, or as a comma-delimted string. This updates the header list deserializer to split a list value if there is only one. It takes care to ensure that lists of http-date headers are supported with and without quoting.
1 parent 1ee8ede commit cbbae6b

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

packages/smithy-http/src/smithy_http/deserializers.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from .aio.interfaces import HTTPResponse
2424
from .bindings import Binding, ResponseBindingMatcher
2525
from .interfaces import Field, Fields
26+
from .utils import split_header
2627

2728
if TYPE_CHECKING:
2829
from smithy_core.aio.interfaces import StreamingBlob as AsyncStreamingBlob
@@ -205,7 +206,17 @@ def __init__(self, field: Field) -> None:
205206
def read_list(
206207
self, schema: Schema, consumer: Callable[["ShapeDeserializer"], None]
207208
) -> None:
208-
for value in self._field.values:
209+
values = self._field.values
210+
if len(values) == 1:
211+
is_http_date_list = False
212+
value_schema = schema.members["member"]
213+
if value_schema.shape_type is ShapeType.TIMESTAMP:
214+
trait = value_schema.get_trait(TimestampFormatTrait)
215+
is_http_date_list = (
216+
trait is None or trait.format is TimestampFormat.HTTP_DATE
217+
)
218+
values = split_header(values[0], is_http_date_list)
219+
for value in values:
209220
consumer(HTTPHeaderDeserializer(value))
210221

211222

packages/smithy-http/tests/unit/test_serializers.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,90 @@ def header_cases() -> list[HTTPMessageTestCase]:
12501250
]
12511251

12521252

1253+
def header_deser_cases() -> list[HTTPMessageTestCase]:
1254+
return [
1255+
HTTPMessageTestCase(
1256+
HTTPHeaders(string_list_member=["foo", "bar", "baz"]),
1257+
HTTPMessage(fields=tuples_to_fields([("stringList", "foo, bar, baz")])),
1258+
),
1259+
HTTPMessageTestCase(
1260+
HTTPHeaders(string_list_member=["foo, bar", "spam", "eggs"]),
1261+
HTTPMessage(
1262+
fields=tuples_to_fields([("stringList", '"foo, bar", spam, eggs')])
1263+
),
1264+
),
1265+
HTTPMessageTestCase(
1266+
HTTPHeaders(
1267+
http_date_list_timestamp_member=[
1268+
datetime.datetime(2025, 1, 1, tzinfo=UTC),
1269+
datetime.datetime(2024, 1, 1, tzinfo=UTC),
1270+
]
1271+
),
1272+
HTTPMessage(
1273+
fields=tuples_to_fields(
1274+
[
1275+
(
1276+
"httpDateListTimestamp",
1277+
"Wed, 01 Jan 2025 00:00:00 GMT, Mon, 01 Jan 2024 00:00:00 GMT",
1278+
),
1279+
]
1280+
),
1281+
),
1282+
),
1283+
HTTPMessageTestCase(
1284+
HTTPHeaders(
1285+
http_date_list_timestamp_member=[
1286+
datetime.datetime(2025, 1, 1, tzinfo=UTC),
1287+
datetime.datetime(2024, 1, 1, tzinfo=UTC),
1288+
]
1289+
),
1290+
HTTPMessage(
1291+
fields=tuples_to_fields(
1292+
[
1293+
(
1294+
"httpDateListTimestamp",
1295+
'"Wed, 01 Jan 2025 00:00:00 GMT", "Mon, 01 Jan 2024 00:00:00 GMT"',
1296+
),
1297+
]
1298+
),
1299+
),
1300+
),
1301+
HTTPMessageTestCase(
1302+
HTTPHeaders(
1303+
date_time_list_timestamp_member=[
1304+
datetime.datetime(2025, 1, 1, tzinfo=UTC),
1305+
datetime.datetime(2024, 1, 1, tzinfo=UTC),
1306+
]
1307+
),
1308+
HTTPMessage(
1309+
fields=tuples_to_fields(
1310+
[
1311+
(
1312+
"dateTimeListTimestamp",
1313+
"2025-01-01T00:00:00Z, 2024-01-01T00:00:00Z",
1314+
),
1315+
]
1316+
),
1317+
),
1318+
),
1319+
HTTPMessageTestCase(
1320+
HTTPHeaders(
1321+
epoch_list_timestamp_member=[
1322+
datetime.datetime(2025, 1, 1, tzinfo=UTC),
1323+
datetime.datetime(2024, 1, 1, tzinfo=UTC),
1324+
]
1325+
),
1326+
HTTPMessage(
1327+
fields=tuples_to_fields(
1328+
[
1329+
("epochListTimestamp", "1735689600, 1704067200"),
1330+
]
1331+
),
1332+
),
1333+
),
1334+
]
1335+
1336+
12531337
def empty_prefix_header_ser_cases() -> list[HTTPMessageTestCase]:
12541338
return [
12551339
HTTPMessageTestCase(
@@ -1714,9 +1798,9 @@ async def test_serialize_response_omitting_empty_payload() -> None:
17141798

17151799
RESPONSE_DESER_CASES: list[HTTPMessageTestCase] = (
17161800
header_cases()
1801+
+ header_deser_cases()
17171802
+ empty_prefix_header_deser_cases()
17181803
+ payload_cases()
1719-
+ response_payload_cases()
17201804
)
17211805

17221806

0 commit comments

Comments
 (0)