fix: stable_id rejects an empty call; hosts(limit=0) returns []

stable_id() with no parts skipped the per-part loop and returned a constant
sha256(b''), colliding on every empty call - now raises ValueError like the other
weak-id guards. hosts(cidr, limit=0) checked the cap after appending, so limit=0
yielded one host - move the check before the append so limit=0 returns [].

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-06 19:21:14 -04:00
parent 0030daeb7b
commit a1702eede8
2 changed files with 6 additions and 3 deletions
+1 -1
View File
@@ -115,7 +115,7 @@ def hosts(cidr: str, *, limit: Optional[int] = None) -> List[str]:
gen = ipaddress.ip_network(cidr, strict=False).hosts()
out: List[str] = []
for host in gen:
out.append(str(host))
if limit is not None and len(out) >= limit:
break
out.append(str(host))
return out
+5 -2
View File
@@ -119,11 +119,14 @@ def stable_id(*parts: str, length: int = 16) -> str:
"""deterministic id from ordered parts; same inputs always produce the same id
joins the parts with a ``\\x00`` separator (so ``("ab","c")`` != ``("a","bc")``) and
returns the leading ``length`` hex chars of their sha256. raises ValueError on an empty
part, a non-str part, or a non-positive length - fail loud rather than emit a weak id.
returns the leading ``length`` hex chars of their sha256. raises ValueError on no parts,
an empty part, a non-str part, or a non-positive length - fail loud rather than emit a
weak or constant id.
"""
if length <= 0:
raise ValueError(f"stable_id: length must be positive, got {length}")
if not parts:
raise ValueError("stable_id: needs at least one part")
for part in parts:
if not isinstance(part, str):
raise ValueError(f"stable_id: parts must be str, got {type(part).__name__}")