Validation — Accumulating Errors
1.51 kB minified + gzipped
A registration form validates a username, an email address, and a password. A check that returns after the first invalid field makes the user fix one problem, submit, and discover the next one; the same round trip repeats for every field that was already wrong. The form has all three values at once, so it can report all three problems at once.
Core Behavior
Section titled “Core Behavior”Validation<E, A> represents a check that either passed with a value or failed with a non-empty
list of errors.
structcombines already-created field results and concatenates every failed field’s errors in own-key order.applycombines a wrapped function and a wrapped argument; when both fail, the function’s errors come first.mapandmapErroract on their matching variant, whilematchreceives the complete error list.- There is no
chain. A check that needs another check’s output must wait for a passed value, which is the shapeResultprovides.
Reach for Validation when independent fields must each report their own error.
Result stops at the first failure, which fits a sequence of dependent steps.
The choosing guide compares the error containers side by side.
Example: Account Registration
Section titled “Example: Account Registration”A registration form needs to check a username, email address, and password. Reporting all invalid fields together lets the user correct them in one pass.
struct receives already-created field results and collects errors from every failed field. It
returns the combined values only when all fields pass:
flowchart TD
Fields["Username, email, and password checks"] --> Collect["struct inspects every field"]
Collect --> Failed{"Any Failed?"}
Failed -->|No| Passed["Passed(registration details)"]
Failed -->|Yes| Errors["Failed(errors in field order)"]
For a failed username followed by a failed email, the error list contains the username errors first,
then the email errors. struct processes fields in own-key enumeration order and preserves the
order within each field’s error list.
Validating Registration Fields
Section titled “Validating Registration Fields”A registration form may return after its first invalid field:
The user must fix the username and submit again before seeing the email error. A single
Validation.from.Predicate makes one check return a value or an error list:
Independent fields can be checked together. A rule comparing the username and password needs both
validated values, so the example converts to Result before that dependent check. The input has
already been decoded into strings by the caller.
struct collects the field errors in enumeration order. to.Result joins them into one message at
the boundary, and the dependent password rule runs only after every field has passed. The error
constructors and error-producing callbacks preserve inline literal types; choose an explicit wider
type if an error must be mutable.
Running the Same Checks Against a Remote Directory
Section titled “Running the Same Checks Against a Remote Directory”Availability cannot be checked from the values alone. Task.Validation keeps the
field results and accumulates their errors while the checks run asynchronously:
The synchronous field checks keep their own error values; only the availability lookup needs an
asynchronous error channel. apply runs both sides and accumulates their errors, so awaiting the
task returns one Validation with every failure from the local checks and the lookup.
Core Operations
Section titled “Core Operations”Creating Values
Section titled “Creating Values”| Operation | What it does | Error or return type |
|---|---|---|
make.passed(value) / make.failed(error) |
Wrap a value / a single error. | Passed<A> / Failed<E> |
make.failedAll(errors) |
Wrap an existing non-empty error array. | Failed<E> |
tryCatch(operation, { onError }) |
Run synchronous code and map a thrown value to one error. | Validation<E, A> |
from.Predicate(predicate, onFalse) |
Check a value; a type guard also narrows it. | Validation<E, A> |
from.nullable(onNull) / from.Maybe(onNone) |
Replace a missing value with one error. | Validation<E, A> |
from.Result(result) |
Convert Ok to Passed and Err to a one-error Failed. |
Validation<E, A> |
Combining Values
Section titled “Combining Values”| Operation | What it does | Guarantee |
|---|---|---|
struct(fields) |
Combine a record of already-created validations. | Collects errors in own-key enumeration order. |
apply(argument) |
Apply a wrapped function to a wrapped argument. | Concatenates errors from the function and argument. |
Transforming Values
Section titled “Transforming Values”| Operation | What it does | Guarantee |
|---|---|---|
map(transform) / mapError(transform) |
Transform a passed value / each error. | The other case passes through. |
tap(effect) / tapError(effect) |
Inspect a passed value / the complete error list. | Returns the original validation when the callback returns. |
recover(fallback) |
Run a fallback validation on failure. | Receives the complete error list. |
recoverUnless(isBlocked, fallback) |
Recover only when no error matches the predicate. | A matching error keeps the original failure. |
Reading and Converting
Section titled “Reading and Converting”| Operation | What it does | Return type |
|---|---|---|
match({ passed, failed }) |
Handle both cases. | The selected handler’s return value. |
getOrElse(fallback) |
Extract success or call a no-argument fallback. | Passed or fallback value. |
is.passed(value) / is.failed(value) |
Test and narrow the variant. | Boolean type guard. |
to.Result(validation) |
Convert to a fail-fast result. | An error contains the non-empty list. |
to.Result(combineErrors) |
Convert using a function to combine the errors. | A curried converter to Result. |
to.Maybe(validation) |
Convert success or failure to presence or absence. | Discards errors. |
Keyed Field Results
Section titled “Keyed Field Results”| Operation | What it does | Return type |
|---|---|---|
keyed.make(validators) |
Run one validator per matching input field. | A validation for each field. |
keyed.is.passed(fields) / keyed.is.failed(fields) |
Check whether all fields passed / any failed. | The passed check narrows each field. |
keyed.getPassed(fields) / keyed.getErrors(fields) |
Extract all passed values / errors grouped by failed field. | Maybe of the record. |
For field-specific messages, keyed.make retains each field’s result. Pass that record to struct
when a single validation with a combined error list is needed. A validator can also transform its
field: the input type comes from the validator’s parameter and the retained result from its return
value.
Failure Behavior and Edge Cases
Section titled “Failure Behavior and Edge Cases”struct combines results that JavaScript has already evaluated; it does not schedule work or run
checks concurrently. An empty record passes with an empty object. Errors retain field enumeration
order and each field’s internal error order. apply likewise receives an already-created argument
and appends the argument’s errors after the function’s errors.
For example, errors from both fields are retained, including multiple errors from one field:
Independent checks can all report failures. A check that needs another check’s output cannot run
until that output exists; convert to Result for dependent fail-fast steps. Validation has no
chain or assign operation.
tryCatch catches synchronous throws from its operation and maps the thrown value to one error. A
throw from onError, a predicate, a mapper, a handler, or any other callback escapes. A promise
returned from tryCatch is a Passed value; a later rejection is not caught. Use Task.Validation
for independent asynchronous validations.
When a form has to decide when errors become visible rather than what the errors are, keep that
decision outside the validators. The per-field validation recipe shows
the trigger and visibility model around Validation.keyed.
Error constructors preserve inline literal types, including
Validation.make.failed("USERNAME_REQUIRED"). Inline object errors can infer readonly literal
properties; supply a wider explicit type when needed. This is a TypeScript inference rule and does
not freeze objects. TypeScript 5.5 can widen primitive literals returned by callbacks, so use
as const when their exact error types are required.