"""Opaque, scope-bound pagination cursors.""" from __future__ import annotations import base64 import json from dataclasses import dataclass from hashlib import sha256 class InvalidCursor(ValueError): """Raised for malformed, cross-query, or out-of-range cursors.""" @dataclass(frozen=True) class Cursor: offset: int fingerprint: str def _fingerprint(*parts: str) -> str: return sha256("\x1f".join(parts).encode("utf-8")).hexdigest()[:32] def encode_cursor(offset: int, *scope: str) -> str: if offset < 0: raise ValueError("cursor offset must be non-negative") payload = {"o": offset, "f": _fingerprint(*scope)} raw = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("ascii") return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=") def decode_cursor(value: str | None, *scope: str) -> Cursor: if value is None: return Cursor(0, _fingerprint(*scope)) if not 1 <= len(value) <= 500: raise InvalidCursor("cursor length is invalid") try: padded = value + "=" * (-len(value) % 4) payload = json.loads(base64.urlsafe_b64decode(padded).decode("ascii")) offset = payload["o"] fingerprint = payload["f"] except (ValueError, KeyError, TypeError, json.JSONDecodeError) as exc: raise InvalidCursor("cursor is invalid") from exc if type(offset) is not int or offset < 0 or fingerprint != _fingerprint(*scope): raise InvalidCursor("cursor does not match this query") return Cursor(offset, fingerprint)