validate

package
v2.2.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 5, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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:

  1. It takes an input of type I
  2. Returns a Reader that depends on validation Context
  3. 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 Decode

type Decode[I, A any] = decode.Decode[I, A]

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:

  1. Takes a validation Context (path through nested structures)
  2. 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

type Kleisli[I, A, B any] = Reader[A, Validate[I, B]]

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 Lazy added in v2.2.10

type Lazy[A any] = lazy.Lazy[A]

type Monoid

type Monoid[A any] = monoid.Monoid[A]

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 AltMonoid added in v2.2.11

func AltMonoid[I, A any](zero Lazy[Validate[I, A]]) Monoid[Validate[I, A]]

AltMonoid creates a Monoid instance for Validate[I, A] using alternative semantics with a provided zero/default validator.

This function creates a monoid where:

  1. The first successful validator wins (no result combination)
  2. If the first fails, the second is tried as a fallback
  3. If both fail, errors are aggregated
  4. The provided zero validator serves as the identity element

Unlike AlternativeMonoid, AltMonoid does NOT combine successful results - it always returns the first success. This makes it ideal for fallback chains and default values.

Type Parameters

  • I: The input type that validators accept
  • A: The output type that validators produce

Parameters

  • zero: A lazy Validate[I, A] that serves as the identity element. This is typically a validator that always succeeds with a default value, but can also be a failing validator if no default is appropriate.

Returns

A Monoid[Validate[I, A]] that combines validators using alternative semantics where the first success wins.

Behavior Details

The AltMonoid implements a "first success wins" strategy:

  • **First succeeds**: Returns the first result, second is never evaluated
  • **First fails, second succeeds**: Returns the second result
  • **Both fail**: Aggregates errors from both validators
  • **Concat with Empty**: The zero validator is used as fallback

Example: Default Value Fallback

import (
    "github.com/IBM/fp-go/v2/optics/codec/validate"
)

// Create a monoid with a default value of 0
m := validate.AltMonoid(func() validate.Validate[string, int] {
    return validate.Of[string, int](0)
})

// First validator succeeds - returns 42, second is not evaluated
validator1 := validate.Of[string, int](42)
validator2 := validate.Of[string, int](100)
combined := m.Concat(validator1, validator2)
result := combined("input")(nil)
// result is validation.Success(42)

Example: Fallback Chain

// Try primary, then fallback, then default
m := validate.AltMonoid(func() validate.Validate[string, string] {
    return validate.Of[string, string]("default")
})

primary := func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        return validation.FailureWithMessage[string](input, "primary failed")(ctx)
    }
}
secondary := func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        return validation.Success("secondary value")
    }
}

// Chain: try primary, then secondary, then default
combined := m.Concat(m.Concat(primary, secondary), m.Empty())
result := combined("input")(nil)
// result is validation.Success("secondary value")

Example: Error Aggregation

// Both fail - errors are aggregated
m := validate.AltMonoid(func() validate.Validate[string, int] {
    return func(input string) validate.Reader[validation.Context, validation.Validation[int]] {
        return func(ctx validation.Context) validation.Validation[int] {
            return validation.FailureWithMessage[int](input, "no default")(ctx)
        }
    }
})

failing1 := func(input string) validate.Reader[validation.Context, validation.Validation[int]] {
    return func(ctx validation.Context) validation.Validation[int] {
        return validation.FailureWithMessage[int](input, "error 1")(ctx)
    }
}
failing2 := func(input string) validate.Reader[validation.Context, validation.Validation[int]] {
    return func(ctx validation.Context) validation.Validation[int] {
        return validation.FailureWithMessage[int](input, "error 2")(ctx)
    }
}

combined := m.Concat(failing1, failing2)
result := combined("input")(nil)
// result contains both "error 1" and "error 2"

Comparison with Other Monoids

  • **ApplicativeMonoid**: Combines results when both succeed using monoid operation
  • **AlternativeMonoid**: Combines results when both succeed, provides fallback when one fails
  • **AltMonoid**: First success wins, never combines results (pure alternative)

Use Cases

  • Configuration loading with fallback sources (try file, then env, then default)
  • Validation with default values
  • Parser combinators with alternative branches
  • Error recovery with multiple strategies

