Skip to content

Commit 56b1a60

Browse files
Normalize transport error text from string subclasses
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 44141f5 commit 56b1a60

2 files changed

Lines changed: 53 additions & 9 deletions

File tree

hyperbrowser/transport/error_utils.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ def _safe_to_string(value: Any) -> str:
5151
normalized_value = str(value)
5252
except Exception:
5353
return f"<unstringifiable {type(value).__name__}>"
54-
if not isinstance(normalized_value, str):
54+
if type(normalized_value) is not str:
5555
return f"<{type(value).__name__}>"
5656
try:
5757
sanitized_value = "".join(
5858
"?" if ord(character) < 32 or ord(character) == 127 else character
5959
for character in normalized_value
6060
)
61+
if type(sanitized_value) is not str:
62+
return f"<{type(value).__name__}>"
6163
if sanitized_value.strip():
6264
return sanitized_value
6365
except Exception:
@@ -189,7 +191,13 @@ def _truncate_error_message(message: str) -> str:
189191

190192
def _normalize_response_text_for_error_message(response_text: Any) -> str:
191193
if isinstance(response_text, str):
192-
return response_text
194+
try:
195+
normalized_response_text = "".join(character for character in response_text)
196+
if type(normalized_response_text) is not str:
197+
raise TypeError("normalized response text must be a string")
198+
return normalized_response_text
199+
except Exception:
200+
return _safe_to_string(response_text)
193201
if isinstance(response_text, (bytes, bytearray, memoryview)):
194202
try:
195203
return memoryview(response_text).tobytes().decode("utf-8")
@@ -202,7 +210,13 @@ def _stringify_error_value(value: Any, *, _depth: int = 0) -> str:
202210
if _depth > 10:
203211
return _safe_to_string(value)
204212
if isinstance(value, str):
205-
return value
213+
try:
214+
normalized_value = "".join(character for character in value)
215+
if type(normalized_value) is not str:
216+
raise TypeError("normalized error value must be a string")
217+
return normalized_value
218+
except Exception:
219+
return _safe_to_string(value)
206220
if isinstance(value, Mapping):
207221
for key in ("message", "error", "detail", "errors", "msg", "title", "reason"):
208222
try:

tests/test_transport_error_utils.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,18 @@ def __str__(self) -> str:
304304
return self._BrokenString("broken\tfallback\nvalue")
305305

306306

307+
class _StringifiesToStringSubclass:
308+
class _StringSubclass(str):
309+
pass
310+
311+
def __str__(self) -> str:
312+
return self._StringSubclass("subclass fallback value")
313+
314+
315+
class _MessageStringSubclass(str):
316+
pass
317+
318+
307319
def test_extract_request_error_context_uses_unknown_when_request_unset():
308320
method, url = extract_request_error_context(httpx.RequestError("network down"))
309321

@@ -972,31 +984,31 @@ def test_extract_error_message_sanitizes_control_characters_in_fallback_error_te
972984
assert message == "bad?fallback?text"
973985

974986

975-
def test_extract_error_message_falls_back_when_message_strip_fails():
987+
def test_extract_error_message_normalizes_message_values_when_strip_fails():
976988
message = extract_error_message(
977989
_DummyResponse({"message": _BrokenStripString("broken message")}),
978990
RuntimeError("fallback detail"),
979991
)
980992

981-
assert message == "fallback detail"
993+
assert message == "broken message"
982994

983995

984-
def test_extract_error_message_falls_back_when_message_length_check_fails():
996+
def test_extract_error_message_normalizes_message_values_when_length_check_fails():
985997
message = extract_error_message(
986998
_DummyResponse({"message": _BrokenLenString("broken message")}),
987999
RuntimeError("fallback detail"),
9881000
)
9891001

990-
assert message == "fallback detail"
1002+
assert message == "broken message"
9911003

9921004

993-
def test_extract_error_message_falls_back_when_response_text_strip_fails():
1005+
def test_extract_error_message_normalizes_response_text_values_when_strip_fails():
9941006
message = extract_error_message(
9951007
_DummyResponse(" ", text=_BrokenStripString("response body")),
9961008
RuntimeError("fallback detail"),
9971009
)
9981010

999-
assert message == "fallback detail"
1011+
assert message == "response body"
10001012

10011013

10021014
def test_extract_error_message_handles_response_text_sanitization_iteration_failures():
@@ -1033,6 +1045,24 @@ def test_extract_error_message_handles_fallback_errors_with_broken_string_subcla
10331045
assert message == "<_StringifiesToBrokenSubclass>"
10341046

10351047

1048+
def test_extract_error_message_handles_fallback_errors_with_string_subclass_results():
1049+
message = extract_error_message(
1050+
_DummyResponse(" ", text=" "),
1051+
_StringifiesToStringSubclass(),
1052+
)
1053+
1054+
assert message == "<_StringifiesToStringSubclass>"
1055+
1056+
1057+
def test_extract_error_message_normalizes_string_subclass_message_values():
1058+
message = extract_error_message(
1059+
_DummyResponse({"message": _MessageStringSubclass("detail from subclass")}),
1060+
RuntimeError("fallback detail"),
1061+
)
1062+
1063+
assert message == "detail from subclass"
1064+
1065+
10361066
def test_extract_error_message_sanitizes_control_characters_in_json_message():
10371067
message = extract_error_message(
10381068
_DummyResponse({"message": "bad\tjson\nmessage"}),

0 commit comments

Comments
 (0)