Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.

Commit cc64c6a

Browse files
committed
fix query override
1 parent 8857831 commit cc64c6a

2 files changed

Lines changed: 43 additions & 90 deletions

File tree

localstack-core/localstack/aws/protocol/serializer.py

Lines changed: 43 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -660,15 +660,23 @@ def _get_error_message(self, error: Exception) -> str | None:
660660
def _get_error_status_code(
661661
self, error: ServiceException, headers: Headers, service_model: ServiceModel
662662
) -> int:
663-
return error.status_code
663+
# by default, some protocols (namely `json` and `smithy-rpc-v2-cbor`) might not define exception status code in
664+
# their specs, so they are not defined in the `ServiceException` object and will use the default value of `400`
665+
# But Query compatible service always do define them, so we get the wrong code for service that are
666+
# multi-protocols like CloudWatch
667+
# we need to verify if the service is compatible, and if the client has requested the query compatible error
668+
# code to return the right value
669+
if not service_model.is_query_compatible:
670+
return error.status_code
664671

665-
def _get_error_code(
666-
self,
667-
error: ServiceException,
668-
shape: StructureShape | None = None,
669-
is_query_compatible: bool = False,
670-
) -> str:
671-
return error.code
672+
if self._is_request_query_compatible(headers):
673+
return error.status_code
674+
675+
# we only want to override status code 4XX
676+
if 400 < error.status_code <= 499:
677+
return 400
678+
679+
return error.status_code
672680

673681
def _is_request_query_compatible(self, headers: Headers) -> bool:
674682
return headers.get("x-amzn-query-mode") == "true"
@@ -1238,6 +1246,11 @@ def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_
12381246
request_id_element = ETree.SubElement(response_metadata, "RequestId")
12391247
request_id_element.text = request_id
12401248

1249+
def _get_error_status_code(
1250+
self, error: ServiceException, headers: Headers, service_model: ServiceModel
1251+
) -> int:
1252+
return error.status_code
1253+
12411254

12421255
class EC2ResponseSerializer(QueryResponseSerializer):
12431256
"""
@@ -1326,11 +1339,18 @@ def _serialize_error(
13261339
# com.amazon.coral.service#ExceptionName
13271340
# if json-1.1, it should only be the name
13281341

1329-
# if the operation is query compatible, we need to add to use shape name
1330-
# when we create `CommonServiceException` and they don't exist in the spec, we give already give the error name
1331-
# as the exception code.
13321342
is_query_compatible = operation_model.service_model.is_query_compatible
1333-
code = self._get_error_code(error, shape, is_query_compatible)
1343+
# if the operation is query compatible, we need to add to use shape name
1344+
if is_query_compatible:
1345+
if shape:
1346+
code = shape.name
1347+
else:
1348+
# if the shape is not defined, we are using the Exception named to derive the `Code`, like you would
1349+
# from the shape. This allows us to have Exception that are valid in multi-protocols by defining its
1350+
# code and its name to be different
1351+
code = error.__class__.__name__
1352+
else:
1353+
code = error.code
13341354

13351355
response.headers["X-Amzn-Errortype"] = code
13361356

@@ -1504,44 +1524,6 @@ def _prepare_additional_traits_in_response(
15041524
)
15051525
return response
15061526

1507-
def _get_error_code(
1508-
self,
1509-
error: ServiceException,
1510-
shape: StructureShape | None = None,
1511-
is_query_compatible: bool = False,
1512-
) -> str:
1513-
# TODO: explain
1514-
if is_query_compatible:
1515-
if shape:
1516-
code = shape.name
1517-
else:
1518-
code = error.__class__.__name__
1519-
else:
1520-
code = error.code
1521-
1522-
return code
1523-
1524-
def _get_error_status_code(
1525-
self, error: ServiceException, headers: Headers, service_model: ServiceModel
1526-
) -> int:
1527-
# by default, `json` services might not define exception status code, so they are not defined in the exception
1528-
# object and will use the default value of `400`
1529-
# But Query compatible service always do define them, so we get the wrong code for service that are
1530-
# multi-protocols like CloudWatch
1531-
# we need to verify if the service is compatible, and if the client has requested the query compatible error
1532-
# code
1533-
if not service_model.is_query_compatible:
1534-
return error.status_code
1535-
1536-
if self._is_request_query_compatible(headers):
1537-
return error.status_code
1538-
1539-
# we only want to override status code 4XX
1540-
if 400 < error.status_code <= 499:
1541-
return 400
1542-
1543-
return error.status_code
1544-
15451527

15461528
class RestJSONResponseSerializer(BaseRestResponseSerializer, JSONResponseSerializer):
15471529
"""
@@ -1987,7 +1969,17 @@ def _serialize_error(
19871969
# Responses for the rpcv2Cbor protocol SHOULD NOT contain the X-Amzn-ErrorType header.
19881970
# Type information is always serialized in the payload. This is different from the `json` protocol
19891971
is_query_compatible = operation_model.service_model.is_query_compatible
1990-
code = self._get_error_code(error, shape, is_query_compatible)
1972+
# if the operation is query compatible, we need to add to use shape name
1973+
if is_query_compatible:
1974+
if shape:
1975+
code = shape.name
1976+
else:
1977+
# if the shape is not defined, we are using the Exception named to derive the `Code`, like you would
1978+
# from the shape. This allows us to have Exception that are valid in multi-protocols by defining its
1979+
# code and its name to be different
1980+
code = error.__class__.__name__
1981+
else:
1982+
code = error.code
19911983

19921984
if not shape:
19931985
shape_copy = DEFAULT_ERROR_STRUCTURE_SHAPE
@@ -2034,44 +2026,6 @@ def _prepare_additional_traits_in_response(
20342026
)
20352027
return response
20362028

2037-
def _get_error_code(
2038-
self,
2039-
error: ServiceException,
2040-
shape: StructureShape | None = None,
2041-
is_query_compatible: bool = False,
2042-
) -> str:
2043-
# TODO: explain
2044-
if is_query_compatible:
2045-
if shape:
2046-
code = shape.name
2047-
else:
2048-
code = error.__class__.__name__
2049-
else:
2050-
code = error.code
2051-
2052-
return code
2053-
2054-
def _get_error_status_code(
2055-
self, error: ServiceException, headers: Headers, service_model: ServiceModel
2056-
) -> int:
2057-
# by default, `json` services might not define exception status code, so they are not defined in the exception
2058-
# object and will use the default value of `400`
2059-
# But Query compatible service always do define them, so we get the wrong code for service that are
2060-
# multi-protocols like CloudWatch
2061-
# we need to verify if the service is compatible, and if the client has requested the query compatible error
2062-
# code
2063-
if not service_model.is_query_compatible:
2064-
return error.status_code
2065-
2066-
if self._is_request_query_compatible(headers):
2067-
return error.status_code
2068-
2069-
# we only want to override status code 4XX
2070-
if 400 < error.status_code <= 499:
2071-
return 400
2072-
2073-
return error.status_code
2074-
20752029

20762030
class S3ResponseSerializer(RestXMLResponseSerializer):
20772031
"""

tests/aws/services/cloudwatch/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ def post(
8585
response = self.post_raw(operation, payload, query_mode=query_mode)
8686
response_body = self._deserialize_response(response)
8787
if response.status_code != status_code:
88-
print(f"{response.content=}")
8988
raise ValueError(f"Bad status: {response.status_code}, response body: {response_body}")
9089
return response_body
9190

0 commit comments

Comments
 (0)