Errors & limits
Every failure raised by the engine belongs to one shared taxonomy. The Rust core classifies the error; each language binding maps it to a host-native exception with the same hierarchy and message, so handling code is parallel across languages — see parity.
The exception hierarchy
PaginateError (TypeScript & Python; aliased PaginationError)
├── ConfigurationError invalid adapter / ORM configuration
├── ValidationError bad page / limit / cursor input
│ └── InvalidCursorError a cursor was malformed, truncated, or tampered with
├── FilterError a filter could not be evaluated
│ └── FilterValidationError a filter spec failed validation (e.g. nesting depth)
├── SortError values are not order-comparable
└── SearchError a search operation failed
└── SearchQueryError the search query failed validation (e.g. too long)
Catch PaginateError (Python) / PaginateError (TypeScript) to handle any of them at
once, or a specific subclass for finer control. In Python, FilterError and
ValidationError also carry an optional field, and every error carries a
structured details mapping for programmatic handling.
What raises each error
| Error | Raised when… | Example message |
|---|---|---|
ValidationError | page < 1, limit < 1, limit > MAX_LIMIT, or both after and before set | limit must not exceed 1000 · after and before are mutually exclusive |
InvalidCursorError | a keyset cursor is malformed, truncated, or tampered with (subclass of ValidationError) | invalid cursor: base64 |
FilterError | unknown operator, an unresolved field path, a _-prefixed segment, an operand of the wrong type, between without exactly 2 elements, or a regex over 200 chars | unknown operator: zzz · Between requires exactly 2 elements |
FilterValidationError | a filter group is nested beyond MAX_FILTER_DEPTH (checked at construction) | FilterGroup nesting must not exceed 5 levels |
SortError | a sort key compares values that aren't order-comparable (e.g. number vs string) | field values are not order-comparable |
SearchQueryError | a search query exceeds MAX_QUERY_LEN (validated via search_spec) | Query must not exceed 500 characters |
ConfigurationError | an adapter can't resolve a field to a column, or a backend is misconfigured | — |
Malformed cursors
When the keyset adapters decode a client-supplied after / before, a malformed,
truncated, or tampered cursor raises InvalidCursorError (invalid cursor: …). It
is a subclass of ValidationError, so except ValidationError / except PaginateError
(and instanceof in TypeScript) catch it — and you can catch InvalidCursorError
specifically to distinguish a bad cursor from other validation failures. The same type
is thrown by decodeCursor in TypeScript, so the behaviour matches across languages.
Because cursors arrive from clients, treat a decode failure as a bad request:
from pypaginate import CursorParams, InvalidCursorError
from pypaginate.adapters.sqlalchemy import SyncSQLAlchemyCursorBackend
try:
page = SyncSQLAlchemyCursorBackend(session).fetch_page(stmt, CursorParams(limit=20, after=token))
except InvalidCursorError:
raise HTTPException(status_code=400, detail="invalid cursor")
import { decodeCursor, InvalidCursorError } from "@cyblow/paginate";
try {
const values = decodeCursor(token);
} catch (err) {
if (err instanceof InvalidCursorError) {
res.status(400).json({ error: "invalid cursor" });
} else throw err;
}
Handling errors
Python
from pypaginate import paginate, OffsetParams
from pypaginate.errors import ValidationError, FilterError, PaginateError
try:
page = paginate(items, OffsetParams(page=0))
except ValidationError as exc:
print(exc) # "page must be >= 1"
print(exc.details) # structured context
except PaginateError:
... # any other paginate failure
TypeScript
The error classes are exported for instanceof checks:
import { paginate, OffsetParams, ValidationError, PaginateError } from "@cyblow/paginate";
try {
const page = paginate(items, new OffsetParams({ page: 0 }));
} catch (err) {
if (err instanceof ValidationError) console.error(err.message);
else if (err instanceof PaginateError) {/* any other paginate failure */}
else throw err;
}
Limits
These limits live once in the core and are shared by every language (the Python values
are exposed as constants, e.g. pypaginate.MAX_LIMIT):
| Limit | Value | Applies to |
|---|---|---|
MAX_LIMIT | 1000 | page size — limit on OffsetParams / CursorParams |
MAX_FILTER_DEPTH | 5 | nesting depth of And() / Or() filter groups |
MAX_QUERY_LEN | 500 | search query length (enforced when you call search_spec) |
| regex length | 200 chars | the regex filter operator's value |
between arity | exactly 2 | the between operator's [lo, hi] value |
MAX_LIMIT is a denial-of-service guard: a request for a larger page is rejected with a
ValidationError rather than silently clamped. The trigram threshold (search) is a
0–100 similarity; values outside that range simply match nothing rather than raising.
See also
- Filtering & operators · Sorting · Search & ranking
- Language handling in context: Python filtering · TypeScript filtering