Notes

  • The zero validator is lazily evaluated, only when needed
  • First success short-circuits evaluation (second validator not called)
  • Error aggregation ensures all validation failures are reported
  • This follows the alternative functor laws

See Also

  • AlternativeMonoid: For combining results when both succeed
  • ApplicativeMonoid: For pure applicative combination
  • MonadAlt: The underlying alternative operation
  • Alt: The curried version for pipeline composition

func AlternativeMonoid added in v2.2.11

func AlternativeMonoid[I, A any](m Monoid[A]) Monoid[Validate[I, A]]

AlternativeMonoid creates a Monoid instance for Validate[I, A] that combines both applicative and alternative semantics.

This function creates a monoid that:

  1. When both validators succeed: Combines their results using the provided monoid operation
  2. When one validator fails: Uses the successful validator's result (alternative behavior)
  3. When both validators fail: Aggregates all errors from both validators

This is a hybrid approach that combines:

  • ApplicativeMonoid: Combines successful results using the monoid operation
  • AltMonoid: Provides fallback behavior when validators fail

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 combines validators using both applicative and alternative semantics.

Behavior Details

The AlternativeMonoid differs from ApplicativeMonoid in how it handles mixed success/failure:

  • **Both succeed**: Results are combined using the monoid operation (like ApplicativeMonoid)
  • **First succeeds, second fails**: Returns the first result (alternative fallback)
  • **First fails, second succeeds**: Returns the second result (alternative fallback)
  • **Both fail**: Aggregates errors from both validators

Example: String Concatenation with Fallback

import (
    "github.com/IBM/fp-go/v2/optics/codec/validate"
    "github.com/IBM/fp-go/v2/optics/codec/validation"
    S "github.com/IBM/fp-go/v2/string"
)

m := validate.AlternativeMonoid[string, string](S.Monoid)

// Both succeed - results are concatenated
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")
    }
}
combined := m.Concat(validator1, validator2)
result := combined("input")(nil)
// result is validation.Success("Hello World")

Example: Fallback Behavior

// First fails, second succeeds - uses second result
failing := func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        return validation.FailureWithMessage[string](input, "first failed")(ctx)
    }
}
succeeding := func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        return validation.Success("fallback")
    }
}
combined := m.Concat(failing, succeeding)
result := combined("input")(nil)
// result is validation.Success("fallback")

Example: Error Aggregation

// Both fail - errors are aggregated
failing1 := func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        return validation.FailureWithMessage[string](input, "error 1")(ctx)
    }
}
failing2 := func(input string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        return validation.FailureWithMessage[string](input, "error 2")(ctx)
    }
}
combined := m.Concat(failing1, failing2)
result := combined("input")(nil)
// result contains both "error 1" and "error 2"

Comparison with Other Monoids

  • **ApplicativeMonoid**: Always combines results when both succeed, fails if either fails
  • **AlternativeMonoid**: Combines results when both succeed, provides fallback when one fails
  • **AltMonoid**: Always uses first success, never combines results

Use Cases

  • Validation with fallback strategies and result combination
  • Building validators that accumulate results but provide alternatives
  • Configuration loading with multiple sources and merging
  • Data aggregation with error recovery

Notes

  • Both validators receive the same input value I
  • The empty element of the monoid serves as the identity for the Concat operation
  • Error aggregation ensures no validation failures are lost
  • This follows both applicative and alternative functor laws

See Also

  • ApplicativeMonoid: For pure applicative combination without fallback
  • AltMonoid: For pure alternative behavior without result combination
  • MonadAlt: The underlying alternative operation

func ApplicativeMonoid

func ApplicativeMonoid[I, A any](m Monoid[A]) Monoid[Validate[I, A]]

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:

  1. The innermost layer uses validation.ApplicativeMonoid(m) to combine Validation[A] values
  2. The middle layer wraps this in reader.ApplicativeMonoid for the Context dependency
  3. 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

type Operator[I, A, B any] = Kleisli[I, Validate[I, A], B]

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 Alt added in v2.2.11

func Alt[I, A any](second Lazy[Validate[I, A]]) Operator[I, A, A]

Alt provides an alternative validator when the primary validator fails.

This is the curried, point-free version of MonadAlt. It creates an operator that transforms a validator by adding a fallback alternative. When the first validator fails, the second (lazily evaluated) validator is tried. If both fail, errors are aggregated.

