Skip to main content

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

ErrorRaised when…Example message
ValidationErrorpage < 1, limit < 1, limit > MAX_LIMIT, or both after and before setlimit must not exceed 1000 · after and before are mutually exclusive
InvalidCursorErrora keyset cursor is malformed, truncated, or tampered with (subclass of ValidationError)invalid cursor: base64
FilterErrorunknown operator, an unresolved field path, a _-prefixed segment, an operand of the wrong type, between without exactly 2 elements, or a regex over 200 charsunknown operator: zzz · Between requires exactly 2 elements
FilterValidationErrora filter group is nested beyond MAX_FILTER_DEPTH (checked at construction)FilterGroup nesting must not exceed 5 levels
SortErrora sort key compares values that aren't order-comparable (e.g. number vs string)field values are not order-comparable
SearchQueryErrora search query exceeds MAX_QUERY_LEN (validated via search_spec)Query must not exceed 500 characters
ConfigurationErroran 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):

LimitValueApplies to
MAX_LIMIT1000page size — limit on OffsetParams / CursorParams
MAX_FILTER_DEPTH5nesting depth of And() / Or() filter groups
MAX_QUERY_LEN500search query length (enforced when you call search_spec)
regex length200 charsthe regex filter operator's value
between arityexactly 2the 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