Documentation
¶
Index ¶
- Constants
- func BuildInsertArgs(body map[string]any) (cols, placeholders string, args []any)
- func CachedGet(next http.HandlerFunc) http.HandlerFunc
- func FilterFields(record map[string]any, allowed []string) map[string]any
- func IsServerError(err error) bool
- func RowsToMaps(rows pgx.Rows) ([]map[string]any, error)
- func WriteDBError(w http.ResponseWriter, err error)
- func WriteJSON(w http.ResponseWriter, v any) error
- type WriteErrorKind
- type WriteErrorVerdict
Constants ¶
const FileRefMessage = "does not reference an existing file of this tenant"
FileRefMessage is the one message every surface uses for a `file` field whose value references no file of the tenant (REST 422, batch 422, GraphQL, Ctx.Insert/Update) — declared once so the four renderers cannot drift.
const UnknownFieldMessage = "is not a field of this resource"
UnknownFieldMessage is the S44 message for a write naming a column no table backs — same single-declaration rationale as FileRefMessage.
Variables ¶
This section is empty.
Functions ¶
func BuildInsertArgs ¶
BuildInsertArgs builds the column list, placeholder list ($1,$2…), and args slice from a JSON-decoded body map. Keys are sorted for deterministic output and each column identifier is quoted with pgx.Identifier.Sanitize so a key can never break out of the identifier position (defence-in-depth alongside ValidateWritableColumns).
func CachedGet ¶
func CachedGet(next http.HandlerFunc) http.HandlerFunc
CachedGet wraps a GET handler to add ETag and Cache-Control headers. If the request includes an If-None-Match header that matches the computed ETag, it returns 304 Not Modified with no body. Only 200 responses are cached.
func FilterFields ¶
FilterFields returns a copy of record containing only the keys in allowed. If allowed is empty, record is returned unchanged.
func IsServerError ¶
IsServerError reports whether err would be answered as HTTP 500 by WriteDBError (i.e. the classifier has no verdict for it). Used to decide whether to capture a stack trace — client/conflict/availability errors are not bugs.
func RowsToMaps ¶
RowsToMaps converts pgx.Rows to a slice of string-keyed maps. UUID columns ([16]byte) are converted to hyphenated UUID strings.
func WriteDBError ¶
func WriteDBError(w http.ResponseWriter, err error)
WriteDBError maps a database error to an HTTP response without leaking internal details (raw SQL, schema names) to the client. It is the REST single-op rendering of ClassifyWriteError:
- unique violation (23505) → 409 field "x": value already exists
- unknown column on a write (42703) → 422 validation_failed/unknown_field
- bad `file` reference (23503 on the files FK) → 422 validation_failed/file_not_found
- other foreign-key violation (23503) → 409 with the safe message
- missing tenant schema/relation (42P01/3F000) → 400 "invalid tenant"
- caller-supplied bad input (class 22) → 400 "invalid request"
- unreachable database → 503 "service unavailable" + Retry-After
- anything else → 500 "internal error"
Types ¶
type WriteErrorKind ¶ added in v0.1.7
type WriteErrorKind int
WriteErrorKind is the engine's client-facing vocabulary for a write's database error. There is exactly ONE ladder that decides which SQLSTATE means what — ClassifyWriteError below — and four renderers compiled from it: the REST single-op response (WriteDBError), the batch-transaction response (codegen.dbTxError), the GraphQL errors array (graphql.safeDBErr) and the library path's typed errors (Ctx.Insert/Update, ENG-42). A kind added here reaches all four by construction; a kind classified in only one of them is the divergence class CTX_PARITY_AUDIT.md exists to prevent.
const ( // WriteErrNone: not classified — an unexpected server error. Handlers mask // it (500), never leaking the raw driver message. WriteErrNone WriteErrorKind = iota // WriteErrUnique: 23505 unique_violation → 409 `field "x": value already // exists` (Field carries the offending column). WriteErrUnique // WriteErrUnknownColumn: 42703 undefined_column on a write → the S44 422 // {rule:"unknown_field"} (Field carries the column). The DB is the source // of truth for the writable column set (see the no-whitelist NOTE in // codegen.BuildRouter), so this classification happens on the error. WriteErrUnknownColumn // WriteErrFileRef: 23503 on the tenant's files FK (FILES-LINK-S1) → the // S44 422 {rule:"file_not_found"} (Field carries the file field's column). WriteErrFileRef // WriteErrForeignKey: any other 23503 → 409 with the safe message naming // the related resource (Message; never raw SQL). WriteErrForeignKey // WriteErrMissingTenant: 42P01/3F000 → 400 "invalid tenant". WriteErrMissingTenant // WriteErrBadInput: the observed class-22 codes (ADR-024) → 400 "invalid // request" — a caller-supplied value Postgres could not parse for the column. WriteErrBadInput // Retry-After. WriteErrUnavailable )
type WriteErrorVerdict ¶ added in v0.1.7
type WriteErrorVerdict struct {
Kind WriteErrorKind
Field string // WriteErrUnique / WriteErrUnknownColumn / WriteErrFileRef
Message string // WriteErrForeignKey's safe, human-readable message
}
WriteErrorVerdict is ClassifyWriteError's result: the kind plus the safe, client-visible pieces each renderer needs (a column name, a referential message) — never the raw driver error.
func ClassifyWriteError ¶ added in v0.1.7
func ClassifyWriteError(err error) WriteErrorVerdict
ClassifyWriteError is the single classification of a write's database error into the engine's error vocabulary. Every branch matches a predicate in pkg/db whose SQLSTATE was OBSERVED being produced by client input (the ADR-024 discipline: a code classified from theory turns a genuine engine fault into a lying 4xx — so an unobserved code stays WriteErrNone and is masked as the 500 it is).
Order matters only once: the files-FK check must precede the generic FK check (both are 23503).