validate

package
v2.1.25 Latest Latest
Warning

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

Go to latest
Published: Jan 23, 2026 License: Apache-2.0 Imports: 5 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]

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 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 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 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

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 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:

  1. 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])

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)

Jump to

Keyboard shortcuts

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