Alt implements the Alternative typeclass pattern, providing a way to express "try this, or else try that" logic in a composable way.

Type Parameters

  • I: The input type
  • A: The type of the validation result

Parameters

  • second: A lazy Validate[I, A] that serves as the fallback. It's only evaluated if the first validator fails.

Returns

An Operator[I, A, A] that transforms validators by adding alternative fallback logic.

Behavior

  • **First succeeds**: Returns the first result, second is never evaluated
  • **First fails, second succeeds**: Returns the second result
  • **Both fail**: Aggregates errors from both validators

Example: Fallback Validation

import (
    F "github.com/IBM/fp-go/v2/function"
    "github.com/IBM/fp-go/v2/optics/codec/validate"
    "github.com/IBM/fp-go/v2/optics/codec/validation"
)

// Primary validator that may fail
validateFromConfig := func(key string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        // Try to get value from config
        if value, ok := config[key]; ok {
            return validation.Success(value)
        }
        return validation.FailureWithMessage[string](key, "not in config")(ctx)
    }
}

// Fallback to environment variable
validateFromEnv := func(key string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        if value := os.Getenv(key); value != "" {
            return validation.Success(value)
        }
        return validation.FailureWithMessage[string](key, "not in env")(ctx)
    }
}

// Use Alt to add fallback - point-free style
withFallback := validate.Alt(func() validate.Validate[string, string] {
    return validateFromEnv
})

validator := withFallback(validateFromConfig)
result := validator("DATABASE_URL")(nil)
// Tries config first, falls back to environment variable

Example: Pipeline with Multiple Alternatives

// Chain multiple alternatives using function composition
validator := F.Pipe2(
    validateFromDatabase,
    validate.Alt(func() validate.Validate[string, Config] {
        return validateFromCache
    }),
    validate.Alt(func() validate.Validate[string, Config] {
        return validate.Of[string](defaultConfig)
    }),
)
// Tries database, then cache, then default

Notes

  • The second validator is lazily evaluated for efficiency
  • First success short-circuits evaluation
  • Errors are aggregated when both fail
  • This is the point-free version of MonadAlt
  • Useful for building validation pipelines with F.Pipe

See Also

  • MonadAlt: The direct application version
  • ChainLeft: The more general error transformation operator
  • OrElse: Semantic alias for ChainLeft
  • AltMonoid: For combining multiple alternatives with monoid structure

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

func ApSL[I, S, T any](
	lens L.Lens[S, T],
	fa Validate[I, T],
) Operator[I, S, S]

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

func BindL[I, S, T any](
	lens L.Lens[S, T],
	f Kleisli[I, T, T],
) Operator[I, S, S]

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 ChainLeft added in v2.2.9

func ChainLeft[I, A any](f Kleisli[I, Errors, A]) Operator[I, A, A]

ChainLeft sequences a computation on the failure (Left) channel of a validation.

This function operates on the error path of validation, allowing you to transform, enrich, or recover from validation failures. It's the dual of Chain - while Chain operates on success values, ChainLeft operates on error values.

Key Behavior

**Critical difference from standard Either operations**: 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.

  1. **Success Pass-Through**: If validation succeeds, the handler is never called and the success value passes through unchanged.

  2. **Error Recovery**: The handler can recover from failures by returning a successful validation, converting Left to Right.

  3. **Error Aggregation**: When the handler also returns a failure, both the original errors and the new errors are combined using the Errors monoid.

  4. **Input Access**: The handler returns a Validate[I, A] function, giving it access to the original input value I for context-aware error handling.

Type Parameters

  • I: The input type
  • A: The type of the validation result

Parameters

  • f: A Kleisli arrow that takes Errors and returns a Validate[I, A]. This function is called only when validation fails, receiving the accumulated errors.

Returns

An Operator[I, A, A] that transforms validators by handling their error cases.

Example: Error Recovery

// Validator that may fail
validatePositive := func(n int) Reader[validation.Context, validation.Validation[int]] {
    return func(ctx validation.Context) validation.Validation[int] {
        if n > 0 {
            return validation.Success(n)
        }
        return validation.FailureWithMessage[int](n, "must be positive")(ctx)
    }
}

