Validation — Accumulating Errors
Using fail-fast error structures (Result, try/catch) for form or schema validation
short-circuits on the first failure, hiding subsequent field errors until previous ones are
corrected.
Validation<E, A> models validation outcomes as a discriminated union: Passed<A> (valid value) or
Failed<E> (accumulated non-empty list of errors). Instead of short-circuiting, it evaluates
independent checks and merges all failures together.
Creating Validations
Section titled “Creating Validations”To begin validating, we lift our data checks into the Validation context:
Under the hood, all failures in Validation are collected inside an Arr.NonEmpty (a type-safe
array guaranteed to contain at least one element). When you call Validation.make.failed(err), the
library automatically wraps your error in an array container.
If you already have an array of errors and want to lift it directly, you can use failedAll:
Constructing checks with from.Predicate
Section titled “Constructing checks with from.Predicate”You can build reusable, rule-specific validation checkers using from.Predicate:
The second argument receives the original input, allowing you to format descriptive, clear feedback for your users.
The Accumulation Pattern: ap
Section titled “The Accumulation Pattern: ap”What makes Validation structurally different from Result is how we combine multiple independent
checks.
The primary tool for this is ap (short for apply). The pattern begins by wrapping a curried
constructor function in passed, and then applying each validated argument one-by-one:
Let’s dissect what happens when this pipeline executes. Each ap step inspects both sides:
- If both the function and the argument have passed, the argument value is applied to the function.
- If either the function or the argument has failed, the errors are gathered.
- If both have failed, their respective error lists are merged.
Because each argument is validated independently before being combined, all validation checks are
guaranteed to run, and every failure is gathered into a single consolidated Failed container.
Alternative Combinators: product and productAll
Section titled “Alternative Combinators: product and productAll”If the curried ap pattern feels unfamiliar or syntactically complex, Validation provides
simpler, array-based alternatives.
Combining two checks with product
Section titled “Combining two checks with product”product takes two independent validations and merges them into a single Validation carrying a
tuple of both values:
If either validation has failed, the errors from both sides are collected and merged.
Combining many checks with productAll
Section titled “Combining many checks with productAll”productAll accepts an array of validations, runs all of them, and returns either a Passed tuple
containing all successfully validated values, or a Failed list containing every accumulated error:
Because productAll expects an Arr.NonEmpty (a non-empty array) of validations, you are
guaranteed that the input list has at least one validation check, carrying a type-safe array of
values, completely avoiding the possibility of empty array inputs or undefined states at compile
time.
Transforming values
Section titled “Transforming values”You can transform the success value inside a Passed container without worrying about the failure
branch using map:
If the validation has failed, map does nothing and lets the accumulated errors propagate.
Extracting the value
Section titled “Extracting the value”Once all checks have run and the errors have been accumulated, you must exit the Validation
context at the edge of your pipeline.
Safe fallbacks with getOrElse
Section titled “Safe fallbacks with getOrElse”getOrElse extracts the validated value from a Passed container, or returns a safe fallback value
if the validations failed:
As with other modules in this library, getOrElse expects a function (a thunk) to defer evaluating
the fallback value, saving execution costs if the validation checks pass successfully.
Exhaustive matching with match and fold
Section titled “Exhaustive matching with match and fold”To drive distinct UI rendering or business branches based on the outcome, you can analyze both cases
using match or fold:
Side effects with tapError
Section titled “Side effects with tapError”When you want to log or inspect validation failures mid-pipeline without altering the validation
flow, you can use tapError. It executes a side-effectful callback only if the validation has
failed, passing the full list of accumulated errors:
Fallback strategies: recover
Section titled “Fallback strategies: recover”recover provides a fallback Validation when validation has failed. It passes the accumulated
error list to your fallback function, allowing you to inspect what went wrong and decide how to
recover dynamically:
Interoperability and Hand-offs
Section titled “Interoperability and Hand-offs”Because software systems use a variety of modeling types, you can translate Validation to and from
other modules.
Discarding errors to Maybe
Section titled “Discarding errors to Maybe”If you only care about obtaining a valid value and do not need to report the reasons for failure,
you can downgrade the Validation to a Maybe using to.Maybe:
Bridging from Result
Section titled “Bridging from Result”When incorporating an operation that throws or fail-fast checks (like a Result parser) into an
accumulating validation flow, you can lift it using from.Result:
The single error from the Err is wrapped in a type-safe Arr.NonEmpty automatically.
Sequencing actions by converting to Result
Section titled “Sequencing actions by converting to Result”Validation is outstanding for running parallel, independent checks. However, once you have
established that the data is 100% valid, you typically need to run sequential side effects that can
fail (like saving to a database, sending a request, or writing to disk).
For this, you should hand off execution to a Result pipeline using to.Result. If you want to
merge accumulated validation errors into a single error string or composite error object, pass a
combiner function to to.Result:
Calling Validation.to.Result without arguments retains the raw Arr.NonEmpty<E> error list inside
Err.
This hand-off represents a highly common, elegant pattern in production applications: use
Validation to gather all input friction, convert to Result once the data is clean, and use
Result.chain to sequence sequential database or network actions.
Why Validation has no bind / bindTo
Section titled “Why Validation has no bind / bindTo”A developer familiar with other modules in this library (like Result or Maybe) might wonder why
Validation completely omits bind and bindTo helpers.
This omission is a deliberate architectural and structural design choice.
bind and bindTo represent monadic sequencing. In a sequential pipeline:
Step B (prefs) requires the successful output of Step A (user). If Step A fails, Step B cannot
run. This sequential dependency naturally defeats the primary, first-class purpose of Validation —
independent error accumulation. If steps are dependent, we cannot evaluate them in parallel, and
we cannot gather all errors across all branches at the same time.
For sequenced pipelines that depend on prior steps, you should use Result (which naturally
supports bind and bindTo for fail-fast chaining). If you need to validate independent inputs,
keep them in Validation. Once the data is validated, you can cleanly hand it off to a Result
pipeline using Validation.to.Result to perform sequential actions.
Combining records: struct
Section titled “Combining records: struct”To combine multiple independent validation checks into a single validated object, you can use
Validation.struct.
Unlike Result.struct or Maybe.struct (which short-circuit on the first failure),
Validation.struct accumulates errors from all failed branches into a single Failed list:
If all validation fields pass successfully, it returns a Passed container containing the fully
constructed record:
Asynchronous Validation: Task.Validation
Section titled “Asynchronous Validation: Task.Validation”When you need to perform validation checks that require asynchronous operations (such as making a
network request to verify email availability, or querying a database to check if a username is
taken), you can use Task.Validation<E, A>.
Under the hood, Task.Validation is an alias for Task<Validation<E, A>>. Like Validation, it
concurrently evaluates independent checks and aggregates all failures into a single list rather than
failing fast.
To run multiple asynchronous validation checks in parallel and accumulate any failures, combine them
using Task.Validation.struct:
If any check fails, the task resolves to a Failed variant containing the accumulated list of all
errors.
Transforming and Inspecting Errors
Section titled “Transforming and Inspecting Errors”Because validation processes are designed to accumulate multiple failures, we often need to format
or log these errors mid-pipeline. Task.Validation provides dedicated error-mapping combinators for
this:
mapError: Transforms all accumulated errors in the list using a function(e: E) => F.tapError: Executes a side effect (such as logging warnings) on the accumulated list of errors without modifying the pipeline’s value.
Problems it solves
Section titled “Problems it solves”- Multi-field web form validation: In registration forms, profile editors, and checkout panels,
users frequently submit multiple invalid fields at once. Short-circuiting error handlers stop on
the first error, forcing frustrating trial-and-error submissions.
Validationaggregates all field errors into a single collection, allowing UIs to render feedback on every invalid input simultaneously. - Batch data ingestion and bulk import diagnostics: When importing CSV spreadsheets, parsing
configuration files, or validating batch API payloads, systems need a full accounting of all
invalid records.
Validation.structandValidation.allevaluate all records in parallel, gathering every schema and domain violation into a complete diagnostic report rather than halting on row one. - Multi-rule entity policy checks: When evaluating complex entity constraints (such as password
strength requirements, credit assessment criteria, or compliance checklists),
Validationruns all checks independently and reports every unmet condition at once for comprehensive user feedback. - Bridging validation accumulation with fail-fast execution (
Validation.to.Result,Validation.from.Result): In web endpoints, validating an incoming request body requires accumulating all field errors withValidation, but subsequent database operations require fail-fast sequential chaining withResult.Validation.to.Resultbridges these patterns cleanly, preserving all collected errors while transitioning into standard sequential pipelines. - Contextual error tagging across nested forms (
Validation.mapError): In nested forms and multi-step wizards, child components validate their own sub-fields.Validation.mapErrorallows parent forms to namespace child errors (e.g. prefixingaddress.zipCode) before merging, keeping error messages structured and localized.