filtering

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: 5 Imported by: 0

Documentation

Overview

Package filtering is wowapi's allowlist-driven filter/sort builder — the mechanism behind docs/blueprint/05 §2, "Pagination / filtering / sorting (allowlist-driven; SQL injection impossible by construction)".

Security invariant (enforced by construction, exercised by the tests):

  • Column names in emitted SQL come ONLY from FieldSpec.Col / SortSpec.Col, which are framework-controlled. A client picks a *key* into an Allowlist; an unknown key is rejected with a KindValidation error. Client text never becomes a column or an operator.
  • Operators are validated against a fixed internal set AND the per-field FieldSpec.Ops permit-list, then rendered from an internal map. Client text never becomes an operator token.
  • Client VALUES are never concatenated into SQL. Every value — including each element of an "in" list — is emitted as a $N placeholder and appended to the args slice. The literal value therefore cannot appear in the SQL text.

The result: a caller can only ever produce SQL fragments that reference allowlisted physical columns with parameter placeholders, so injection is impossible regardless of what a client sends.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func KeysetClause

func KeysetClause(s Sort, cur pagination.Cursor, startArg int) (sql string, args []any, nextArg int, err error)

KeysetClause builds the "rows strictly after the cursor" predicate for keyset pagination, matching the given Sort's columns and directions exactly (blueprint 05 §2). It is injection-proof by the same construction as the filter/sort builders: column names come only from the Sort's allowlisted terms, never from the cursor; the cursor supplies only VALUES, bound as $N placeholders (review findings ARCH-31, SEC-22).

For a sort (c1 d1, c2 d2, …) the predicate is the standard lexicographic expansion, correct for mixed directions:

(c1 OP1 v1)
 OR (c1 = v1 AND c2 OP2 v2)
 OR (c1 = v1 AND c2 = v2 AND c3 OP3 v3) …

where OPi is ">" for ascending and "<" for descending. It returns "" (no predicate) when the sort or cursor is empty. Placeholders start at startArg; nextArg is the next free placeholder index.

The cursor MUST carry a value for every sort column (it was minted from a row under this sort); a missing value is a KindValidation error, which also guards against a forged cursor whose keys do not match the sort (SEC-22).

func NextCursor

func NextCursor(s Sort, values map[string]any) (string, error)

NextCursor mints the opaque keyset cursor for the last row returned under sort s, binding s's signature so KeysetClause rejects it if a later request changes the sort order (roadmap R7). values must carry exactly one entry per sort column. An empty sort yields a signatureless cursor.

Types

type Allowlist

type Allowlist map[string]FieldSpec

Allowlist maps client-facing field names to their FieldSpec. A field absent from the allowlist cannot be filtered.

type Condition

type Condition struct {
	Field  string
	Op     Op
	Values []any
	// contains filtered or unexported fields
}

Condition is one parsed, validated filter predicate. Field is the client-facing name (kept for introspection); the resolved physical column is held privately so only Parse can set it — external code cannot forge a column.

type Dir

type Dir string

Dir is a sort direction. The set is closed.

const (
	DirAsc  Dir = "asc"
	DirDesc Dir = "desc"
)

type FieldSpec

type FieldSpec struct {
	Col string
	Ops []Op
}

FieldSpec maps a client-facing field to a physical column and the operators permitted on it. Col is framework-controlled and is the only text that can reach the SQL column position.

type Op

type Op string

Op is a filter comparison operator. The set is closed.

const (
	OpEq   Op = "eq"
	OpNeq  Op = "neq"
	OpIn   Op = "in"
	OpGt   Op = "gt"
	OpGte  Op = "gte"
	OpLt   Op = "lt"
	OpLte  Op = "lte"
	OpLike Op = "like"
)

type Set

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

Set is an AND-combined collection of validated Conditions.

func Parse

func Parse(raw map[string][]string, allow Allowlist) (Set, error)

Parse validates raw client filter input against the allowlist and returns a Set. Wire format (documented contract): raw maps a client field name to one or more "op:value" entries.

{"status": {"eq:active"}}          → status = $n
{"age":    {"gte:18"}}             → age >= $n
{"status": {"in:active,pending"}}  → status IN ($n, $n+1)   (comma-separated)
{"name":   {"like:ac%"}}           → name LIKE $n

Multiple entries for one field, and multiple fields, are AND-combined. Fields are processed in sorted order so placeholder numbering is deterministic.

Errors (all KindValidation): unknown field, missing "op:" prefix, unknown operator, an operator not permitted by the field's FieldSpec.Ops, or an empty "in" list.

func (Set) Conditions

func (s Set) Conditions() []Condition

Conditions returns a copy of the parsed conditions (introspection only).

func (Set) IsEmpty

func (s Set) IsEmpty() bool

IsEmpty reports whether the set carries no conditions.

func (Set) SQL

func (s Set) SQL(startArg int) (sql string, args []any, nextArg int)

SQL renders the conditions into a boolean SQL fragment (no leading WHERE), appending each value to args as a $N placeholder. startArg is the next placeholder number (1-based); the returned nextArg is the following free number. An empty Set returns "", nil, startArg.

Columns are taken from the resolved FieldSpec.Col; values only ever appear as $N. See the package doc for the injection-proof invariant.

func (Set) Where

func (s Set) Where(startArg int) (sql string, args []any, nextArg int)

Where wraps SQL with a leading "WHERE ". An empty Set returns "", nil, startArg so callers can append it unconditionally. Conditions are AND-combined.

type Sort

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

Sort is an ordered list of validated sort terms.

func ParseSort

func ParseSort(raw string, allow SortAllowlist) (Sort, error)

ParseSort validates a raw sort string against the allowlist. Wire format (documented contract): a comma-separated list of "key[:dir]" terms, e.g. "created_at:desc,id:asc". A term without ":dir" defaults to ascending. An empty raw string yields an empty Sort (SQL == "").

Errors (all KindValidation): unknown sort key, or a direction other than "asc"/"desc". Client text only ever selects an allowlisted key and one of the two direction constants — it never reaches the SQL text.

func (Sort) IsEmpty

func (s Sort) IsEmpty() bool

IsEmpty reports whether the sort carries no terms.

func (Sort) SQL

func (s Sort) SQL() string

SQL renders the sort as an "ORDER BY <col> <DIR>, ..." clause using only allowlisted physical columns and validated direction keywords. An empty Sort returns "". The output contains no client-supplied text and needs no args.

func (Sort) Signature

func (s Sort) Signature() string

Signature is a canonical, stable string identifying this exact sort order — ordered columns and their directions, e.g. "created_at:desc,id:asc". Two sorts share a signature iff they produce the same ORDER BY. Keyset cursors carry it so a later request under a changed sort fails loudly instead of silently returning wrong pages (roadmap R7). An empty Sort has the empty signature.

func (Sort) Terms

func (s Sort) Terms() []Term

Terms returns the ordered, resolved sort terms so a keyset predicate can be built matching the ORDER BY exactly (review finding ARCH-31).

type SortAllowlist

type SortAllowlist map[string]SortSpec

SortAllowlist maps client-facing sort keys to their SortSpec.

type SortSpec

type SortSpec struct {
	Col string
}

SortSpec maps a client-facing sort key to a physical column. Col is framework-controlled and is the only text that reaches the SQL column position.

type Term

type Term struct {
	Col string
	Dir Dir
}

Term is one resolved sort term exposed for keyset pagination: the physical column and its direction. Terms come only from allowlisted SortSpecs.

Jump to

Keyboard shortcuts

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