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 ¶
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 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 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 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 is a function that validates input I to produce type A with full context tracking.
Type structure:
Validate[I, A] = Reader[I, Decode[Context, A]]
This means:
- Takes an input of type I
- Returns a Reader that depends on validation Context
- That Reader produces a Validation[A] (Either[Errors, A])
The layered structure enables:
- Access to the input value being validated
- Context tracking through nested structures
- Error accumulation with detailed paths
- Composition with other validators
Example usage:
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]
The Validate type forms:
- A Functor: Can map over successful results
- An Applicative: Can combine validators in parallel
- A Monad: Can chain dependent validations
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)