Documentation
¶
Overview ¶
Package pagination provides generic, ORM-agnostic types and utilities for paginated API responses.
The package defines request binding structs, a generic result type, and sort validation — all with zero external dependencies. Actual query execution (COUNT + LIMIT/OFFSET) lives in ORM-specific adapters such as the github.com/credo-go/credo/store/sqldb.SelectQuery.Page terminal.
Request Binding ¶
PageRequest and SortRequest are embeddable structs that work with BindQuery via query tags:
type BranchFilter struct {
pagination.PageRequest
pagination.SortRequest
Search string `query:"search"`
}
PageRequest.Normalize and PageRequest.Validate are forgiving input-policy operations: they apply defaults and clamp PerPage in place. Execution is a separate, strict boundary. PageRequest.Offset returns (int, error), never mutates or normalizes the request, and wraps ErrInvalidPageRequest when Page or PerPage is non-positive or the multiplication would overflow int. ORM adapters may impose a narrower execution range; sqldb additionally enforces Bun v1.2.18's signed-int32 LIMIT/OFFSET representation before COUNT.
Page Construction and Mapping ¶
A Page carries pagination metadata (Total, Page, PerPage, TotalPages) that is computed once — by NewPage or by a query terminal such as sqldb's SelectQuery.Page — and never recomputed or hand-copied afterward. NewPage computes the ceiling without an addition that can overflow at MaxInt64:
page := pagination.NewPage(dtos, total, filter.Page, filter.PerPage)
To turn a page of database models into a page of response DTOs, use Page.Map; it transforms the records and carries the metadata over:
dtoPage := modelPage.Map(func(m Model) DTO { return toDTO(m) })
JSON Response ¶
Page.ToDataMeta splits the page into a data slice and a Meta struct suitable for JSON serialization:
data, meta := page.ToDataMeta()
Sort Safety ¶
SortRequest.ValidateSort rejects unknown sort fields via a whitelist, preventing SQL injection through ORDER BY clauses:
col, ord := filter.SortRequest.ValidateSort(sortCfg)
Index ¶
Constants ¶
const ( DefaultPage = 1 DefaultPerPage = 50 MinPerPage = 1 MaxPerPage = 50 )
Default pagination constants.
Variables ¶
var ErrInvalidPageRequest = errors.New("pagination: invalid page request")
ErrInvalidPageRequest reports that pagination execution values violate the strict Page/PerPage or offset invariants. Normalize and Validate apply input defaults; Offset uses this error instead of normalizing or clamping.
Functions ¶
This section is empty.
Types ¶
type Meta ¶
type Meta struct {
Total int64 `json:"total_count"`
Page int `json:"page"`
PerPage int `json:"per_page"`
TotalPages int64 `json:"total_pages"`
HasNext bool `json:"has_next"`
HasPrev bool `json:"has_prev"`
}
Meta is pagination metadata for JSON serialization.
type Page ¶
Page is a generic paginated result. Its pagination metadata (Total, Page, PerPage, TotalPages) is computed once — by NewPage or by a query terminal such as sqldb's SelectQuery.Page — and carried unchanged through any later reshaping: Page.Map turns a Page of database models into a Page of response DTOs while preserving that metadata, so it is never recomputed or hand-copied.
func NewPage ¶
NewPage creates a Page from raw values. TotalPages is computed automatically. A nil records slice is replaced with an empty slice for safe JSON serialization ([] instead of null).
func (*Page[T]) Map ¶
Map returns a new Page[U] whose records are produced by applying fn to each record of p, carrying the pagination metadata (Total, Page, PerPage, TotalPages) over unchanged. It is the canonical way to turn a page of database models into a page of response DTOs:
modelPage, err := db.Select().Where(...).Page[Model](ctx, req)
if err != nil {
return err
}
dtoPage := modelPage.Map(func(m Model) DTO { return toDTO(m) })
fn is applied once per record, in order, and must be pure: for a conversion that can fail, map in the service layer and build the page with NewPage. fn must not be nil — a nil mapping is always a programming error, so Map panics on a nil fn even when the page is empty, rather than silently returning an empty Page[U].
func (*Page[T]) ToDataMeta ¶
ToDataMeta splits the page into a records slice and a Meta struct for JSON response serialization.
type PageRequest ¶
PageRequest is an embeddable struct for pagination query parameters. It works with BindQuery via query tags.
func (*PageRequest) Normalize ¶
func (r *PageRequest) Normalize()
Normalize applies pagination defaults and limits in place. Zero or negative values are replaced with defaults. PerPage is clamped to [MinPerPage, MaxPerPage]. For a custom upper bound, use PageRequest.NormalizeWithMax.
func (*PageRequest) NormalizeWithMax ¶
func (r *PageRequest) NormalizeWithMax(maxPerPage int)
NormalizeWithMax is like PageRequest.Normalize but clamps PerPage to the given maximum instead of the package default MaxPerPage. maxPerPage values <= 0 fall back to MaxPerPage.
PageRequest's own Validate — and therefore BindQuery's auto-validation — always applies the package default. To raise the cap for a specific endpoint, shadow Validate on the embedding struct:
type ListQuery struct {
pagination.PageRequest
}
func (q *ListQuery) Validate() error {
q.NormalizeWithMax(200)
return nil
}
func (PageRequest) Offset ¶
func (r PageRequest) Offset() (int, error)
Offset returns the zero-based offset for LIMIT/OFFSET queries. Unlike Normalize and Validate, Offset is strict and never mutates, defaults, or clamps the request. Page and PerPage must both be positive, and their offset product must fit in an int; violations wrap ErrInvalidPageRequest. Adapters must additionally enforce any narrower driver or ORM limit.
func (*PageRequest) Validate ¶
func (r *PageRequest) Validate() error
Validate implements [validation.Validatable] so that BindQuery automatically normalizes pagination values after decoding.
type SortConfig ¶
type SortConfig struct {
DefaultField string
DefaultOrder string // "ASC" or "DESC"
AllowedFields map[string]string // API field name → DB column name
}
SortConfig defines allowed sort fields for SQL injection prevention. AllowedFields maps API field names to DB column names.
type SortRequest ¶
SortRequest is an embeddable struct for sort query parameters.
func (*SortRequest) ValidateSort ¶
func (r *SortRequest) ValidateSort(cfg *SortConfig) (column, order string)
ValidateSort validates sort parameters against allowed fields. It returns the DB column name and normalized sort order (uppercase). Unknown sort fields fall back to cfg.DefaultField. Invalid sort orders fall back to cfg.DefaultOrder. If cfg is nil, both return values are empty strings. A nil receiver is safe to call.