// Recover from specific errors with a default value
withDefault := ChainLeft(func(errs Errors) Validate[int, int] {
    for _, err := range errs {
        if err.Messsage == "must be positive" {
            return Of[int](0) // recover with default
        }
    }
    return func(input int) Reader[validation.Context, validation.Validation[int]] {
        return func(ctx validation.Context) validation.Validation[int] {
            return either.Left[int](errs)
        }
    }
})

validator := withDefault(validatePositive)
result := validator(-5)(nil)
// Result: Success(0) - recovered from failure

Example: Error Context Addition

// Add contextual information to errors
addContext := ChainLeft(func(errs Errors) Validate[string, int] {
    return func(input string) Reader[validation.Context, validation.Validation[int]] {
        return func(ctx validation.Context) validation.Validation[int] {
            return either.Left[int](validation.Errors{
                {
                    Context:  validation.Context{{Key: "user", Type: "User"}, {Key: "age", Type: "int"}},
                    Messsage: "failed to validate user age",
                },
            })
        }
    }
})

validator := addContext(someValidator)
// Errors will include both original error and context

Example: Input-Dependent Recovery

// Recover with different defaults based on input
smartDefault := ChainLeft(func(errs Errors) Validate[string, int] {
    return func(input string) Reader[validation.Context, validation.Validation[int]] {
        return func(ctx validation.Context) validation.Validation[int] {
            // Use input to determine appropriate default
            if strings.Contains(input, "http") {
                return validation.Of(80)
            }
            if strings.Contains(input, "https") {
                return validation.Of(443)
            }
            return validation.Of(8080)
        }
    }
})

Notes

  • Errors are accumulated, not replaced - this ensures no validation failures are lost
  • The handler has access to both the errors and the original input
  • Success values bypass the handler completely
  • This enables sophisticated error handling strategies including recovery, enrichment, and transformation
  • Use OrElse as a semantic alias when emphasizing fallback/alternative logic

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

func LetL[I, S, T any](
	lens L.Lens[S, T],
	f Endomorphism[T],
) Operator[I, S, S]

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

func LetToL[I, S, T any](
	lens L.Lens[S, T],
	b T,
) Operator[I, S, S]

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

func OrElse added in v2.2.9

func OrElse[I, A any](f Kleisli[I, Errors, A]) Operator[I, A, A]

OrElse provides an alternative validation when the primary validation fails.

This is a semantic alias for ChainLeft with identical behavior. The name "OrElse" emphasizes the intent of providing fallback or alternative validation logic, making code more readable when that's the primary use case.

Relationship to ChainLeft

**OrElse and ChainLeft are functionally identical** - they produce exactly the same results for all inputs. The choice between them is purely about code readability:

  • Use **OrElse** when emphasizing fallback/alternative validation logic
  • Use **ChainLeft** when emphasizing technical error channel transformation

Both maintain the critical property of **error aggregation**, ensuring all validation failures are preserved and reported together.

Type Parameters

  • I: The input type
  • A: The type of the validation result

Parameters

  • f: A Kleisli arrow that takes Errors and returns a Validate[I, A]. This function is called only when validation fails, receiving the accumulated errors.

Returns

An Operator[I, A, A] that transforms validators by providing alternative validation.

Example: Fallback Validation

// Primary validator that may fail
validateFromConfig := func(key string) Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        // Try to get value from config
        if value, ok := config[key]; ok {
            return validation.Success(value)
        }
        return validation.FailureWithMessage[string](key, "not found in config")(ctx)
    }
}

// Use OrElse for semantic clarity - "try config, or else use environment"
withEnvFallback := OrElse(func(errs Errors) Validate[string, string] {
    return func(key string) Reader[validation.Context, validation.Validation[string]] {
        return func(ctx validation.Context) validation.Validation[string] {
            if value := os.Getenv(key); value != "" {
                return validation.Success(value)
            }
            return either.Left[string](errs) // propagate original errors
        }
    }
})

validator := withEnvFallback(validateFromConfig)
result := validator("DATABASE_URL")(nil)
// Tries config first, falls back to environment variable

Example: Default Value on Failure

// Provide a default value when validation fails
withDefault := OrElse(func(errs Errors) Validate[int, int] {
    return Of[int](0) // default to 0 on any failure
})

validator := withDefault(someValidator)
result := validator(input)(nil)
// Always succeeds, using default value if validation fails

Example: Pipeline with Multiple Fallbacks

// Build a validation pipeline with multiple fallback strategies
validator := F.Pipe2(
    validateFromDatabase,
    OrElse(func(errs Errors) Validate[string, Config] {
        // Try cache as first fallback
        return validateFromCache
    }),
    OrElse(func(errs Errors) Validate[string, Config] {
        // Use default config as final fallback
        return Of[string](defaultConfig)
    }),
)
// Tries database, then cache, then default

Notes

  • Identical behavior to ChainLeft - they are aliases
  • Errors are accumulated when transformations fail
  • Success values pass through unchanged
  • The handler has access to both errors and original input
  • Choose OrElse for better readability when providing alternatives
  • See ChainLeft documentation for detailed behavior and additional examples

type Reader

type Reader[R, A any] = reader.Reader[R, A]

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

type Validate[I, A any] = Reader[I, Decode[Context, A]]

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:

  1. Input access: The outer Reader[I, ...] gives access to the input value I
  2. Context tracking: The middle Reader[Context, ...] tracks the validation path
  3. 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 MonadAlt added in v2.2.11

func MonadAlt[I, A any](first Validate[I, A], second Lazy[Validate[I, A]]) Validate[I, A]

MonadAlt provides an alternative validator when the primary validator fails.

This is the direct application version of Alt. It takes two validators and returns a new validator that tries the first, and if it fails, tries the second. If both fail, errors from both are aggregated.

MonadAlt implements the Alternative typeclass pattern, enabling "try this, or else try that" logic with comprehensive error reporting.

Type Parameters

  • I: The input type
  • A: The type of the validation result

Parameters

  • first: The primary Validate[I, A] to try first
  • second: A lazy Validate[I, A] that serves as the fallback. It's only evaluated if the first validator fails.

Returns

A Validate[I, A] that tries the first validator, falling back to the second if needed.

Behavior

  • **First succeeds**: Returns the first result, second is never evaluated
  • **First fails, second succeeds**: Returns the second result
  • **Both fail**: Aggregates errors from both validators

Example: Configuration with Fallback

import (
    "github.com/IBM/fp-go/v2/optics/codec/validate"
    "github.com/IBM/fp-go/v2/optics/codec/validation"
)

// Primary validator
validateFromConfig := func(key string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        if value, ok := config[key]; ok {
            return validation.Success(value)
        }
        return validation.FailureWithMessage[string](key, "not in config")(ctx)
    }
}

// Fallback validator
validateFromEnv := func(key string) validate.Reader[validation.Context, validation.Validation[string]] {
    return func(ctx validation.Context) validation.Validation[string] {
        if value := os.Getenv(key); value != "" {
            return validation.Success(value)
        }
        return validation.FailureWithMessage[string](key, "not in env")(ctx)
    }
}

// Combine with MonadAlt
validator := validate.MonadAlt(
    validateFromConfig,
    func() validate.Validate[string, string] { return validateFromEnv },
)
result := validator("DATABASE_URL")(nil)
// Tries config first, falls back to environment variable

Example: Multiple Fallbacks

// Chain multiple alternatives
validator := validate.MonadAlt(
    validate.MonadAlt(
        validateFromDatabase,
        func() validate.Validate[string, Config] { return validateFromCache },
    ),
    func() validate.Validate[string, Config] { return validate.Of[string](defaultConfig) },
)
// Tries database, then cache, then default

Example: Error Aggregation

failing1 := func(input string) validate.Reader[validation.Context, validation.Validation[int]] {
    return func(ctx validation.Context) validation.Validation[int] {
        return validation.FailureWithMessage[int](input, "error 1")(ctx)
    }
}
failing2 := func(input string) validate.Reader[validation.Context, validation.Validation[int]] {
    return func(ctx validation.Context) validation.Validation[int] {
        return validation.FailureWithMessage[int](input, "error 2")(ctx)
    }
}

validator := validate.MonadAlt(
    failing1,
    func() validate.Validate[string, int] { return failing2 },
)
result := validator("input")(nil)
// result contains both "error 1" and "error 2"

Notes

  • The second validator is lazily evaluated for efficiency
  • First success short-circuits evaluation (second not called)
  • Errors are aggregated when both fail
  • This is equivalent to Alt but with direct application
  • Both validators receive the same input value

See Also

  • Alt: The curried, point-free version
  • MonadChainLeft: The underlying error transformation operation
  • OrElse: Semantic alias for ChainLeft
  • AltMonoid: For combining multiple alternatives with monoid structure

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 MonadChainLeft added in v2.2.11

func MonadChainLeft[I, A any](fa Validate[I, A], f Kleisli[I, Errors, A]) Validate[I, A]

MonadChainLeft sequences a computation on the failure (Left) channel of a validation.

This is the direct application version of ChainLeft. It operates on the error path of validation, allowing you to transform, enrich, or recover from validation failures. It's the dual of Chain - while Chain operates on success values, MonadChainLeft operates on error values.

Key Behavior

**Critical difference from standard Either operations**: 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.

  1. **Success Pass-Through**: If validation succeeds, the handler is never called and the success value passes through unchanged.

  2. **Error Recovery**: The handler can recover from failures by returning a successful validation, converting Left to Right.

  3. **Error Aggregation**: When the handler also returns a failure, both the original errors and the new errors are combined using the Errors monoid.

  4. **Input Access**: The handler returns a Validate[I, A] function, giving it access to the original input value I for context-aware error handling.

Type Parameters

  • I: The input type
  • A: The type of the validation result

Parameters

  • fa: The Validate[I, A] to transform
  • f: A Kleisli arrow that takes Errors and returns a Validate[I, A]. This function is called only when validation fails, receiving the accumulated errors.

Returns

A Validate[I, A] that handles error cases according to the provided function.

Example: Error Recovery

import (
    "github.com/IBM/fp-go/v2/optics/codec/validate"
    "github.com/IBM/fp-go/v2/optics/codec/validation"
)

// Validator that may fail
validatePositive := func(n int) validate.Reader[validation.Context, validation.Validation[int]] {
    return func(ctx validation.Context) validation.Validation[int] {
        if n > 0 {
            return validation.Success(n)
        }
        return validation.FailureWithMessage[int](n, "must be positive")(ctx)
    }
}

// Recover from specific errors with a default value
withDefault := func(errs validation.Errors) validate.Validate[int, int] {
    for _, err := range errs {
        if err.Messsage == "must be positive" {
            return validate.Of[int](0) // recover with default
        }
    }
    // Propagate other errors
    return func(input int) validate.Reader[validation.Context, validation.Validation[int]] {
        return func(ctx validation.Context) validation.Validation[int] {
            return either.Left[int](errs)
        }
    }
}

validator := validate.MonadChainLeft(validatePositive, withDefault)
result := validator(-5)(nil)
// Result: Success(0) - recovered from failure

Example: Error Context Addition

// Add contextual information to errors
addContext := func(errs validation.Errors) validate.Validate[string, int] {
    return func(input string) validate.Reader[validation.Context, validation.Validation[int]] {
        return func(ctx validation.Context) validation.Validation[int] {
            // Add context error (will be aggregated with original)
            return either.Left[int](validation.Errors{
                {
                    Context:  validation.Context{{Key: "user", Type: "User"}, {Key: "age", Type: "int"}},
                    Messsage: "failed to validate user age",
                },
            })
        }
    }
}

validator := validate.MonadChainLeft(someValidator, addContext)
// Errors will include both original error and context

Example: Input-Dependent Recovery

// Recover with different defaults based on input
smartDefault := func(errs validation.Errors) validate.Validate[string, int] {
    return func(input string) validate.Reader[validation.Context, validation.Validation[int]] {
        return func(ctx validation.Context) validation.Validation[int] {
            // Use input to determine appropriate default
            if strings.Contains(input, "http:") {
                return validation.Success(80)
            }
            if strings.Contains(input, "https:") {
                return validation.Success(443)
            }
            return validation.Success(8080)
        }
    }
}

validator := validate.MonadChainLeft(parsePort, smartDefault)

Notes

  • Errors are accumulated, not replaced - this ensures no validation failures are lost
  • The handler has access to both the errors and the original input
  • Success values bypass the handler completely
  • This is the direct application version of ChainLeft
  • This enables sophisticated error handling strategies including recovery, enrichment, and transformation

See Also

  • ChainLeft: The curried, point-free version
  • OrElse: Semantic alias for ChainLeft emphasizing fallback logic
  • MonadAlt: Simplified alternative that ignores error details
  • Alt: Curried version of MonadAlt

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)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL