AnnouncementsIntroducing Arkoren

    Menu

    Validation Errors

    Read structured violations, translate their messages, and distinguish input failures from invalid definitions.

    What's on this page
    1. Classify input violations separately from operational and definition errors.
    2. Read stable codes and JSON Pointers instead of parsing error summaries.
    3. Translate and copy diagnostics with explicit ownership limits.
    On this page

    A form needs field-level correction advice, while a failed lookup needs operational handling. Validation keeps those outcomes separate. Rule failures return validate.Errors, a map from JSON Pointers to violation records; custom checks can instead return their operational error unchanged.

    Examples import arkoren.dev/foundation/validate and use an existing context named ctx. Standard errors and fmt imports are named where used.

    Classify Failures

    After schema.Validate or schema.Parse, use the standard errors package to extract violations rather than treating every error as invalid input:

    var violations validate.Errors
    if errors.As(err, &violations) {
    	// Use violations to identify input that needs correction.
    }

    Other errors include dependency failures and cancellation. Do not translate them into field advice. Success returns nil, never an empty validate.Errors. Parse returns the zero typed result on failure; the decoded-JSON importer returns nil. Neither result is partially accepted input.

    HTTP handlers should use the request validation adapter instead of returning raw validate.Errors, which has no HTTP status. For strict byte checks, also distinguish malformed JSON and resource refusals.

    Read Violations

    A Violation has a stable Code, optional Params, and a localized Message. Match codes in application logic and show messages to people. With fmt imported, iterate in deterministic path order:

    for pointer, failures := range violations.All() {
    	for _, failure := range failures {
    		fmt.Println(pointer, failure.Code, failure.Message)
    	}
    }

    The root pointer is ""; /items/0/quantity selects a nested value. Pointer segments escape / as ~1 and ~ as ~0. Struct paths use JSON-v2 field names, although declarations and At use Go names. All orders digit-only segments numerically, so /items/2 precedes /items/10.

    Error() is a deterministic English summary, not a wire format or the current locale’s message representation. Do not parse it. Pointers and parameters may reveal private member names or application data; redact diagnostics before exposing them beyond their intended audience.

    Translate Messages

    Keep codes stable across languages. Configure messages on a context rather than changing global state:

    catalog := validate.Catalog{
    	Default: "en",
    	Locales: map[string]validate.Messages{
    		"en": {"username.reserved": "This username is reserved."},
    		"es": {"username.reserved": "Este nombre de usuario está reservado."},
    	},
    }
    ctx = validate.NewCatalogContext(ctx, catalog)
    ctx = validate.NewLocaleContext(ctx, "es")

    Checks using this context render Spanish messages for username.reserved without changing its code. Templates interpolate {param} placeholders from the violation parameters. To change a built-in message, start from the mutable copy returned by validate.English() and edit the relevant code.

    Lookup tries the selected locale, its base language, the catalog default and its base language, built-in English, then the code. LocaleFromContext(ctx) returns the selected locale and whether one exists; it does not negotiate a language. Keep catalog maps immutable while requests read them. HTTP locale middleware supplies per-request context.

    For existing diagnostics, localized := violations.Localize(ctx) returns a copy with rerendered messages. Codes, locations, and parameter values remain the same; an unknown code retains its existing message. Describe is unrelated: it annotates JSON Schema and never overrides a violation message.

    Add Diagnostics

    If application code already knows a field-level failure, initialize the map before appending it:

    failures := make(validate.Errors)
    failures.Add("/email", validate.Violation{
    	Code:    "email.taken",
    	Message: "This email address is already registered.",
    })

    Add takes a JSON Pointer; custom checks use At with a Go field name. Add does not copy supplied parameters, and All yields borrowed slices and parameters. Neither creates a snapshot.

    Diagnostic Ownership

    Fail and At capture their parameter containers. The engine detaches supported containers again before rendering/publishing violations, including built-in and imported enum/const parameters. Localize also copies them, even when an unknown code keeps its existing message. Mutating a copied parameter container does not rewrite a reusable failure, schema, or another result.

    Copying traverses acyclic slices, arrays, and maps with string-kind keys, including nested containers reached through interfaces. Named types and nil-versus-empty distinctions are preserved. Cycles along these copied containers are unsupported and are not detected.

    This is not JSON conversion or a general object deep copy. Struct fields, pointers, and non-string-keyed maps are not traversed; opaque native objects, functions, and captures remain application-owned. Copying invokes no custom marshaling methods. Keep shared opaque data immutable or synchronized, and do not mutate borrowed diagnostics concurrently with readers. Parameters need to be JSON-serializable if you encode them; the helpers do not enforce that.

    Definition Errors

    A malformed schema is a programming error, not user input needing correction. ErrInvalidSchema identifies these failures, with different reporting stages:

    Operation Invalid definition outcome
    Struct construction Aggregates recorded problems after callbacks, then panics
    Standalone explicit Parse or Validate Panics during compilation
    JSONSchema export Returns nil bytes and an error
    Infer inspection, execution, or export Returns its inference error rather than panicking
    JSON Schema import Returns a construction error

    These failures wrap ErrInvalidSchema. Recorded problems include missing or duplicate fields, wrong types or paths, Use(nil), negative string lengths or multiple factors, and non-finite floating bounds/factors. They survive later valid settings, reuse, and publication. Aggregated struct diagnostics sort by Go field name, retaining declaration order for ties.

    Some failures are immediate: MultipleOf(0) panics at the setter before later callbacks run. Supplied nil functions and application callback panics also propagate; they are not collected as ordinary registration problems.

    Construct composite schemas with constructors. A zero StructSchema[T] remains invalid even through Ptr; use Struct[T]() for a valid empty registration. Constructed slices/maps may deliberately omit child rules. Struct construction checks registered defaults against their rules, whereas export does not execute those checks or prove default validity. Check export errors before publishing documents; JSON encoding failures preserve the encoder’s cause instead of pretending every failure is a bad rule.

    For exact error and localization contracts, see the validate API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive