Documentation
¶
Overview ¶
Package validation provides functional validation types and operations for the codec system.
This package implements a validation monad that accumulates errors during validation operations, making it ideal for form validation, data parsing, and other scenarios where you want to collect all validation errors rather than failing on the first error.
Core Concepts ¶
Validation[A]: Represents the result of a validation operation as Either[Errors, A]:
- Left(Errors): Validation failed with one or more errors
- Right(A): Successfully validated value of type A
ValidationError: A detailed error type that includes:
- Value: The actual value that failed validation
- Context: The path through nested structures (e.g., "user.address.zipCode")
- Message: Human-readable error description
- Cause: Optional underlying error
Context: A stack of ContextEntry values that tracks the validation path through nested data structures, enabling precise error reporting.
Basic Usage ¶
Creating validation results:
// Success case
valid := validation.Success(42)
// Failure case
invalid := validation.Failures[int](validation.Errors{
&validation.ValidationError{
Value: "not a number",
Message: "expected integer",
Context: nil,
},
})
Using with context:
failWithMsg := validation.FailureWithMessage[int]("invalid", "must be positive")
result := failWithMsg([]validation.ContextEntry{
{Key: "age", Type: "int"},
})
Applicative Validation ¶
The validation type supports applicative operations, allowing you to combine multiple validations and accumulate all errors:
type User struct {
Name string
Email string
Age int
}
validateName := func(s string) validation.Validation[string] {
if len(s) > 0 {
return validation.Success(s)
}
return validation.Failures[string](/* error */)
}
// Combine validations - all errors will be collected
result := validation.Ap(validation.Ap(validation.Ap(
validation.Of(func(name string) func(email string) func(age int) User {
return func(email string) func(age int) User {
return func(age int) User {
return User{name, email, age}
}
}
}),
)(validateName("")))(validateEmail("")))(validateAge(-1))
Error Formatting ¶
ValidationError implements custom formatting for detailed error messages:
err := &ValidationError{
Value: "abc",
Context: []ContextEntry{{Key: "user"}, {Key: "age"}},
Message: "expected integer",
}
fmt.Printf("%v", err) // at user.age: expected integer
fmt.Printf("%+v", err) // at user.age: expected integer
// value: "abc"
Monoid Operations ¶
The package provides monoid instances for combining validations:
// Combine validation results
m := validation.ApplicativeMonoid(stringMonoid)
combined := m.Concat(validation.Success("hello"), validation.Success(" world"))
// Result: Success("hello world")
Integration ¶
This package integrates with:
- either: Validation is built on Either for error handling
- array: For collecting multiple errors
- monoid: For combining validation results
- reader: For context-dependent validation operations
Index ¶
- func Applicative[A, B any]() ...
- func MakeValidationErrors(errors Errors) error
- type Context
- type ContextEntry
- type Either
- type Endomorphism
- type Errors
- type Kleisli
- type Monoid
- type Operator
- func Ap[B, A any](fa Validation[A]) Operator[func(A) B, B]
- func ApS[S1, S2, T any](setter func(T) func(S1) S2, fa Validation[T]) Operator[S1, S2]
- func ApSL[S, T any](lens L.Lens[S, T], fa Validation[T]) Operator[S, S]
- func Bind[S1, S2, A any](setter func(A) func(S1) S2, f Kleisli[S1, A]) Operator[S1, S2]
- func BindL[S, T any](lens L.Lens[S, T], f Kleisli[T, T]) Operator[S, S]
- func BindTo[S1, T any](setter func(T) S1) Operator[T, S1]
- func Chain[A, B any](f Kleisli[A, B]) Operator[A, B]
- func ChainLeft[A any](f Kleisli[Errors, A]) Operator[A, A]
- func Let[S1, S2, B any](key func(B) func(S1) S2, f func(S1) B) Operator[S1, S2]
- func LetL[S, T any](lens L.Lens[S, T], f Endomorphism[T]) Operator[S, S]
- func LetTo[S1, S2, B any](key func(B) func(S1) S2, b B) Operator[S1, S2]
- func LetToL[S, T any](lens L.Lens[S, T], b T) Operator[S, S]
- func Map[A, B any](f func(A) B) Operator[A, B]
- type Reader
- type Result
- type Validation
- func Do[S any](empty S) Validation[S]
- func Failures[T any](err Errors) Validation[T]
- func MonadAp[B, A any](fab Validation[func(A) B], fa Validation[A]) Validation[B]
- func MonadChain[A, B any](fa Validation[A], f Kleisli[A, B]) Validation[B]
- func MonadChainLeft[A any](fa Validation[A], f Kleisli[Errors, A]) Validation[A]
- func MonadMap[A, B any](fa Validation[A], f func(A) B) Validation[B]
- func Of[A any](a A) Validation[A]
- func Success[T any](value T) Validation[T]
- type ValidationError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Applicative ¶
func Applicative[A, B any]() applicative.Applicative[A, B, Validation[A], Validation[B], Validation[func(A) B]]
Applicative creates an Applicative instance for Validation with error accumulation.
This returns a lawful Applicative that accumulates validation errors using the Errors monoid. Unlike the standard Either applicative which fails fast, this validation applicative collects all errors when combining independent validations with Ap.
The returned instance satisfies all applicative laws:
- Identity: Ap(Of(identity))(v) == v
- Homomorphism: Ap(Of(f))(Of(x)) == Of(f(x))
- Interchange: Ap(Of(f))(u) == Ap(Map(f => f(y))(u))(Of(y))
- Composition: Ap(Ap(Map(compose)(f))(g))(x) == Ap(f)(Ap(g)(x))
Key behaviors:
- Of: lifts a value into a successful Validation (Right)
- Map: transforms successful values, preserves failures (standard functor)
- Ap: when both operands fail, combines all errors using the Errors monoid
This is particularly useful for form validation, configuration validation, and any scenario where you want to collect all validation errors at once rather than stopping at the first failure.
Example - Validating Multiple Fields:
app := Applicative[string, User]()
// Validate individual fields
validateName := func(name string) Validation[string] {
if len(name) < 3 {
return Failure("Name must be at least 3 characters")
}
return Success(name)
}
validateAge := func(age int) Validation[int] {
if age < 18 {
return Failure("Must be 18 or older")
}
return Success(age)
}
// Create a curried constructor
makeUser := func(name string) func(int) User {
return func(age int) User {
return User{Name: name, Age: age}
}
}
// Combine validations - all errors are collected
name := validateName("ab") // Failure: name too short
age := validateAge(16) // Failure: age too low
result := app.Ap(age)(app.Ap(name)(app.Of(makeUser)))
// result contains both validation errors:
// - "Name must be at least 3 characters"
// - "Must be 18 or older"
Type Parameters:
- A: The input value type (Right value)
- B: The output value type after transformation
Returns:
An Applicative instance with Of, Map, and Ap operations that accumulate errors
func MakeValidationErrors ¶ added in v2.1.19
func MakeValidationErrors(errors Errors) error
MakeValidationErrors converts a collection of validation errors into a single error. It wraps the Errors slice in a ValidationErrors struct that implements the error interface. This is useful for converting validation failures into standard Go errors.
Parameters:
- errors: A slice of ValidationError pointers representing validation failures
Returns:
- An error that contains all the validation errors and can be used with standard error handling
Example:
errors := Errors{
&ValidationError{Value: "abc", Messsage: "expected number"},
&ValidationError{Value: nil, Messsage: "required field"},
}
err := MakeValidationErrors(errors)
fmt.Println(err) // Output: ValidationErrors: 2 errors
Types ¶
type Context ¶
type Context = []ContextEntry
Context is a stack of ContextEntry values representing the path through nested structures during validation. Used to provide detailed error messages that show exactly where in a nested structure a validation failure occurred.
The context builds up as validation descends into nested structures:
- [] - root level
- [{Key: "user"}] - inside user object
- [{Key: "user"}, {Key: "address"}] - inside user.address
- [{Key: "user"}, {Key: "address"}, {Key: "zipCode"}] - at user.address.zipCode
Example:
ctx := Context{
{Key: "user", Type: "User"},
{Key: "address", Type: "Address"},
{Key: "zipCode", Type: "string"},
}
// Represents path: user.address.zipCode
type ContextEntry ¶
type ContextEntry struct {
Key string // The key or field name (for objects/maps)
Type string // The expected type name
Actual any // The actual value being validated
}
ContextEntry represents a single entry in the validation context path. It tracks the location and type information during nested validation, enabling precise error reporting with full path information.
Fields:
- Key: The key or field name (e.g., "email", "address", "items[0]")
- Type: The expected type name (e.g., "string", "int", "User")
- Actual: The actual value being validated (for error reporting)
Example:
entry := ContextEntry{
Key: "user.email",
Type: "string",
Actual: 12345,
}
type Either ¶
Either represents a value that can be one of two types: Left (error) or Right (success). This is an alias for either.Either[E, A], a disjoint union type.
In the validation context:
- Left[E]: Contains error information of type E
- Right[A]: Contains a successfully validated value of type A
type Endomorphism ¶ added in v2.2.6
type Endomorphism[A any] = endomorphism.Endomorphism[A]
Endomorphism represents a function from a type to itself: func(A) A. This is an alias for endomorphism.Endomorphism[A].
In the validation context, endomorphisms are used with LetL to transform values within a validation context using pure functions.
Example:
double := func(x int) int { return x * 2 } // Endomorphism[int]
result := LetL(lens, double)(Success(21)) // Success(42)
type Errors ¶
type Errors = []*ValidationError
Errors is a collection of validation errors. This type is used to accumulate multiple validation failures, allowing all errors to be reported at once rather than failing fast.
Example:
errors := Errors{
&ValidationError{Value: "", Messsage: "name is required"},
&ValidationError{Value: "invalid", Messsage: "invalid email"},
&ValidationError{Value: -1, Messsage: "age must be positive"},
}
type Kleisli ¶
type Kleisli[A, B any] = Reader[A, Validation[B]]
Kleisli represents a function from A to a validated B. It's a Reader that takes an input A and produces a Validation[B]. This is the fundamental building block for composable validation operations.
Type: func(A) Validation[B]
Kleisli arrows can be composed using Chain/Bind operations to build complex validation pipelines from simple validation functions.
Example:
validatePositive := func(x int) Validation[int] {
if x > 0 {
return Success(x)
}
return Failures[int](/* error */)
}
validateEven := func(x int) Validation[int] {
if x%2 == 0 {
return Success(x)
}
return Failures[int](/* error */)
}
// Compose validations
validatePositiveEven := Chain(validateEven)(Success(42))
type Monoid ¶
Monoid represents an algebraic structure with an associative binary operation and an identity element. This is an alias for monoid.Monoid[A].
In the validation context, monoids are used to combine validation results:
- ApplicativeMonoid: Combines successful validations using the monoid operation
- AlternativeMonoid: Provides fallback behavior for failed validations
Example:
import N "github.com/IBM/fp-go/v2/number" intAdd := N.MonoidSum[int]() m := ApplicativeMonoid(intAdd) result := m.Concat(Success(5), Success(3)) // Success(8)
func ApplicativeMonoid ¶
func ApplicativeMonoid[A any](m Monoid[A]) Monoid[Validation[A]]
ApplicativeMonoid creates a Monoid instance for Validation[A] given a Monoid for A. This allows combining validation results where the success values are also combined using the provided monoid. If any validation fails, all errors are accumulated.
The resulting monoid:
- Empty: Returns a successful validation with the empty value from the inner monoid
- Concat: Combines two validations:
- Both success: Combines values using the inner monoid
- Any failure: Accumulates all errors
Example:
import "github.com/IBM/fp-go/v2/string"
// Create a monoid for validations of strings
m := ApplicativeMonoid(string.Monoid)
v1 := Success("Hello")
v2 := Success(" World")
combined := m.Concat(v1, v2) // Success("Hello World")
v3 := Failures[string](someErrors)
failed := m.Concat(v1, v3) // Failures with accumulated errors
func ErrorsMonoid ¶
func ErrorsMonoid() Monoid[Errors]
ErrorsMonoid returns a Monoid instance for Errors (array of ValidationError pointers). The monoid concatenates error arrays, with an empty array as the identity element. This is used internally by the applicative operations to accumulate validation errors.
Example:
m := ErrorsMonoid() combined := m.Concat(errors1, errors2) // Concatenates both error arrays empty := m.Empty() // Returns empty error array
type Operator ¶
type Operator[A, B any] = Kleisli[Validation[A], B]
Operator represents a validation transformation that takes a validated A and produces a validated B. It's a specialized Kleisli arrow for composing validation operations where the input is already a Validation[A].
Type: func(Validation[A]) Validation[B]
Operators are used to transform and compose validation results, enabling functional composition of validation pipelines.
Example:
// Transform a validated int to a validated string
intToString := Map(func(x int) string {
return strconv.Itoa(x)
}) // Operator[int, string]
result := intToString(Success(42)) // Success("42")
func Ap ¶
func Ap[B, A any](fa Validation[A]) Operator[func(A) B, B]
Ap applies a validation containing a function to a validation containing a value. This is the applicative apply operation that accumulates errors from both validations. If either validation fails, all errors are collected. If both succeed, the function is applied.
This enables combining multiple validations while collecting all errors:
Example:
// Validate multiple fields and collect all errors
validateUser := Ap(Ap(Of(func(name string) func(age int) User {
return func(age int) User { return User{name, age} }
}))(validateName))(validateAge)
func ApS ¶ added in v2.2.6
func ApS[S1, S2, T any]( setter func(T) func(S1) S2, fa Validation[T], ) Operator[S1, S2]
ApS attaches a value to a context S1 to produce a context S2 by considering the context and the value concurrently. This uses the applicative functor pattern, allowing parallel composition.
IMPORTANT: Unlike Bind which fails fast, ApS aggregates ALL validation errors from both the context and the value. If both validations fail, all errors are collected and returned together. This is useful for validating multiple independent fields and reporting all errors at once.
Example:
type State struct { x int; y int }
result := F.Pipe2(
Do(State{}),
ApS(func(x int) func(State) State {
return func(s State) State { s.x = x; return s }
}, Success(42)),
)
Error aggregation example:
stateFailure := Failures[State](Errors{&ValidationError{Messsage: "state error"}})
valueFailure := Failures[int](Errors{&ValidationError{Messsage: "value error"}})
result := ApS(setter, valueFailure)(stateFailure)
// Result contains BOTH errors: ["state error", "value error"]
func ApSL ¶ added in v2.2.6
ApSL attaches a value to a context using a lens-based setter. This is a convenience function that combines ApS with a lens, allowing you to use optics to update nested structures in a more composable way.
IMPORTANT: Like ApS, this function aggregates ALL validation errors. If both the context and the value fail validation, all errors are collected and returned together. This enables comprehensive error reporting for complex nested structures.
The lens parameter provides both the getter and setter for a field within the structure S. This eliminates the need to manually write setter functions.
Example:
type Address struct {
Street string
City string
}
type Person struct {
Name string
Address Address
}
// Create a lens for the Address field
addressLens := lens.MakeLens(
func(p Person) Address { return p.Address },
func(p Person, a Address) Person { p.Address = a; return p },
)
// Use ApSL to update the address
result := F.Pipe2(
Success(Person{Name: "Alice"}),
ApSL(
addressLens,
Success(Address{Street: "Main St", City: "NYC"}),
),
)
func Bind ¶ added in v2.2.6
func Bind[S1, S2, A any]( setter func(A) func(S1) S2, f Kleisli[S1, A], ) Operator[S1, S2]
Bind attaches the result of a computation to a context S1 to produce a context S2. This is used in do-notation style to sequentially build up a context.
Example:
type State struct { x int; y int }
result := F.Pipe2(
Do(State{}),
Bind(func(x int) func(State) State {
return func(s State) State { s.x = x; return s }
}, func(s State) Validation[int] { return Success(42) }),
)
func BindL ¶ added in v2.2.6
BindL attaches the result of a computation to a context using a lens-based setter. This is a convenience function that combines Bind with a lens, allowing you to use optics to update nested structures based on their current values.
The lens parameter provides both the getter and setter for a field within the structure S. The computation function f receives the current value of the focused field and returns a Validation that produces the new value.
Unlike ApSL, BindL uses monadic sequencing, meaning the computation f can depend on the current value of the focused field.
Example:
type Counter struct {
Value int
}
valueLens := lens.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
// Increment the counter, but fail if it would exceed 100
increment := func(v int) Validation[int] {
if v >= 100 {
return Failures[int](Errors{&ValidationError{Messsage: "exceeds limit"}})
}
return Success(v + 1)
}
result := F.Pipe1(
Success(Counter{Value: 42}),
BindL(valueLens, increment),
) // Success(Counter{Value: 43})
func BindTo ¶ added in v2.2.6
func BindTo[S1, T any]( setter func(T) S1, ) Operator[T, S1]
BindTo initializes a new state S1 from a value T. This is typically used as the first operation after creating a Validation value.
Example:
type State struct { value int }
result := F.Pipe1(
Success(42),
BindTo(func(x int) State { return State{value: x} }),
)
func Chain ¶ added in v2.1.21
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B]
Chain is the curried version of MonadChain. Sequences two validation computations where the second depends on the first.
Example:
validatePositive := func(x int) Validation[int] {
if x > 0 { return Success(x) }
return Failure("must be positive")
}
result := Chain(validatePositive)(Success(42)) // Success(42)
func ChainLeft ¶ added in v2.2.8
func ChainLeft[A any](f Kleisli[Errors, A]) Operator[A, A]
ChainLeft is the curried version of MonadChainLeft. Returns a function that transforms validation failures while preserving successes.
Unlike the standard Either ChainLeft which replaces errors, this validation-specific implementation **aggregates errors** using the Errors monoid. When the transformation function returns a failure, both the original errors and the new errors are combined, ensuring no validation errors are lost.
This is particularly useful for:
- Error recovery with fallback validation
- Adding contextual information to existing errors
- Transforming error types while preserving all error details
- Building error handling pipelines that accumulate failures
Key behavior:
- Success values pass through unchanged
- When transforming failures, if the transformation also fails, **all errors are aggregated**
- If the transformation succeeds, it recovers from the original failure
Example - Error recovery with aggregation:
recoverFromNotFound := ChainLeft(func(errs Errors) Validation[int] {
// Check if this is a "not found" error
for _, err := range errs {
if err.Messsage == "not found" {
return Success(0) // recover with default
}
}
// Add context to existing errors
return Failures[int](Errors{
&ValidationError{Messsage: "recovery failed"},
})
// Result will contain BOTH original errors AND "recovery failed"
})
result := recoverFromNotFound(Failures[int](Errors{
&ValidationError{Messsage: "database error"},
}))
// Result contains: ["database error", "recovery failed"]
Example - Adding context to errors:
addContext := ChainLeft(func(errs Errors) Validation[string] {
// Add contextual information
return Failures[string](Errors{
&ValidationError{
Messsage: "validation failed in user.email field",
},
})
// Original errors are preserved and new context is added
})
result := F.Pipe1(
Failures[string](Errors{
&ValidationError{Messsage: "invalid format"},
}),
addContext,
)
// Result contains: ["invalid format", "validation failed in user.email field"]
Example - Success values pass through:
handler := ChainLeft(func(errs Errors) Validation[int] {
return Failures[int](Errors{
&ValidationError{Messsage: "never called"},
})
})
result := handler(Success(42)) // Success(42) - unchanged
func Let ¶ added in v2.2.6
func Let[S1, S2, B any]( key func(B) func(S1) S2, f func(S1) B, ) Operator[S1, S2]
Let attaches the result of a pure computation to a context S1 to produce a context S2. Unlike Bind, the computation function returns a plain value, not an Option.
Example:
type State struct { x int; computed int }
result := F.Pipe2(
Do(State{x: 5}),
Let(func(c int) func(State) State {
return func(s State) State { s.computed = c; return s }
}, func(s State) int { return s.x * 2 }),
)
func LetL ¶ added in v2.2.6
LetL attaches the result of a pure computation to a context using a lens-based setter. This is a convenience function that combines Let with a lens, allowing you to use optics to update nested structures with pure transformations.
The lens parameter provides both the getter and setter for a field within the structure S. The transformation function f receives the current value of the focused field and returns the new value directly (not wrapped in Validation).
This is useful for pure transformations that cannot fail, such as mathematical operations, string manipulations, or other deterministic updates.
Example:
type Counter struct {
Value int
}
valueLens := lens.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
// Double the counter value
double := func(v int) int { return v * 2 }
result := F.Pipe1(
Success(Counter{Value: 21}),
LetL(valueLens, double),
) // Success(Counter{Value: 42})
func LetTo ¶ added in v2.2.6
func LetTo[S1, S2, B any]( key func(B) func(S1) S2, b B, ) Operator[S1, S2]
LetTo attaches a constant value to a context S1 to produce a context S2.
Example:
type State struct { x int; name string }
result := F.Pipe2(
Do(State{x: 5}),
LetTo(func(n string) func(State) State {
return func(s State) State { s.name = n; return s }
}, "example"),
)
func LetToL ¶ added in v2.2.6
LetToL attaches a constant value to a context using a lens-based setter. This is a convenience function that combines LetTo with a lens, allowing you to use optics to set nested fields to specific values.
The lens parameter provides the setter for a field within the structure S. Unlike LetL which transforms the current value, LetToL simply replaces it with the provided constant value b.
This is useful for resetting fields, initializing values, or setting fields to predetermined constants.
Example:
type Config struct {
Debug bool
Timeout int
}
debugLens := lens.MakeLens(
func(c Config) bool { return c.Debug },
func(c Config, d bool) Config { c.Debug = d; return c },
)
result := F.Pipe1(
Success(Config{Debug: true, Timeout: 30}),
LetToL(debugLens, false),
) // Success(Config{Debug: false, Timeout: 30})
func Map ¶
func Map[A, B any](f func(A) B) Operator[A, B]
Map transforms the value inside a successful validation using the provided function. If the validation is a failure, the errors are preserved unchanged. This is the functor map operation for Validation.
Map is used for transforming successful values without changing the validation context. It's the most basic operation for working with validated values and forms the foundation for more complex validation pipelines.
Behavior:
- Success: applies function to value → Success(f(value))
- Failure: preserves errors unchanged → Failure(same errors)
This is useful for:
- Type transformations: converting validated values to different types
- Value transformations: normalizing, formatting, or computing derived values
- Pipeline composition: chaining multiple transformations
- Preserving validation context: errors pass through unchanged
Example - Transform successful value:
doubled := Map(func(x int) int { return x * 2 })(Of(21))
// Result: Success(42)
Example - Failure preserved:
result := Map(func(x int) int { return x * 2 })(
Failures[int](Errors{&ValidationError{Messsage: "invalid"}}),
)
// Result: Failure with same error: ["invalid"]
Example - Type transformation:
toString := Map(func(x int) string { return fmt.Sprintf("%d", x) })
result := toString(Of(42))
// Result: Success("42")
Example - Chaining transformations:
result := F.Pipe3(
Of(5),
Map(func(x int) int { return x + 10 }), // 15
Map(func(x int) int { return x * 2 }), // 30
Map(func(x int) string { return fmt.Sprintf("%d", x) }), // "30"
)
// Result: Success("30")
type Reader ¶
Reader represents a computation that depends on an environment R and produces a value A. This is an alias for reader.Reader[R, A], which is func(R) A.
In the validation context, Reader is used for context-dependent validation operations where the validation logic needs access to the current validation context path.
Example:
validateWithContext := func(ctx Context) Validation[int] {
// Use ctx to provide detailed error messages
return Success(42)
}
func FailureWithError ¶
func FailureWithError[T any](value any, message string) Reader[error, Reader[Context, Validation[T]]]
FailureWithError creates a validation failure with a custom message and underlying cause. Returns a Reader that takes an error, then a Context, and produces a Validation[T] failure. This is useful for wrapping errors from other operations while maintaining validation context.
Example:
fail := FailureWithError[int]("abc", "parse failed")
result := fail(parseErr)([]ContextEntry{{Key: "count", Type: "int"}})
func FailureWithMessage ¶
FailureWithMessage creates a validation failure with a custom message. Returns a Reader that takes a Context and produces a Validation[T] failure. This is useful for creating context-aware validation errors.
Example:
fail := FailureWithMessage[int]("abc", "expected integer")
result := fail([]ContextEntry{{Key: "age", Type: "int"}})
type Result ¶ added in v2.1.19
Result represents a computation that may succeed with a value of type A or fail with an error. This is an alias for result.Result[A], which is Either[error, A].
Used for converting validation results to standard Go error handling patterns.
func ToResult ¶ added in v2.1.19
ToResult converts a Validation[T] to a Result[T]. It transforms the Left side (validation errors) into a standard error using MakeValidationErrors, while preserving the Right side (successful value) unchanged. This is useful for integrating validation results with code that expects Result types.
Type Parameters:
- T: The type of the successfully validated value
Parameters:
- val: A Validation[T] which is Either[Errors, T]
Returns:
- A Result[T] which is Either[error, T], with validation errors converted to a single error
Example:
validation := Success[int](42)
result := ToResult(validation) // Result containing 42
validation := Failures[int](Errors{&ValidationError{Messsage: "invalid"}})
result := ToResult(validation) // Result containing ValidationErrors error
type Validation ¶
Validation represents the result of a validation operation. It's an Either type where:
- Left(Errors): Validation failed with one or more errors
- Right(A): Successfully validated value of type A
This type supports applicative operations, allowing multiple validations to be combined while accumulating all errors rather than failing fast.
Example:
// Success case
valid := Success(42) // Right(42)
// Failure case
invalid := Failures[int](Errors{
&ValidationError{Messsage: "must be positive"},
}) // Left([...])
// Combining validations (accumulates all errors)
result := Ap(Ap(Of(func(x int) func(y int) int {
return func(y int) int { return x + y }
}))(validateX))(validateY)
func Do ¶ added in v2.2.6
func Do[S any]( empty S, ) Validation[S]
Do creates an empty context of type S to be used with the Bind operation. This is the starting point for building up a context using do-notation style.
Example:
type Result struct {
x int
y string
}
result := Do(Result{})
func Failures ¶
func Failures[T any](err Errors) Validation[T]
Failures creates a validation failure from a collection of errors. Returns a Left Either containing the errors.
func MonadAp ¶ added in v2.1.21
func MonadAp[B, A any](fab Validation[func(A) B], fa Validation[A]) Validation[B]
MonadAp applies a validation containing a function to a validation containing a value. This is the applicative apply operation that **accumulates errors** from both validations.
**Key behavior**: Unlike Either's MonadAp which fails fast (returns first error), this validation-specific implementation **accumulates all errors** using the Errors monoid. When both the function validation and value validation fail, all errors from both are combined.
This error accumulation is the defining characteristic of the Validation applicative, making it ideal for scenarios where you want to collect all validation failures at once rather than stopping at the first error.
Behavior:
- Both succeed: applies the function to the value → Success(result)
- Function fails, value succeeds: returns function's errors → Failure(func errors)
- Function succeeds, value fails: returns value's errors → Failure(value errors)
- Both fail: **combines all errors** → Failure(func errors + value errors)
This is particularly useful for:
- Form validation: collect all field errors at once
- Configuration validation: report all invalid settings together
- Data validation: accumulate all constraint violations
- Multi-field validation: validate independent fields in parallel
Example - Both succeed:
double := func(x int) int { return x * 2 }
result := MonadAp(Of(double), Of(21))
// Result: Success(42)
Example - Error accumulation (key feature):
funcValidation := Failures[func(int) int](Errors{
&ValidationError{Messsage: "function error"},
})
valueValidation := Failures[int](Errors{
&ValidationError{Messsage: "value error"},
})
result := MonadAp(funcValidation, valueValidation)
// Result: Failure with BOTH errors: ["function error", "value error"]
Example - Validating multiple fields:
type User struct {
Name string
Age int
}
makeUser := func(name string) func(int) User {
return func(age int) User { return User{name, age} }
}
nameValidation := validateName("ab") // Fails: too short
ageValidation := validateAge(16) // Fails: too young
// First apply name
step1 := MonadAp(Of(makeUser), nameValidation)
// Then apply age
result := MonadAp(step1, ageValidation)
// Result contains ALL validation errors from both fields
func MonadChain ¶ added in v2.1.21
func MonadChain[A, B any](fa Validation[A], f Kleisli[A, B]) Validation[B]
MonadChain sequences two validation computations where the second depends on the first. If the first validation fails, returns the failure without executing the second. This is the monadic bind operation for Validation.
Example:
result := MonadChain(
Success(42),
func(x int) Validation[string] {
return Success(fmt.Sprintf("Value: %d", x))
},
) // Success("Value: 42")
func MonadChainLeft ¶ added in v2.2.8
func MonadChainLeft[A any](fa Validation[A], f Kleisli[Errors, A]) Validation[A]
MonadChainLeft sequences a computation on the failure (Left) channel of a Validation. If the Validation is a failure, applies the function to transform or recover from the errors. If the Validation is a success, returns the success value unchanged.
**Critical difference from Either.MonadChainLeft**: This validation-specific implementation **aggregates errors** using the Errors monoid. When the transformation function returns a failure, both the original errors and the new errors are combined, ensuring comprehensive error reporting.
This is the dual of MonadChain - while Chain operates on success values, ChainLeft operates on failure values. It's particularly useful for:
- Error recovery: converting specific errors into successful values
- Error enrichment: adding context or transforming error messages
- Fallback logic: providing alternative validations when the first fails
- Error aggregation: combining multiple validation failures
The function parameter receives the collection of validation errors and must return a new Validation[A]. This allows you to:
- Recover by returning Success(value)
- Transform errors by returning Failures(newErrors) - **original errors are preserved**
- Implement conditional error handling based on error content
Example - Error recovery:
result := MonadChainLeft(
Failures[int](Errors{
&ValidationError{Messsage: "not found"},
}),
func(errs Errors) Validation[int] {
// Check if we can recover
for _, err := range errs {
if err.Messsage == "not found" {
return Success(0) // recover with default value
}
}
return Failures[int](errs) // propagate errors
},
) // Success(0)
Example - Error aggregation (key feature):
result := MonadChainLeft(
Failures[string](Errors{
&ValidationError{Messsage: "error 1"},
&ValidationError{Messsage: "error 2"},
}),
func(errs Errors) Validation[string] {
// Transformation also fails
return Failures[string](Errors{
&ValidationError{Messsage: "error 3"},
})
},
)
// Result contains ALL errors: ["error 1", "error 2", "error 3"]
// This is different from Either.MonadChainLeft which would only keep "error 3"
Example - Adding context to errors:
result := MonadChainLeft(
Failures[int](Errors{
&ValidationError{Value: "abc", Messsage: "invalid number"},
}),
func(errs Errors) Validation[int] {
// Add contextual information
contextErrors := Errors{
&ValidationError{
Context: []ContextEntry{{Key: "user", Type: "User"}, {Key: "age", Type: "int"}},
Messsage: "failed to parse user age",
},
}
return Failures[int](contextErrors)
},
)
// Result contains both original error and context:
// ["invalid number", "failed to parse user age"]
Example - Success values pass through:
result := MonadChainLeft(
Success(42),
func(errs Errors) Validation[int] {
return Failures[int](Errors{
&ValidationError{Messsage: "never called"},
})
},
) // Success(42) - unchanged
func MonadMap ¶ added in v2.1.21
func MonadMap[A, B any](fa Validation[A], f func(A) B) Validation[B]
MonadMap transforms the value inside a successful validation using the provided function. If the validation is a failure, the errors are preserved unchanged. This is the non-curried version of Map.
MonadMap is useful when you have both the validation and the transformation function available at the same time, rather than needing to create a reusable operator.
Behavior:
- Success: applies function to value → Success(f(value))
- Failure: preserves errors unchanged → Failure(same errors)
Example - Transform successful value:
result := MonadMap(Of(21), func(x int) int { return x * 2 })
// Result: Success(42)
Example - Failure preserved:
result := MonadMap(
Failures[int](Errors{&ValidationError{Messsage: "invalid"}}),
func(x int) int { return x * 2 },
)
// Result: Failure with same error: ["invalid"]
Example - Type transformation:
result := MonadMap(Of(42), func(x int) string {
return fmt.Sprintf("Value: %d", x)
})
// Result: Success("Value: 42")
Example - Computing derived values:
type User struct { FirstName, LastName string }
result := MonadMap(
Of(User{"John", "Doe"}),
func(u User) string { return u.FirstName + " " + u.LastName },
)
// Result: Success("John Doe")
type ValidationError ¶
type ValidationError struct {
Value any // The value that failed validation
Context Context // The path to the value in nested structures
Messsage string // Human-readable error message
Cause error // Optional underlying error cause
}
ValidationError represents a single validation failure with full context information. It implements the error interface and provides detailed information about what failed, where it failed, and why it failed.
Fields:
- Value: The actual value that failed validation
- Context: The path to the value in nested structures (e.g., user.address.zipCode)
- Messsage: Human-readable error description
- Cause: Optional underlying error that caused the validation failure
The ValidationError type implements:
- error interface: For standard Go error handling
- fmt.Formatter: For custom formatting with %v, %+v
- slog.LogValuer: For structured logging with slog
Example:
err := &ValidationError{
Value: "not-an-email",
Context: []ContextEntry{{Key: "user"}, {Key: "email"}},
Messsage: "invalid email format",
Cause: nil,
}
fmt.Printf("%v", err) // at user.email: invalid email format
fmt.Printf("%+v", err) // at user.email: invalid email format
// value: "not-an-email"
func (*ValidationError) Error ¶
func (v *ValidationError) Error() string
Error implements the error interface for ValidationError. Returns a generic error message.
func (*ValidationError) Format ¶
Format implements fmt.Formatter for custom formatting of ValidationError. It includes the context path, message, and optionally the cause error. Supports verbs: %s, %v, %+v (with additional details)
func (*ValidationError) LogValue ¶ added in v2.1.21
LogValue implements the slog.LogValuer interface for ValidationError. It provides structured logging representation of the validation error. Returns a slog.Value containing the error details as a group with message, value, context path, and optional cause.
This method is called automatically when logging a ValidationError with slog.
Example:
err := &ValidationError{Value: "abc", Messsage: "expected number"}
slog.Error("validation failed", "error", err)
// Logs: error={message="expected number" value="abc"}