handlers

package
v0.1.8 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Index

Constants

View Source
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.

View Source
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

func BuildInsertArgs(body map[string]any) (cols, placeholders string, args []any)

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

func FilterFields(record map[string]any, allowed []string) map[string]any

FilterFields returns a copy of record containing only the keys in allowed. If allowed is empty, record is returned unchanged.

func IsServerError

func IsServerError(err error) bool

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

func RowsToMaps(rows pgx.Rows) ([]map[string]any, error)

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"

func WriteJSON

func WriteJSON(w http.ResponseWriter, v any) error

WriteJSON encodes v to JSON and writes it to w using a pooled buffer, avoiding per-request buffer allocations for the JSON encoder. Callers must set Content-Type and status code before calling.

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
	// WriteErrUnavailable: the database cannot serve this right now → 503 +
	// 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).

Jump to

Keyboard shortcuts

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