pagination

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package pagination provides wowapi's page/cursor response envelopes and the opaque keyset cursor used for feed-style listing. It is the kernel counterpart of the shapes documented in docs/blueprint/04 §4 (PageResponse, CursorPage) and the pagination half of docs/blueprint/05 §2 ("allowlist-driven; SQL injection impossible by construction").

A Cursor is an opaque, tamper-evident-by-decode-failure encoding of a keyset position — the physical column values of the last row returned. httpx's ParsePagination builds a Request from the raw per_page + cursor query params via Parse; the resulting Request carries a clamped Limit and the decoded Cursor. Attacker-supplied cursors that do not decode yield a KindValidation error and never panic.

The package name is pagination even though the blueprint refers to it as "page" in some signatures.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EncodeCursor

func EncodeCursor(values map[string]any) (string, error)

EncodeCursor encodes a keyset tuple (last-row column values) into an opaque cursor string. An empty/nil map encodes to "" (the zero cursor). An unsupported value type is a server-side programming error (the caller controls the keyset columns), so it is returned as a plain error, not a KindValidation.

func EncodeCursorWithSig

func EncodeCursorWithSig(sig string, values map[string]any) (string, error)

EncodeCursorWithSig encodes a keyset tuple together with the signature of the sort it was minted under, so a later request can detect that the sort order changed (a direction flip or column reorder that the column-set check alone would miss — roadmap R7). An empty sig produces the legacy flat encoding, so this is a drop-in for EncodeCursor when no sort binding is desired.

Types

type Cursor

type Cursor struct {
	// contains filtered or unexported fields
}

Cursor is an opaque keyset position: the physical column values of the last row returned, so the next query can resume with a WHERE (cols) > (values) comparison. It is encoded as base64url(JSON) and is deliberately not human-meaningful — clients round-trip it verbatim via CursorPage.NextCursor.

Supported scalar value types (encode → decode round-trip):

string          → string
bool            → bool
int/…/int64     → int64   (integer JSON numbers decode back to int64)
uint/…/uint64   → int64
float32/float64 → float64 (fractional/exponent JSON numbers)
uuid.UUID       → string  (canonical RFC 4122 form)
time.Time       → string  (RFC 3339, nanosecond precision, UTC)

uuid.UUID and time.Time are normalised to their string forms on encode, so Values reports them as strings; that is sufficient to rebuild a keyset WHERE clause where the column type drives the parameter binding.

func DecodeCursor

func DecodeCursor(s string) (Cursor, error)

DecodeCursor parses an opaque cursor produced by EncodeCursor. An empty string decodes to the zero Cursor. Any malformed input — bad base64, non-object JSON, trailing data, or an oversized payload — yields a KindValidation error and never panics (this is attacker-reachable input).

func (Cursor) IsZero

func (c Cursor) IsZero() bool

IsZero reports whether the cursor carries no position (start from the beginning).

func (Cursor) Sig

func (c Cursor) Sig() string

Sig returns the sort-spec signature the cursor was minted under, or "" if it carries none (a legacy flat cursor). Callers that know the current sort should reject a cursor whose Sig does not match — see filtering.KeysetClause.

func (Cursor) Values

func (c Cursor) Values() map[string]any

Values returns a copy of the decoded keyset tuple. Mutating the result does not affect the Cursor.

type CursorPage

type CursorPage[T any] struct {
	Items      []T    `json:"items"`
	NextCursor string `json:"next_cursor,omitempty"`
	HasMore    bool   `json:"has_more"`
}

CursorPage is the cursor-page envelope (default for feeds/large lists). NextCursor is an opaque base64url(JSON) keyset position (see Cursor); it is omitted when there is no further page. See 04 §4.

type Defaults

type Defaults struct {
	PerPage    int
	MaxPerPage int
}

Defaults configures per_page clamping for Parse.

PerPage    – page size used when the client omits per_page (or sends 0).
MaxPerPage – hard upper bound; requests above it are clamped down. A value
             <= 0 disables the upper bound.

type PageResponse

type PageResponse[T any] struct {
	Items      []T   `json:"items"`
	Page       int   `json:"page"`
	PerPage    int   `json:"per_page"`
	TotalCount int64 `json:"total_count,omitempty"`
}

PageResponse is the offset-page envelope (admin/small lists). TotalCount is omitted from the wire when a COUNT would be too expensive. See 04 §4.

type Request

type Request struct {
	Limit  int
	Cursor Cursor
}

Request is the parsed, validated pagination input for a list query: a page size clamped to [1, MaxPerPage] and the decoded keyset Cursor.

func Parse

func Parse(perPageRaw, cursorRaw string, def Defaults) (Request, error)

Parse turns the raw per_page and cursor query-parameter strings into a Request. per_page handling (documented contract):

""        → Defaults.PerPage
"0"       → Defaults.PerPage
> Max     → Defaults.MaxPerPage (when Max > 0)
negative  → KindValidation error
non-int   → KindValidation error

A malformed cursor yields a KindValidation error (see DecodeCursor); an empty cursor yields a zero Cursor (IsZero == true), i.e. "start from the beginning".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL