Documentation
¶
Overview ¶
Package validate provides functional validation primitives for building composable validators.
This package implements a validation framework based on functional programming principles, allowing you to build complex validators from simple, composable pieces. It uses the Reader monad pattern to thread validation context through nested structures.
Core Concepts ¶
The validate package is built around several key types:
- Validate[I, A]: A validator that transforms input I to output A with validation context
- Validation[A]: The result of validation, either errors or a valid value A
- Context: Tracks the path through nested structures for detailed error messages
Type Structure ¶
A Validate[I, A] is defined as:
Reader[I, Decode[A]]]
This means:
- It takes an input of type I
- Returns a Reader that depends on validation Context
- That Reader produces a Validation[A] (Either[Errors, A])
This layered structure allows validators to:
- Access the input value
- Track validation context (path in nested structures)
- Accumulate multiple validation errors
- Compose with other validators
Validation Context ¶
The Context type tracks the path through nested data structures during validation. Each ContextEntry contains:
- Key: The field name or map key
- Type: The expected type name
- Actual: The actual value being validated
This provides detailed error messages like "at user.address.zipCode: expected string, got number".
Monoid Operations ¶
The package provides ApplicativeMonoid for combining validators using monoid operations. This allows you to:
- Combine multiple validators that produce monoidal values
- Accumulate results from parallel validations
- Build complex validators from simpler ones
Example Usage ¶
Basic validation structure:
import (
"github.com/IBM/fp-go/v2/optics/codec/validate"
"github.com/IBM/fp-go/v2/optics/codec/validation"
)
// A validator that checks if a string is non-empty
func nonEmptyString(input string) validate.Reader[validation.Context, validation.Validation[string]] {
if input == "" {
return validation.FailureWithMessage[string](input, "string must not be empty")
}
return func(ctx validation.Context) validation.Validation[string] {
return validation.Success(input)
}
}
// Create a Validate function
var validateNonEmpty validate.Validate[string, string] = func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
return nonEmptyString(input)
}
Combining validators with monoids:
import (
"github.com/IBM/fp-go/v2/monoid"
"github.com/IBM/fp-go/v2/string"
)
// Combine string validators using string concatenation monoid
stringMonoid := string.Monoid
validatorMonoid := validate.ApplicativeMonoid[string, string](stringMonoid)
// Now you can combine validators that produce strings
combined := validatorMonoid.Concat(validator1, validator2)
Integration with Codec ¶
This package is designed to work with the optics/codec package for building type-safe encoders and decoders with validation. Validators can be composed into codecs that handle serialization, deserialization, and validation in a unified way.
Error Handling ¶
Validation errors are accumulated using the Either monad's applicative instance. This means:
- Multiple validation errors can be collected in a single pass
- Errors include full context path for debugging
- Errors can be formatted for logging or user display
See the validation package for error types and formatting options.
Index ¶
- type Context
- type Decode
- type Endomorphism
- type Errors
- type Kleisli
- type Monoid
- type Operator
- func Ap[B, I, A any](fa Validate[I, A]) Operator[I, func(A) B, B]
- func ApS[I, S1, S2, T any](setter func(T) func(S1) S2, fa Validate[I, T]) Operator[I, S1, S2]
- func ApSL[I, S, T any](lens L.Lens[S, T], fa Validate[I, T]) Operator[I, S, S]
- func Bind[I, S1, S2, A any](setter func(A) func(S1) S2, f Kleisli[I, S1, A]) Operator[I, S1, S2]
- func BindL[I, S, T any](lens L.Lens[S, T], f Kleisli[I, T, T]) Operator[I, S, S]
- func BindTo[I, S1, T any](setter func(T) S1) Operator[I, T, S1]
- func Chain[I, A, B any](f Kleisli[I, A, B]) Operator[I, A, B]
- func Let[I, S1, S2, B any](key func(B) func(S1) S2, f func(S1) B) Operator[I, S1, S2]
- func LetL[I, S, T any](lens L.Lens[S, T], f Endomorphism[T]) Operator[I, S, S]
- func LetTo[I, S1, S2, B any](key func(B) func(S1) S2, b B) Operator[I, S1, S2]
- func LetToL[I, S, T any](lens L.Lens[S, T], b T) Operator[I, S, S]
- func Map[I, A, B any](f func(A) B) Operator[I, A, B]
- type Reader
- type Validate
- type Validation
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Context ¶
type Context = validation.Context
Context provides contextual information for validation operations, tracking the path through nested data structures.
Context is a slice of ContextEntry values, where each entry represents a level in the nested structure being validated. This enables detailed error messages that show exactly where validation failed.
Example context path for nested validation:
Context{
{Key: "user", Type: "User"},
{Key: "address", Type: "Address"},
{Key: "zipCode", Type: "string"},
}
// Represents: user.address.zipCode
The context is used to generate error messages like:
"at user.address.zipCode: expected string, got number"
type Decode ¶
Decode represents a decoding operation that transforms input I into output A within a validation context.
Type structure:
Decode[I, A] = Reader[Context, Validation[A]]
This means:
- Takes a validation Context (path through nested structures)
- Returns a Validation[A] (Either[Errors, A])
Decode is used as the foundation for validation operations, providing:
- Context-aware error reporting with detailed paths
- Error accumulation across multiple validations
- Composable validation logic
The Decode type is typically not used directly but through the Validate type, which adds an additional Reader layer for accessing the input value.
Example:
decoder := func(ctx Context) Validation[int] {
// Perform validation and return result
return validation.Success(42)
}
// decoder is a Decode[any, int]
type Endomorphism ¶ added in v2.2.6
type Endomorphism[A any] = endomorphism.Endomorphism[A]
Endomorphism represents a function from a type to itself.
Type: Endomorphism[A] = func(A) A
An endomorphism is a morphism (structure-preserving map) where the source and target are the same type. In simpler terms, it's a function that takes a value of type A and returns a value of the same type A.
Endomorphisms are useful for:
- Transformations that preserve type (e.g., string normalization)
- Composable updates and modifications
- Building pipelines of same-type transformations
- Implementing the Monoid pattern (composition as binary operation)
Endomorphisms form a Monoid under function composition:
- Identity: func(a A) A { return a }
- Concat: func(f, g Endomorphism[A]) Endomorphism[A] { return func(a A) A { return f(g(a)) } }
Example:
trim := strings.TrimSpace // Endomorphism[string] lower := strings.ToLower // Endomorphism[string] normalize := compose(trim, lower) // Endomorphism[string]
type Errors ¶
type Errors = validation.Errors
Errors is a collection of validation errors that occurred during validation.
Each error in the collection contains:
- The value that failed validation
- The context path where the error occurred
- A human-readable error message
- An optional underlying cause error
Errors can be accumulated from multiple validation failures, allowing all problems to be reported at once rather than failing fast.
type Kleisli ¶
Kleisli represents a Kleisli arrow for the Validate monad.
A Kleisli arrow is a function from A to a monadic value Validate[I, B]. It's used for composing computations that produce monadic results.
Type: Kleisli[I, A, B] = func(A) Validate[I, B]
Kleisli arrows can be composed using the Chain function, enabling sequential validation where later validators depend on earlier results.
Example:
parseString := func(s string) Validate[string, int] {
// Parse string to int with validation
}
checkPositive := func(n int) Validate[string, int] {
// Validate that int is positive
}
// Both are Kleisli arrows that can be composed
type Monoid ¶
Monoid represents an algebraic structure with an associative binary operation and an identity element. Used for combining values of type A.
A Monoid[A] must satisfy:
- Associativity: Concat(Concat(a, b), c) == Concat(a, Concat(b, c))
- Identity: Concat(Empty(), a) == a == Concat(a, Empty())
Common examples:
- Numbers with addition (identity: 0)
- Numbers with multiplication (identity: 1)
- Strings with concatenation (identity: "")
- Lists with concatenation (identity: [])
func ApplicativeMonoid ¶
ApplicativeMonoid creates a Monoid instance for Validate[I, A] given a Monoid[A].
This function lifts a monoid operation on values of type A to work with validators that produce values of type A. It uses the applicative functor structure of the nested Reader types to combine validators while preserving their validation context.
The resulting monoid allows you to:
- Combine multiple validators that produce monoidal values
- Run validators in parallel and merge their results using the monoid operation
- Build complex validators compositionally from simpler ones
Type Parameters ¶
- I: The input type that validators accept
- A: The output type that validators produce (must have a Monoid instance)
Parameters ¶
- m: A Monoid[A] that defines how to combine values of type A
Returns ¶
A Monoid[Validate[I, A]] that can combine validators using the applicative structure.
How It Works ¶
The function composes three layers of applicative monoids:
- The innermost layer uses validation.ApplicativeMonoid(m) to combine Validation[A] values
- The middle layer wraps this in reader.ApplicativeMonoid for the Context dependency
- The outer layer wraps everything in reader.ApplicativeMonoid for the input I dependency
This creates a monoid that:
- Takes the same input I for both validators
- Threads the same Context through both validators
- Combines successful results using the monoid operation on A
- Accumulates validation errors from both validators if either fails
Example ¶
Combining string validators using string concatenation:
import (
"github.com/IBM/fp-go/v2/monoid"
"github.com/IBM/fp-go/v2/string"
"github.com/IBM/fp-go/v2/optics/codec/validate"
"github.com/IBM/fp-go/v2/optics/codec/validation"
)
// Create a monoid for string validators
stringMonoid := string.Monoid
validatorMonoid := validate.ApplicativeMonoid[string, string](stringMonoid)
// Define two validators that extract different parts
validator1 := func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
return func(ctx validation.Context) validation.Validation[string] {
return validation.Success("Hello ")
}
}
validator2 := func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
return func(ctx validation.Context) validation.Validation[string] {
return validation.Success("World")
}
}
// Combine them - results will be concatenated
combined := validatorMonoid.Concat(validator1, validator2)
// When run, produces validation.Success("Hello World")
Combining numeric validators using addition:
import (
"github.com/IBM/fp-go/v2/number"
)
// Create a monoid for int validators using addition
intMonoid := number.MonoidSum[int]()
validatorMonoid := validate.ApplicativeMonoid[string, int](intMonoid)
// Validators that extract and validate different numeric fields
// Results will be summed together
Notes ¶
- Both validators receive the same input value I
- If either validator fails, all errors are accumulated
- If both succeed, their results are combined using the monoid operation
- The empty element of the monoid serves as the identity for the Concat operation
- This follows the applicative functor laws for combining effectful computations
See Also ¶
- validation.ApplicativeMonoid: The underlying monoid for validation results
- reader.ApplicativeMonoid: The monoid for reader computations
- Monoid[A]: The monoid instance for the result type
type Operator ¶
Operator represents a transformation operator for validators.
An Operator transforms a Validate[I, A] into a Validate[I, B]. It's a specialized Kleisli arrow where the input is itself a validator.
Type: Operator[I, A, B] = func(Validate[I, A]) Validate[I, B]
Operators are used to:
- Transform validation results (Map)
- Chain dependent validations (Chain)
- Apply function validators to value validators (Ap)
Example:
toUpper := Map[string, string, string](strings.ToUpper) // toUpper is an Operator[string, string, string] // It can be applied to any string validator to uppercase the result
func Ap ¶
func Ap[B, I, A any](fa Validate[I, A]) Operator[I, func(A) B, B]
Ap creates an operator that applies a function validator to a value validator.
This is the curried version of MonadAp, returning a function that can be applied to function validators. It's useful for creating reusable applicative patterns.
Type Parameters ¶
- B: The result type after applying the function
- I: The input type
- A: The type of the value to which the function is applied
Parameters ¶
- fa: A validator that produces a value of type A
Returns ¶
An Operator[I, func(A) B, B] that applies function validators to the value validator.
Example ¶
// Create a value validator
validateValue := validate.Of[string, int](21)
// Create an applicative operator
applyTo21 := validate.Ap[int, string, int](validateValue)
// Create a function validator
validateDouble := validate.Of[string, func(int) int](func(x int) int { return x * 2 })
// Apply it
result := applyTo21(validateDouble)
// When run, produces validation.Success(42)
Notes ¶
- This is the point-free style version of MonadAp
- Useful for building applicative pipelines
- Enables parallel validation with error accumulation
- Can be composed with other applicative operators
func ApS ¶ added in v2.2.6
func ApS[I, S1, S2, T any]( setter func(T) func(S1) S2, fa Validate[I, T], ) Operator[I, 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 }
decoder := F.Pipe2(
Do[string](State{}),
ApS(func(x int) func(State) State {
return func(s State) State { s.x = x; return s }
}, Of[string](42)),
)
result := decoder("input") // Returns validation.Success(State{x: 42})
Error aggregation example:
// Both decoders fail - errors are aggregated
decoder1 := func(input string) Validation[State] {
return validation.Failures[State](/* errors */)
}
decoder2 := func(input string) Validation[int] {
return validation.Failures[int](/* errors */)
}
combined := ApS(setter, decoder2)(decoder1)
result := combined("input") // Contains BOTH sets of errors
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
decoder := F.Pipe2(
Of[string](Person{Name: "Alice"}),
ApSL(
addressLens,
Of[string](Address{Street: "Main St", City: "NYC"}),
),
)
result := decoder("input") // Returns validation.Success(Person{...})
func Bind ¶ added in v2.2.6
func Bind[I, S1, S2, A any]( setter func(A) func(S1) S2, f Kleisli[I, S1, A], ) Operator[I, 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 }
decoder := F.Pipe2(
Do[string](State{}),
Bind(func(x int) func(State) State {
return func(s State) State { s.x = x; return s }
}, func(s State) Validate[string, int] {
return Of[string](42)
}),
)
result := decoder("input") // Returns validation.Success(State{x: 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) Validate[string, int] {
return func(input string) Validation[int] {
if v >= 100 {
return validation.Failures[int](/* errors */)
}
return validation.Success(v + 1)
}
}
decoder := F.Pipe1(
Of[string](Counter{Value: 42}),
BindL(valueLens, increment),
)
result := decoder("input") // Returns validation.Success(Counter{Value: 43})
func BindTo ¶ added in v2.2.6
func BindTo[I, S1, T any]( setter func(T) S1, ) Operator[I, T, S1]
BindTo initializes a new state S1 from a value T. This is typically used as the first operation after creating a Validate value.
Example:
type State struct { value int }
decoder := F.Pipe1(
Of[string](42),
BindTo[string](func(x int) State { return State{value: x} }),
)
result := decoder("input") // Returns validation.Success(State{value: 42})
func Chain ¶
func Chain[I, A, B any](f Kleisli[I, A, B]) Operator[I, A, B]
Chain sequences two validators, where the second depends on the result of the first.
This is the monadic bind operation for Validate. It allows you to create validators that depend on the results of previous validations, enabling complex validation logic that builds on earlier results.
Type Parameters ¶
- I: The input type
- A: The type of the first validation result
- B: The type of the second validation result
Parameters ¶
- f: A Kleisli arrow that takes a value of type A and returns a Validate[I, B]
Returns ¶
An Operator[I, A, B] that sequences the validations.
Example ¶
// First validate that a string is non-empty, then validate its length
validateNonEmpty := func(s string) validate.Reader[validation.Context, validation.Validation[string]] {
return func(ctx validation.Context) validation.Validation[string] {
if s == "" {
return validation.FailureWithMessage[string](s, "must not be empty")(ctx)
}
return validation.Success(s)
}
}
validateLength := func(s string) validate.Validate[string, int] {
return func(input string) validate.Reader[validation.Context, validation.Validation[int]] {
return func(ctx validation.Context) validation.Validation[int] {
if len(s) < 3 {
return validation.FailureWithMessage[int](len(s), "too short")(ctx)
}
return validation.Success(len(s))
}
}
}
// Chain them together
chained := validate.Chain(validateLength)(validateNonEmpty)
Notes ¶
- If the first validation fails, the second is not executed
- Errors from the first validation are preserved
- This enables dependent validation logic
- Satisfies the monad laws: associativity and identity
func Let ¶ added in v2.2.6
func Let[I, S1, S2, B any]( key func(B) func(S1) S2, f func(S1) B, ) Operator[I, 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 wrapped in Validate.
Example:
type State struct { x int; computed int }
decoder := F.Pipe2(
Do[string](State{x: 5}),
Let[string](func(c int) func(State) State {
return func(s State) State { s.computed = c; return s }
}, func(s State) int { return s.x * 2 }),
)
result := decoder("input") // Returns validation.Success(State{x: 5, computed: 10})
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 }
decoder := F.Pipe1(
Of[string](Counter{Value: 21}),
LetL(valueLens, double),
)
result := decoder("input") // Returns validation.Success(Counter{Value: 42})
func LetTo ¶ added in v2.2.6
func LetTo[I, S1, S2, B any]( key func(B) func(S1) S2, b B, ) Operator[I, 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 },
)
decoder := F.Pipe1(
Of[string](Config{Debug: true, Timeout: 30}),
LetToL(debugLens, false),
)
result := decoder("input") // Returns validation.Success(Config{Debug: false, Timeout: 30})
func Map ¶
func Map[I, A, B any](f func(A) B) Operator[I, A, B]
Map creates an operator that transforms validation results.
This is the curried version of MonadMap, returning a function that can be applied to validators. It's useful for creating reusable transformation pipelines.
Type Parameters ¶
- I: The input type
- A: The type of the current validation result
- B: The type after applying the transformation
Parameters ¶
- f: The transformation function to apply to successful results
Returns ¶
An Operator[I, A, B] that transforms Validate[I, A] to Validate[I, B].
Example ¶
// Create a reusable transformation toUpper := validate.Map[string, string, string](strings.ToUpper) // Apply it to different validators validator1 := toUpper(someStringValidator) validator2 := toUpper(anotherStringValidator)
Notes ¶
- This is the point-free style version of MonadMap
- Useful for building transformation pipelines
- Can be composed with other operators
type Reader ¶
Reader represents a computation that depends on an environment R and produces a value A.
Reader[R, A] is a function type: func(R) A
The Reader pattern is used to:
- Thread configuration or context through computations
- Implement dependency injection in a functional way
- Defer computation until the environment is available
- Compose computations that share the same environment
Example:
type Config struct { Port int }
getPort := func(cfg Config) int { return cfg.Port }
// getPort is a Reader[Config, int]
type Validate ¶
Validate represents a composable validator that transforms input I to output A with comprehensive error tracking and context propagation.
Type Structure ¶
Validate[I, A] = Reader[I, Decode[Context, A]]
= Reader[I, Reader[Context, Validation[A]]]
= func(I) func(Context) Either[Errors, A]
This three-layer structure provides:
- Input access: The outer Reader[I, ...] gives access to the input value I
- Context tracking: The middle Reader[Context, ...] tracks the validation path
- Error handling: The inner Validation[A] accumulates errors or produces value A
Purpose ¶
Validate is the core type for building type-safe, composable validators that:
- Transform and validate data from one type to another
- Track the path through nested structures for detailed error messages
- Accumulate multiple validation errors instead of failing fast
- Compose with other validators using functional patterns
Key Features ¶
- Context-aware: Automatically tracks validation path (e.g., "user.address.zipCode")
- Error accumulation: Collects all validation errors, not just the first one
- Type-safe: Leverages Go's type system to ensure correctness
- Composable: Validators can be combined using Map, Chain, Ap, and other operators
Algebraic Structure ¶
Validate forms several algebraic structures:
- Functor: Transform successful results with Map
- Applicative: Combine independent validators in parallel with Ap
- Monad: Chain dependent validators sequentially with Chain
Example Usage ¶
Basic validator:
validatePositive := func(n int) Reader[Context, Validation[int]] {
return func(ctx Context) Validation[int] {
if n > 0 {
return validation.Success(n)
}
return validation.FailureWithMessage[int](n, "must be positive")(ctx)
}
}
// validatePositive is a Validate[int, int]
Composing validators:
// Transform the result of a validator
doubled := Map[int, int, int](func(x int) int { return x * 2 })(validatePositive)
// Chain dependent validations
validateRange := func(n int) Validate[int, int] {
return func(input int) Reader[Context, Validation[int]] {
return func(ctx Context) Validation[int] {
if n <= 100 {
return validation.Success(n)
}
return validation.FailureWithMessage[int](n, "must be <= 100")(ctx)
}
}
}
combined := Chain(validateRange)(validatePositive)
Integration ¶
Validate integrates with the broader optics/codec ecosystem:
- Works with Decode for decoding operations
- Uses Validation for error handling
- Leverages Context for detailed error reporting
- Composes with other codec types for complete encode/decode pipelines
See the package documentation for more examples and patterns.
func Do ¶ added in v2.2.6
func Do[I, S any]( empty S, ) Validate[I, 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 MonadAp ¶
func MonadAp[B, I, A any](fab Validate[I, func(A) B], fa Validate[I, A]) Validate[I, B]
MonadAp applies a validator containing a function to a validator containing a value.
This is the applicative apply operation for Validate. It allows you to apply functions wrapped in validation context to values wrapped in validation context, accumulating errors from both if either fails.
Type Parameters ¶
- B: The result type after applying the function
- I: The input type
- A: The type of the value to which the function is applied
Parameters ¶
- fab: A validator that produces a function from A to B
- fa: A validator that produces a value of type A
Returns ¶
A Validate[I, B] that applies the function to the value if both validations succeed.
Example ¶
// Create a validator that produces a function
validateFunc := validate.Of[string, func(int) int](func(x int) int { return x * 2 })
// Create a validator that produces a value
validateValue := validate.Of[string, int](21)
// Apply them
result := validate.MonadAp(validateFunc, validateValue)
// When run, produces validation.Success(42)
Notes ¶
- Both validators receive the same input
- If either validation fails, all errors are accumulated
- If both succeed, the function is applied to the value
- This enables parallel validation with error accumulation
- Satisfies the applicative functor laws
func MonadMap ¶
func MonadMap[I, A, B any](fa Validate[I, A], f func(A) B) Validate[I, B]
MonadMap applies a function to the successful result of a validation.
This is the functor map operation for Validate. It transforms the success value without affecting the validation logic or error handling. If the validation fails, the function is not applied and errors are preserved.
Type Parameters ¶
- I: The input type
- A: The type of the current validation result
- B: The type after applying the transformation
Parameters ¶
- fa: The validator to transform
- f: The transformation function to apply to successful results
Returns ¶
A new Validate[I, B] that applies f to the result if validation succeeds.
Example ¶
// Transform a string validator to uppercase
validateString := func(s string) validate.Reader[validation.Context, validation.Validation[string]] {
return func(ctx validation.Context) validation.Validation[string] {
return validation.Success(s)
}
}
upperValidator := validate.MonadMap(validateString, strings.ToUpper)
result := upperValidator("hello")(nil)
// result is validation.Success("HELLO")
Notes ¶
- Preserves validation errors unchanged
- Only applies the function to successful validations
- Satisfies the functor laws: composition and identity
func Of ¶
func Of[I, A any](a A) Validate[I, A]
Of creates a Validate that always succeeds with the given value.
This is the "pure" or "return" operation for the Validate monad. It lifts a plain value into the validation context without performing any actual validation.
Type Parameters ¶
- I: The input type (not used, but required for type consistency)
- A: The type of the value to wrap
Parameters ¶
- a: The value to wrap in a successful validation
Returns ¶
A Validate[I, A] that ignores its input and always returns a successful validation containing the value a.
Example ¶
// Create a validator that always succeeds with value 42
alwaysValid := validate.Of[string, int](42)
result := alwaysValid("any input")(nil)
// result is validation.Success(42)
Notes ¶
- This is useful for lifting pure values into the validation context
- The input type I is ignored; the validator succeeds regardless of input
- This satisfies the monad laws: Of is the left and right identity for Chain
type Validation ¶
type Validation[A any] = validation.Validation[A]
Validation represents the result of a validation operation that may contain validation errors or a successfully validated value of type A.
Validation[A] is an Either[Errors, A], where:
- Left(errors): Validation failed with one or more errors
- Right(value): Validation succeeded with value of type A
The Validation type supports:
- Error accumulation: Multiple validation errors can be collected
- Applicative composition: Parallel validations with error aggregation
- Monadic composition: Sequential validations with short-circuiting
Example:
success := validation.Success(42) // Right(42) failure := validation.Failure[int](errors) // Left(errors)