Skip to content

Commit f46e6c4

Browse files
Harden operation-name builder against non-string components
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 9089a4b commit f46e6c4

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

hyperbrowser/client/polling.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ class _NonRetryablePollingError(HyperbrowserError):
2929
pass
3030

3131

32+
def _coerce_operation_name_component(value: object, *, fallback: str) -> str:
33+
if isinstance(value, str):
34+
return value
35+
try:
36+
return str(value)
37+
except Exception:
38+
return fallback
39+
40+
3241
def _normalize_non_negative_real(value: float, *, field_name: str) -> float:
3342
is_supported_numeric_type = isinstance(value, Real) or isinstance(value, Decimal)
3443
if isinstance(value, bool) or not is_supported_numeric_type:
@@ -68,24 +77,31 @@ def _validate_operation_name(operation_name: str) -> None:
6877

6978

7079
def build_operation_name(prefix: str, identifier: str) -> str:
71-
normalized_identifier = identifier.strip()
80+
normalized_prefix = _coerce_operation_name_component(prefix, fallback="")
81+
raw_identifier = _coerce_operation_name_component(identifier, fallback="unknown")
82+
normalized_identifier = raw_identifier.strip()
7283
if not normalized_identifier:
7384
normalized_identifier = "unknown"
7485
normalized_identifier = "".join(
7586
"?" if ord(character) < 32 or ord(character) == 127 else character
7687
for character in normalized_identifier
7788
)
7889

79-
operation_name = f"{prefix}{normalized_identifier}"
90+
operation_name = f"{normalized_prefix}{normalized_identifier}"
8091
if len(operation_name) <= _MAX_OPERATION_NAME_LENGTH:
8192
return operation_name
8293
available_identifier_length = (
83-
_MAX_OPERATION_NAME_LENGTH - len(prefix) - len(_TRUNCATED_OPERATION_NAME_SUFFIX)
94+
_MAX_OPERATION_NAME_LENGTH
95+
- len(normalized_prefix)
96+
- len(_TRUNCATED_OPERATION_NAME_SUFFIX)
8497
)
8598
if available_identifier_length > 0:
8699
truncated_identifier = normalized_identifier[:available_identifier_length]
87-
return f"{prefix}{truncated_identifier}{_TRUNCATED_OPERATION_NAME_SUFFIX}"
88-
return prefix[:_MAX_OPERATION_NAME_LENGTH]
100+
return (
101+
f"{normalized_prefix}{truncated_identifier}"
102+
f"{_TRUNCATED_OPERATION_NAME_SUFFIX}"
103+
)
104+
return normalized_prefix[:_MAX_OPERATION_NAME_LENGTH]
89105

90106

91107
def build_fetch_operation_name(operation_name: str) -> str:

tests/test_polling.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,41 @@ def test_build_operation_name_uses_unknown_for_blank_identifier():
8383
assert operation_name == "crawl job unknown"
8484

8585

86+
def test_build_operation_name_supports_non_string_identifier_values():
87+
operation_name = build_operation_name(
88+
"crawl job ",
89+
123, # type: ignore[arg-type]
90+
)
91+
92+
assert operation_name == "crawl job 123"
93+
94+
95+
def test_build_operation_name_falls_back_for_unstringifiable_identifiers():
96+
class _BadIdentifier:
97+
def __str__(self) -> str:
98+
raise RuntimeError("cannot stringify")
99+
100+
operation_name = build_operation_name(
101+
"crawl job ",
102+
_BadIdentifier(), # type: ignore[arg-type]
103+
)
104+
105+
assert operation_name == "crawl job unknown"
106+
107+
108+
def test_build_operation_name_falls_back_for_unstringifiable_prefixes():
109+
class _BadPrefix:
110+
def __str__(self) -> str:
111+
raise RuntimeError("cannot stringify")
112+
113+
operation_name = build_operation_name(
114+
_BadPrefix(), # type: ignore[arg-type]
115+
"identifier",
116+
)
117+
118+
assert operation_name == "identifier"
119+
120+
86121
def test_poll_until_terminal_status_allows_immediate_terminal_on_zero_max_wait():
87122
status = poll_until_terminal_status(
88123
operation_name="sync immediate zero wait",

0 commit comments

Comments
 (0)