Documentation
¶
Overview ¶
Package codec provides pre-built codec implementations for common types. This package includes codecs for URL parsing, date/time formatting, and other standard data transformations that require bidirectional encoding/decoding.
The codecs in this package follow functional programming principles and integrate with the validation framework to provide type-safe, composable transformations.
Package codec provides a functional approach to encoding and decoding data with validation.
The codec package combines the concepts of encoders and decoders into a unified Type that can both encode values to an output format and decode/validate values from an input format. This is particularly useful for data serialization, API validation, and type-safe transformations.
Core Concepts ¶
Type[A, O, I]: A bidirectional codec that can:
- Decode input I to type A with validation
- Encode type A to output O
- Check if a value is of type A
Validation: Decoding returns Either[Errors, A] which represents:
- Left(Errors): Validation failed with detailed error information
- Right(A): Successfully decoded and validated value
Context: A stack of ContextEntry values that tracks the path through nested structures during validation, providing detailed error messages.
Basic Usage ¶
Creating a simple type:
nilType := codec.MakeNilType[string]()
result := nilType.Decode(nil) // Success
result := nilType.Decode("not nil") // Failure
Composing types with Pipe:
composed := codec.Pipe(typeB)(typeA) // Decodes: I -> A -> B // Encodes: B -> A -> O
Type Parameters ¶
Most functions use three type parameters:
- A: The domain type (the actual Go type being encoded/decoded)
- O: The output type for encoding
- I: The input type for decoding
Validation Errors ¶
ValidationError contains:
- Value: The actual value that failed validation
- Context: The path to the value in nested structures
- Message: Human-readable error description
Integration ¶
This package integrates with:
- optics/decoder: For decoding operations
- optics/encoder: For encoding operations
- either: For validation results
- option: For optional type checking
- reader: For context-dependent operations
Index ¶
- func Pipe[A, B, O, I any](ab Type[B, A, A]) func(Type[A, O, I]) Type[B, O, I]
- type Codec
- type Context
- type Decode
- type Decoder
- type Encode
- type Encoder
- type Endomorphism
- type Formattable
- type Kleisli
- type Lazy
- type Monoid
- type Operator
- type Option
- type Pair
- type Prism
- type Reader
- type ReaderResult
- type Refinement
- type Result
- type Type
- func Array[T, O any](item Type[T, O, any]) Type[[]T, []O, any]
- func Bool() Type[bool, bool, any]
- func Date(layout string) Type[time.Time, string, string]
- func Either[A, B, O, I any](leftItem Type[A, O, I], rightItem Type[B, O, I]) Type[either.Either[A, B], O, I]
- func FromRefinement[A, B any](refinement Refinement[A, B]) Type[B, A, A]
- func Id[T any]() Type[T, T, T]
- func Int() Type[int, int, any]
- func Int64FromString() Type[int64, string, string]
- func IntFromString() Type[int, string, string]
- func MakeSimpleType[A any]() Type[A, A, any]
- func MakeType[A, O, I any](name string, is Reader[any, Result[A]], validate Validate[I, A], ...) Type[A, O, I]
- func MonadAlt[A, O, I any](first Type[A, O, I], second Lazy[Type[A, O, I]]) Type[A, O, I]
- func Nil[A any]() Type[*A, *A, any]
- func Regex(re *regexp.Regexp) Type[prism.Match, string, string]
- func RegexNamed(re *regexp.Regexp) Type[prism.NamedMatch, string, string]
- func String() Type[string, string, any]
- func TranscodeArray[T, O, I any](item Type[T, O, I]) Type[[]T, []O, []I]
- func TranscodeEither[L, R, OL, OR, IL, IR any](leftItem Type[L, OL, IL], rightItem Type[R, OR, IR]) Type[either.Either[L, R], either.Either[OL, OR], either.Either[IL, IR]]
- func URL() Type[*url.URL, string, string]
- type Validate
- type Validation
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Pipe ¶
func Pipe[A, B, O, I any](ab Type[B, A, A]) func(Type[A, O, I]) Type[B, O, I]
Pipe composes two Types, creating a pipeline where:
- Decoding: I -> A -> B (decode with 'this', then validate with 'ab')
- Encoding: B -> A -> O (encode with 'ab', then encode with 'this')
This allows building complex codecs from simpler ones.
Example:
stringToInt := codec.MakeType(...) // Type[int, string, string] intToPositive := codec.MakeType(...) // Type[PositiveInt, int, int] composed := codec.Pipe(intToPositive)(stringToInt) // Type[PositiveInt, string, string]
Types ¶
type Codec ¶
Codec combines a Decoder and an Encoder for bidirectional transformations. It can decode input I to type A and encode type A to output O.
This is a simple struct that pairs a decoder with an encoder, providing the basic building blocks for bidirectional data transformation. Unlike the Type interface, Codec is a concrete struct without validation context or type checking capabilities.
Type Parameters:
- I: The input type to decode from
- O: The output type to encode to
- A: The intermediate type (decoded to, encoded from)
Fields:
- Decode: A decoder that transforms I to A
- Encode: An encoder that transforms A to O
Example:
A Codec[string, string, int] can decode strings to integers and encode integers back to strings.
Note: For most use cases, prefer using the Type interface which provides additional validation and type checking capabilities.
type Context ¶
type Context = validation.Context
Context provides contextual information for validation operations, such as the current path in a nested structure.
type Decode ¶
Decode is a function that decodes input I to type A with validation. It returns a Validation result directly.
The Decode type is defined as:
Reader[I, Validation[A]]
This is simpler than Validate as it doesn't require explicit context passing. The context is typically created automatically when the decoder is invoked.
Decode is used when:
- You don't need to manually manage validation context
- You want a simpler API for basic validation
- You're working at the top level of validation
Example:
A Decode[string, int] takes a string and returns a Validation[int] which is Either[Errors, int].
type Decoder ¶
type Decoder[I, A any] interface { Name() string Validate(I) Decode[Context, A] Decode(I) Validation[A] }
Decoder is an interface for types that can decode and validate input.
A Decoder transforms input of type I into a validated value of type A, providing detailed error information when validation fails. It supports both context-aware validation (via Validate) and direct decoding (via Decode).
Type Parameters:
- I: The input type to decode from
- A: The target type to decode to
Methods:
- Name(): Returns a descriptive name for this decoder (used in error messages)
- Validate(I): Returns a context-aware validation function that can track the path through nested structures
- Decode(I): Directly decodes input to a Validation result with a fresh context
The Validate method is more flexible as it returns a Reader that can be called with different contexts, while Decode is a convenience method that creates a new context automatically.
Example:
A Decoder[string, int] can decode strings to integers with validation.
type Encode ¶
Encode is a function that encodes type A to output O.
Encode is simply a Reader[A, O], which is a function from A to O. Encoders are pure functions with no error handling - they assume the input is valid.
Encoding is the inverse of decoding:
- Decoding: I -> Validation[A] (may fail)
- Encoding: A -> O (always succeeds)
Example:
An Encode[int, string] takes an integer and returns its string representation.
type Encoder ¶
type Encoder[A, O any] interface { // Encode transforms a value of type A into output format O. Encode(A) O }
Encoder is an interface for types that can encode values.
An Encoder transforms values of type A into output format O. This is the inverse operation of decoding, allowing bidirectional transformations.
Type Parameters:
- A: The source type to encode from
- O: The output type to encode to
Methods:
- Encode(A): Transforms a value of type A into output format O
Encoders are pure functions with no validation or error handling - they assume the input is valid. Validation should be performed during decoding.
Example:
An Encoder[int, string] can encode integers to their string representation.
type Endomorphism ¶
type Endomorphism[A any] = endomorphism.Endomorphism[A]
Endomorphism represents a function from type A to itself (A -> A). It forms a monoid under function composition.
type Formattable ¶ added in v2.1.21
type Formattable = formatting.Formattable
Formattable represents a type that can be formatted as a string representation. It provides a way to obtain a human-readable description of a type or value.
type Kleisli ¶ added in v2.2.12
Kleisli represents a Kleisli arrow in the codec context. It's a function that takes a value of type A and returns a codec Type[B, O, I].
This is the fundamental building block for codec transformations and compositions. Kleisli arrows allow you to:
- Chain codec operations
- Build dependent codecs (where the next codec depends on the previous result)
- Create codec pipelines
Type Parameters:
- A: The input type to the function
- B: The target type that the resulting codec decodes to
- O: The output type that the resulting codec encodes to
- I: The input type that the resulting codec decodes from
Example:
A Kleisli[string, int, string, string] takes a string and returns a codec that can decode strings to ints and encode ints to strings.
type Monoid ¶ added in v2.2.13
Monoid represents an algebraic structure with an associative binary operation and an identity element.
A Monoid[A] provides:
- Empty(): Returns the identity element
- Concat(A, A): Combines two values associatively
Monoid laws:
- Left Identity: Concat(Empty(), a) = a
- Right Identity: Concat(a, Empty()) = a
- Associativity: Concat(Concat(a, b), c) = Concat(a, Concat(b, c))
In the codec context, monoids are used to:
- Combine multiple codecs with specific semantics
- Build codec chains with fallback behavior (AltMonoid)
- Aggregate validation results (ApplicativeMonoid)
- Compose codec transformations
Example monoids for codecs:
- AltMonoid: First success wins (alternative semantics)
- ApplicativeMonoid: Combines successful results using inner monoid
- AlternativeMonoid: Combines applicative and alternative behaviors
func AltMonoid ¶ added in v2.2.13
func AltMonoid[A, O, I any](zero Lazy[Type[A, O, I]]) Monoid[Type[A, O, I]]
AltMonoid creates a Monoid instance for Type[A, O, I] using alternative semantics with a provided zero/default codec.
This function creates a monoid where:
- The first successful codec wins (no result combination)
- If the first fails during validation, the second is tried as a fallback
- If both fail, errors are aggregated
- The provided zero codec serves as the identity element
Unlike other monoid patterns, AltMonoid does NOT combine successful results - it always returns the first success. This makes it ideal for building fallback chains with default codecs, configuration loading from multiple sources, and parser combinators with alternatives.
Type Parameters ¶
- A: The target type that all codecs decode to
- O: The output type that all codecs encode to
- I: The input type that all codecs decode from
Parameters ¶
- zero: A lazy Type[A, O, I] that serves as the identity element. This is typically a codec that always succeeds with a default value, but can also be a failing codec if no default is appropriate.
Returns ¶
A Monoid[Type[A, O, I]] that combines codecs 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 codec is used as fallback
- **Encoding**: Always uses the first codec's encoder
Example: Configuration Loading with Fallbacks ¶
import (
"github.com/IBM/fp-go/v2/optics/codec"
"github.com/IBM/fp-go/v2/array"
)
// Create a monoid with a default configuration
m := codec.AltMonoid(func() codec.Type[Config, string, string] {
return codec.MakeType(
"DefaultConfig",
codec.Is[Config](),
func(s string) codec.Decode[codec.Context, Config] {
return func(c codec.Context) codec.Validation[Config] {
return validation.Success(defaultConfig)
}
},
encodeConfig,
)
})
// Define codecs for different sources
fileCodec := loadFromFile("config.json")
envCodec := loadFromEnv()
defaultCodec := m.Empty()
// Try file, then env, then default
configCodec := array.MonadFold(
[]codec.Type[Config, string, string]{fileCodec, envCodec, defaultCodec},
m.Empty(),
m.Concat,
)
// Load configuration - tries each source in order
result := configCodec.Decode(input)
Example: Parser with Multiple Formats ¶
// Create a monoid for parsing dates in multiple formats
m := codec.AltMonoid(func() codec.Type[time.Time, string, string] {
return codec.Date(time.RFC3339) // default format
})
// Define parsers for different date formats
iso8601 := codec.Date("2006-01-02")
usFormat := codec.Date("01/02/2006")
euroFormat := codec.Date("02/01/2006")
// Combine: try ISO 8601, then US, then European, then RFC3339
flexibleDate := m.Concat(
m.Concat(
m.Concat(iso8601, usFormat),
euroFormat,
),
m.Empty(),
)
// Can parse any of these formats
result1 := flexibleDate.Decode("2024-03-15") // ISO 8601
result2 := flexibleDate.Decode("03/15/2024") // US format
result3 := flexibleDate.Decode("15/03/2024") // European format
Example: Integer Parsing with Default ¶
// Create a monoid with default value of 0
m := codec.AltMonoid(func() codec.Type[int, string, string] {
return codec.MakeType(
"DefaultZero",
codec.Is[int](),
func(s string) codec.Decode[codec.Context, int] {
return func(c codec.Context) codec.Validation[int] {
return validation.Success(0)
}
},
strconv.Itoa,
)
})
// Try parsing as int, fall back to 0
intOrZero := m.Concat(codec.IntFromString(), m.Empty())
result1 := intOrZero.Decode("42") // Success(42)
result2 := intOrZero.Decode("invalid") // Success(0) - uses default
Example: Error Aggregation ¶
// Both codecs fail - errors are aggregated
m := codec.AltMonoid(func() codec.Type[int, string, string] {
return codec.MakeType(
"NoDefault",
codec.Is[int](),
func(s string) codec.Decode[codec.Context, int] {
return func(c codec.Context) codec.Validation[int] {
return validation.FailureWithMessage[int](s, "no default available")(c)
}
},
strconv.Itoa,
)
})
failing1 := codec.MakeType(
"Failing1",
codec.Is[int](),
func(s string) codec.Decode[codec.Context, int] {
return func(c codec.Context) codec.Validation[int] {
return validation.FailureWithMessage[int](s, "error 1")(c)
}
},
strconv.Itoa,
)
failing2 := codec.MakeType(
"Failing2",
codec.Is[int](),
func(s string) codec.Decode[codec.Context, int] {
return func(c codec.Context) codec.Validation[int] {
return validation.FailureWithMessage[int](s, "error 2")(c)
}
},
strconv.Itoa,
)
combined := m.Concat(failing1, failing2)
result := combined.Decode("input")
// result contains errors: "error 1", "error 2", and "no default available"
Monoid Laws ¶
AltMonoid satisfies the monoid laws:
- **Left Identity**: m.Concat(m.Empty(), codec) ≡ codec
- **Right Identity**: m.Concat(codec, m.Empty()) ≡ codec (tries codec first, falls back to zero)
- **Associativity**: m.Concat(m.Concat(a, b), c) ≡ m.Concat(a, m.Concat(b, c))
Note: Due to the "first success wins" behavior, right identity means the zero is only used if the codec fails.
Use Cases ¶
- Configuration loading with multiple sources (file, env, default)
- Parsing data in multiple formats with fallbacks
- API versioning (try v2, fall back to v1, then default)
- Content negotiation (try JSON, then XML, then plain text)
- Validation with default values
- Parser combinators with alternative branches
Notes ¶
- The zero codec is lazily evaluated, only when needed
- First success short-circuits evaluation (subsequent codecs not tried)
- Error aggregation ensures all validation failures are reported
- Encoding always uses the first codec's encoder
- This follows the alternative functor laws
See Also ¶
- MonadAlt: The underlying alternative operation for two codecs
- Alt: The curried version for pipeline composition
- validate.AltMonoid: The validation-level alternative monoid
- decode.AltMonoid: The decode-level alternative monoid
type Operator ¶ added in v2.2.12
Operator is a specialized Kleisli arrow that transforms codecs. It takes a codec Type[A, O, I] and returns a new codec Type[B, O, I].
Operators are the primary way to build codec transformation pipelines. They enable functional composition of codec transformations using F.Pipe.
Type Parameters:
- A: The source type that the input codec decodes to
- B: The target type that the output codec decodes to
- O: The output type (same for both input and output codecs)
- I: The input type (same for both input and output codecs)
Common operators include:
- Map: Transforms the decoded value
- Chain: Sequences dependent codec operations
- Alt: Provides alternative fallback codecs
- Refine: Adds validation constraints
Example:
An Operator[int, PositiveInt, int, any] transforms a codec that decodes to int into a codec that decodes to PositiveInt (with validation).
Usage with F.Pipe:
codec := F.Pipe2(
baseCodec,
operator1, // Operator[A, B, O, I]
operator2, // Operator[B, C, O, I]
)
func Alt ¶ added in v2.2.12
func Alt[A, O, I any](second Lazy[Type[A, O, I]]) Operator[A, A, O, I]
Alt creates an operator that adds alternative fallback logic to a codec.
This is the curried, point-free version of MonadAlt. It returns a function that can be applied to codecs to add fallback behavior. This style is particularly useful for building codec transformation pipelines using function composition.
Alt implements the Alternative typeclass pattern, enabling "try this codec, or else try that codec" logic in a composable way.
Type Parameters ¶
- A: The target type that both codecs decode to
- O: The output type that both codecs encode to
- I: The input type that both codecs decode from
Parameters ¶
- second: A lazy codec that serves as the fallback. It's only evaluated if the first codec's validation fails.
Returns ¶
An Operator[A, A, O, I] that transforms codecs by adding alternative fallback logic. This operator can be applied to any Type[A, O, I] to create a new codec with fallback behavior.
Behavior ¶
When the returned operator is applied to a codec:
- **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: Point-Free Style ¶
import (
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/optics/codec"
)
// Create a reusable fallback operator
withNumberFallback := codec.Alt(func() codec.Type[int, any, any] {
return codec.Int()
})
// Apply it to different codecs
flexibleInt1 := withNumberFallback(codec.IntFromString())
flexibleInt2 := withNumberFallback(codec.IntFromHex())
Example: Pipeline Composition ¶
// Build a codec pipeline with multiple fallbacks
flexibleCodec := F.Pipe2(
primaryCodec,
codec.Alt(func() codec.Type[T, O, I] { return fallback1 }),
codec.Alt(func() codec.Type[T, O, I] { return fallback2 }),
)
// Tries primary, then fallback1, then fallback2
Example: Reusable Transformations ¶
// Create a transformation that adds JSON fallback
withJSONFallback := codec.Alt(func() codec.Type[Config, string, string] {
return codec.JSONCodec[Config]()
})
// Apply to multiple codecs
yamlWithFallback := withJSONFallback(yamlCodec)
tomlWithFallback := withJSONFallback(tomlCodec)
Notes ¶
- This is the point-free style version of MonadAlt
- Useful for building transformation pipelines with F.Pipe
- The second codec is lazily evaluated for efficiency
- First success short-circuits evaluation
- Errors are aggregated when both fail
- Can be composed with other codec operators
See Also ¶
- MonadAlt: The direct application version
- validate.Alt: The underlying validation operation
- F.Pipe: For composing multiple operators
type Prism ¶ added in v2.1.19
Prism is an optic that focuses on a part of a sum type S that may or may not contain a value of type A. It provides a way to preview and review values.
func TypeToPrism ¶ added in v2.1.19
func TypeToPrism[S, A any](t Type[A, S, S]) Prism[S, A]
TypeToPrism converts a Type codec into a Prism optic.
A Type[A, S, S] represents a bidirectional codec that can decode S to A (with validation) and encode A back to S. A Prism[S, A] is an optic that can optionally extract an A from S and always construct an S from an A.
This conversion bridges the codec and optics worlds, allowing you to use validation-based codecs as prisms for functional optics composition.
Type Parameters:
- S: The source/encoded type (both input and output)
- A: The decoded/focus type
Parameters:
- t: A Type[A, S, S] codec where:
- Decode: S → Validation[A] (may fail with validation errors)
- Encode: A → S (always succeeds)
- Name: Provides a descriptive name for the type
Returns:
- A Prism[S, A] where:
- GetOption: S → Option[A] (Some if decode succeeds, None if validation fails)
- ReverseGet: A → S (uses the codec's Encode function)
- Name: Inherited from the Type's name
The conversion works as follows:
- GetOption: Decodes the value and converts validation result to Option (Right(a) → Some(a), Left(errors) → None)
- ReverseGet: Directly uses the Type's Encode function
- Name: Preserves the Type's descriptive name
Example:
// Create a codec for positive integers
positiveInt := codec.MakeType[int, int, int](
"PositiveInt",
func(i any) result.Result[int] { ... },
func(i int) codec.Validate[int] {
if i <= 0 {
return validation.FailureWithMessage(i, "must be positive")
}
return validation.Success(i)
},
func(i int) int { return i },
)
// Convert to prism
prism := codec.TypeToPrism(positiveInt)
// Use as prism
value := prism.GetOption(42) // Some(42) - validation succeeds
value = prism.GetOption(-5) // None - validation fails
result := prism.ReverseGet(10) // 10 - encoding always succeeds
Use cases:
- Composing codecs with other optics (lenses, prisms, traversals)
- Using validation logic in optics pipelines
- Building complex data transformations with functional composition
- Integrating type-safe parsing with optics-based data access
Note: The prism's GetOption will return None for any validation failure, discarding the specific error details. If you need error information, use the Type's Decode method directly instead.
type Reader ¶
Reader represents a computation that depends on an environment R and produces a value A.
type ReaderResult ¶
type ReaderResult[R, A any] = readerresult.ReaderResult[R, A]
ReaderResult represents a computation that depends on an environment R, produces a value A, and may fail with an error.
func Is ¶
func Is[T any]() ReaderResult[any, T]
Is checks if a value can be converted to type T. Returns Some(value) if the conversion succeeds, None otherwise. This is a type-safe cast operation.
type Refinement ¶ added in v2.1.19
Refinement represents the concept that B is a specialized type of A. It's an alias for Prism[A, B], providing a semantic name for type refinement operations.
A refinement allows you to:
- Preview: Try to extract a B from an A (may fail if A is not a B)
- Review: Inject a B back into an A
This is useful for working with subtypes, validated types, or constrained types.
Example:
- Refinement[int, PositiveInt] - refines int to positive integers only
- Refinement[string, NonEmptyString] - refines string to non-empty strings
- Refinement[any, User] - refines any to User type
type Type ¶
type Type[A, O, I any] interface { Formattable Decoder[I, A] Encoder[A, O] AsDecoder() Decoder[I, A] AsEncoder() Encoder[A, O] Is(any) Result[A] }
Type is a bidirectional codec that combines encoding, decoding, validation, and type checking capabilities. It represents a complete specification of how to work with a particular type.
Type is the central abstraction in the codec package, providing:
- Decoding: Transform input I to validated type A
- Encoding: Transform type A to output O
- Validation: Context-aware validation with detailed error reporting
- Type Checking: Runtime type verification via Is()
- Formatting: Human-readable type descriptions via Name()
Type Parameters:
- A: The target type (what we decode to and encode from)
- O: The output type (what we encode to)
- I: The input type (what we decode from)
Common patterns:
- Type[A, A, A]: Identity codec (no transformation)
- Type[A, string, string]: String-based serialization
- Type[A, any, any]: Generic codec accepting any input/output
- Type[A, JSON, JSON]: JSON codec
Methods:
- Name(): Returns the codec's descriptive name
- Validate(I): Returns context-aware validation function
- Decode(I): Decodes input with automatic context creation
- Encode(A): Encodes value to output format
- AsDecoder(): Returns this Type as a Decoder interface
- AsEncoder(): Returns this Type as an Encoder interface
- Is(any): Checks if a value can be converted to type A
Example usage:
intCodec := codec.Int() // Type[int, int, any]
stringCodec := codec.String() // Type[string, string, any]
intFromString := codec.IntFromString() // Type[int, string, string]
// Decode
result := intFromString.Decode("42") // Validation[int]
// Encode
str := intFromString.Encode(42) // "42"
// Type check
isInt := intCodec.Is(42) // Right(42)
notInt := intCodec.Is("42") // Left(error)
Composition:
Types can be composed using operators like Alt, Map, Chain, and Pipe to build complex codecs from simpler ones.
func Array ¶ added in v2.1.19
Array creates a Type for array/slice values with elements of type T. It validates that input is an array, slice, or string, and validates each element using the provided item Type. During encoding, it maps the encode function over all elements.
Type Parameters:
- T: The type of elements in the decoded array
- O: The type of elements in the encoded array
Parameters:
- item: A Type[T, O, any] that defines how to validate/encode individual elements
Returns:
- A Type[[]T, []O, any] that can validate, decode, and encode array values
The function handles:
- Native Go slices of type []T (passed through directly)
- reflect.Array, reflect.Slice, reflect.String (validated element by element)
- Collects all validation errors from individual elements
- Provides detailed context for each element's position in error messages
Example:
intArray := codec.Array(codec.Int())
result := intArray.Decode([]int{1, 2, 3}) // Success: Right([1, 2, 3])
result := intArray.Decode([]any{1, "2", 3}) // Failure: validation error at index 1
encoded := intArray.Encode([]int{1, 2, 3}) // Returns: []int{1, 2, 3}
stringArray := codec.Array(codec.String())
result := stringArray.Decode([]string{"a", "b"}) // Success: Right(["a", "b"])
result := stringArray.Decode("hello") // Success: Right(["h", "e", "l", "l", "o"])
func Bool ¶
Bool creates a Type for bool values. It validates that input is a bool type and provides identity encoding/decoding. This is a simple type that accepts any input and validates it's a bool.
Returns:
- A Type[bool, bool, any] that can validate, decode, and encode bool values
Example:
boolType := codec.Bool() result := boolType.Decode(true) // Success: Right(true) result := boolType.Decode(1) // Failure: Left(validation errors) encoded := boolType.Encode(false) // Returns: false
func Date ¶ added in v2.2.1
Date creates a bidirectional codec for date/time parsing and formatting with a specific layout. This codec uses Go's time.Parse and time.Format with the provided layout string.
The codec:
- Decodes: Parses a string into time.Time using the specified layout
- Encodes: Formats a time.Time back to a string using the same layout
- Validates: Ensures the input string matches the expected date/time format
Parameters:
- layout: The time layout string (e.g., "2006-01-02", time.RFC3339) See time package documentation for layout format details
Returns:
- A Type[time.Time, string, string] codec that handles date/time transformations
Example:
// Create a codec for ISO 8601 dates
dateCodec := Date("2006-01-02")
// Decode a string to time.Time
validation := dateCodec.Decode("2024-03-15")
// validation is Right(time.Time{...})
// Encode a time.Time to string
t := time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC)
str := dateCodec.Encode(t)
// str is "2024-03-15"
// Create a codec for RFC3339 timestamps
timestampCodec := Date(time.RFC3339)
validation := timestampCodec.Decode("2024-03-15T10:30:00Z")
// Invalid format fails validation
validation := dateCodec.Decode("15-03-2024")
// validation is Left(ValidationError{...})
func Either ¶
func Either[A, B, O, I any]( leftItem Type[A, O, I], rightItem Type[B, O, I], ) Type[either.Either[A, B], O, I]
Either creates a codec for Either[A, B] values.
This function constructs a complete codec that can encode, decode, and validate Either values. An Either represents a value that can be one of two types: Left (A) or Right (B). This is commonly used for error handling, where Left represents an error and Right represents a success value.
The codec handles both branches of the Either type using the provided codecs for each branch. During validation, it attempts to validate the input as both types and succeeds if either validation passes.
Type Parameters ¶
- A: The type of the Left value
- B: The type of the Right value
- O: The output type after encoding
- I: The input type for validation
Parameters ¶
- leftItem: The codec for handling Left values of type A
- rightItem: The codec for handling Right values of type B
Returns ¶
A Type[either.Either[A, B], O, I] that can encode, decode, and validate Either values.
Codec Behavior ¶
Encoding:
- Left values are encoded using leftItem.Encode
- Right values are encoded using rightItem.Encode
Validation:
- First attempts to validate as Right (B)
- If Right fails, attempts to validate as Left (A)
- If both fail, returns all accumulated errors
- If either succeeds, returns the corresponding Either value
Type Checking:
- Uses Is[either.Either[A, B]]() to verify the value is an Either
Naming:
- The codec name is "Either[<leftName>, <rightName>]"
- Example: "Either[string, int]"
Example ¶
// Create a codec for Either[string, int]
stringCodec := String()
intCodec := Int()
eitherCodec := Either(stringCodec, intCodec)
// Encode a Left value
leftEncoded := eitherCodec.Encode(either.Left[int]("error"))
// leftEncoded contains the encoded string
// Encode a Right value
rightEncoded := eitherCodec.Encode(either.Right[string](42))
// rightEncoded contains the encoded int
// Decode/validate an input
result := eitherCodec.Decode("hello")
// result is Success(Either.Left[int]("hello"))
result2 := eitherCodec.Decode(42)
// result2 is Success(Either.Right[string](42))
// Get the codec name
name := eitherCodec.Name()
// name is "Either[string, int]"
Use Cases ¶
- Error handling: Either[Error, Value]
- Alternative values: Either[DefaultValue, CustomValue]
- Union types: Either[TypeA, TypeB]
- Validation results: Either[ValidationError, ValidatedValue]
Notes ¶
- The codec prioritizes Right validation over Left validation
- Both branches must have compatible encoding output types (O)
- Both branches must have compatible validation input types (I)
- The codec name includes the names of both branch codecs
- This is a building block for more complex sum types
func FromRefinement ¶ added in v2.1.19
func FromRefinement[A, B any](refinement Refinement[A, B]) Type[B, A, A]
FromRefinement creates a Type codec from a Refinement (Prism).
A Refinement[A, B] represents the concept that B is a specialized/refined version of A. For example, PositiveInt is a refinement of int, or NonEmptyString is a refinement of string. This function converts a Prism[A, B] into a Type[B, A, A] codec that can validate and transform between the base type A and the refined type B.
Type Parameters:
- A: The base/broader type (e.g., int, string)
- B: The refined/specialized type (e.g., PositiveInt, NonEmptyString)
Parameters:
- refinement: A Refinement[A, B] (which is a Prism[A, B]) that defines:
- GetOption: A → Option[B] - attempts to refine A to B (may fail if refinement conditions aren't met)
- ReverseGet: B → A - converts refined type back to base type (always succeeds)
Returns:
- A Type[B, A, A] codec where:
- Decode: A → Validation[B] - validates that A satisfies refinement conditions and produces B
- Encode: B → A - converts refined type back to base type using ReverseGet
- Is: Checks if a value is of type B
- Name: Descriptive name including the refinement's string representation
The codec:
- Uses the refinement's GetOption for validation during decoding
- Returns validation errors if the refinement conditions are not met
- Uses the refinement's ReverseGet for encoding (always succeeds)
- Provides context-aware error messages indicating why refinement failed
Example:
// Define a refinement for positive integers
positiveIntPrism := prism.MakePrismWithName(
func(n int) option.Option[int] {
if n > 0 {
return option.Some(n)
}
return option.None[int]()
},
func(n int) int { return n },
"PositiveInt",
)
// Create a codec from the refinement
positiveIntCodec := codec.FromRefinement[int, int](positiveIntPrism)
// Decode: validates the refinement condition
result := positiveIntCodec.Decode(42) // Success: Right(42)
result = positiveIntCodec.Decode(-5) // Failure: validation error
result = positiveIntCodec.Decode(0) // Failure: validation error
// Encode: converts back to base type
encoded := positiveIntCodec.Encode(42) // Returns: 42
Use cases:
- Creating codecs for refined types (positive numbers, non-empty strings, etc.)
- Validating that values meet specific constraints
- Building type-safe APIs with refined types
- Composing refinements with other codecs using Pipe
Common refinement patterns:
- Numeric constraints: PositiveInt, NonNegativeFloat, BoundedInt
- String constraints: NonEmptyString, EmailAddress, URL
- Collection constraints: NonEmptyArray, UniqueElements
- Domain-specific constraints: ValidAge, ValidZipCode, ValidCreditCard
Note: The refinement's GetOption returning None will result in a validation error with a message indicating the type cannot be refined. For more specific error messages, consider using MakeType directly with custom validation logic.
func Id ¶ added in v2.1.19
func Id[T any]() Type[T, T, T]
Id creates an identity Type codec that performs no transformation or validation.
An identity codec is a Type[T, T, T] where:
- Decode: Always succeeds and returns the input value unchanged
- Encode: Returns the input value unchanged (identity function)
- Validation: Always succeeds without any checks
This is useful as:
- A building block for more complex codecs
- A no-op codec when you need a Type but don't want any transformation
- A starting point for codec composition
- Testing and debugging codec pipelines
Type Parameters:
- T: The type that passes through unchanged
Returns:
- A Type[T, T, T] that performs identity operations on type T
The codec:
- Name: Uses the type's string representation (e.g., "int", "string")
- Is: Checks if a value is of type T
- Validate: Always succeeds and returns the input value
- Encode: Identity function (returns input unchanged)
Example:
// Create an identity codec for strings
stringId := codec.Id[string]()
// Decode always succeeds
result := stringId.Decode("hello") // Success: Right("hello")
// Encode is identity
encoded := stringId.Encode("world") // Returns: "world"
// Use in composition
arrayOfStrings := codec.TranscodeArray(stringId)
result := arrayOfStrings.Decode([]string{"a", "b", "c"})
Use cases:
- When you need a Type but don't want any validation or transformation
- As a placeholder in generic code that requires a Type parameter
- Building blocks for TranscodeArray, TranscodeEither, etc.
- Testing codec composition without side effects
Note: Unlike MakeSimpleType which validates the type, Id always succeeds in validation. It only checks the type during the Is operation.
func Int ¶
Int creates a Type for int values. It validates that input is an int type and provides identity encoding/decoding. This is a simple type that accepts any input and validates it's an int.
Returns:
- A Type[int, int, any] that can validate, decode, and encode int values
Example:
intType := codec.Int()
result := intType.Decode(42) // Success: Right(42)
result := intType.Decode("42") // Failure: Left(validation errors)
encoded := intType.Encode(100) // Returns: 100
func Int64FromString ¶ added in v2.2.1
Int64FromString creates a bidirectional codec for parsing 64-bit integers from strings. This codec converts string representations of integers to int64 values and vice versa.
The codec:
- Decodes: Parses a string to an int64 using strconv.ParseInt with base 10
- Encodes: Converts an int64 to its string representation
- Validates: Ensures the string contains a valid 64-bit integer (base 10)
The codec accepts integers in base 10 format, with optional leading sign (+/-). It supports the full range of int64 values (-9223372036854775808 to 9223372036854775807).
Returns:
- A Type[int64, string, string] codec that handles int64/string conversions
Example:
int64Codec := Int64FromString()
// Decode a valid integer string
validation := int64Codec.Decode("9223372036854775807")
// validation is Right(9223372036854775807)
// Decode negative integer
validation := int64Codec.Decode("-9223372036854775808")
// validation is Right(-9223372036854775808)
// Encode an int64 to string
str := int64Codec.Encode(42)
// str is "42"
// Invalid integer string fails validation
validation := int64Codec.Decode("not a number")
// validation is Left(ValidationError{...})
// Out of range value fails validation
validation := int64Codec.Decode("9223372036854775808")
// validation is Left(ValidationError{...})
func IntFromString ¶ added in v2.2.1
IntFromString creates a bidirectional codec for parsing integers from strings. This codec converts string representations of integers to int values and vice versa.
The codec:
- Decodes: Parses a string to an int using strconv.Atoi
- Encodes: Converts an int to its string representation using strconv.Itoa
- Validates: Ensures the string contains a valid integer (base 10)
The codec accepts integers in base 10 format, with optional leading sign (+/-). It does not accept hexadecimal, octal, or other number formats.
Returns:
- A Type[int, string, string] codec that handles int/string conversions
Example:
intCodec := IntFromString()
// Decode a valid integer string
validation := intCodec.Decode("42")
// validation is Right(42)
// Decode negative integer
validation := intCodec.Decode("-123")
// validation is Right(-123)
// Encode an integer to string
str := intCodec.Encode(42)
// str is "42"
// Invalid integer string fails validation
validation := intCodec.Decode("not a number")
// validation is Left(ValidationError{...})
// Floating point fails validation
validation := intCodec.Decode("3.14")
// validation is Left(ValidationError{...})
func MakeSimpleType ¶
func MakeType ¶
func MakeType[A, O, I any]( name string, is Reader[any, Result[A]], validate Validate[I, A], encode Encode[A, O], ) Type[A, O, I]
MakeType creates a new Type with the given name, type checker, validator, and encoder.
Parameters:
- name: A descriptive name for this type (used in error messages)
- is: A function that checks if a value is of type A
- validate: A function that validates and decodes input I to type A
- encode: A function that encodes type A to output O
Returns a Type[A, O, I] that can both encode and decode values.
func MonadAlt ¶ added in v2.2.12
func MonadAlt[A, O, I any](first Type[A, O, I], second Lazy[Type[A, O, I]]) Type[A, O, I]
MonadAlt creates a new codec that tries the first codec, and if it fails during validation, tries the second codec as a fallback.
This function implements the Alternative typeclass pattern for codecs, enabling "try this codec, or else try that codec" logic. It's particularly useful for:
- Handling multiple valid input formats
- Providing backward compatibility with legacy formats
- Implementing graceful degradation in parsing
- Supporting union types or polymorphic data
The resulting codec uses the first codec's encoder and combines both validators using the Alternative pattern. If both validations fail, errors from both are aggregated for comprehensive error reporting.
Type Parameters ¶
- A: The target type that both codecs decode to
- O: The output type that both codecs encode to
- I: The input type that both codecs decode from
Parameters ¶
- first: The primary codec to try first. Its encoder is used for the result.
- second: A lazy codec that serves as the fallback. It's only evaluated if the first validation fails.
Returns ¶
A new Type[A, O, I] that combines both codecs with Alternative semantics.
Behavior ¶
**Validation**:
- **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
**Encoding**:
- Always uses the first codec's encoder
- This assumes both codecs encode to the same output format
**Type Checking**:
- Uses the generic Is[A]() type checker
- Validates that values are of type A
Example: Multiple Input Formats ¶
import (
"github.com/IBM/fp-go/v2/optics/codec"
)
// Accept integers as either strings or numbers
intFromString := codec.IntFromString()
intFromNumber := codec.Int()
// Try parsing as string first, fall back to number
flexibleInt := codec.MonadAlt(
intFromString,
func() codec.Type[int, any, any] { return intFromNumber },
)
// Can now decode both "42" and 42
result1 := flexibleInt.Decode("42") // Success(42)
result2 := flexibleInt.Decode(42) // Success(42)
Example: Backward Compatibility ¶
// Support both old and new configuration formats
newConfigCodec := codec.Struct(/* new format */)
oldConfigCodec := codec.Struct(/* old format */)
// Try new format first, fall back to old format
configCodec := codec.MonadAlt(
newConfigCodec,
func() codec.Type[Config, any, any] { return oldConfigCodec },
)
// Automatically handles both formats
config := configCodec.Decode(input)
Example: Error Aggregation ¶
// Both validations will fail for invalid input
result := flexibleInt.Decode("not a number")
// Result contains errors from both string and number parsing attempts
Notes ¶
- The second codec is lazily evaluated for efficiency
- First success short-circuits evaluation (second not called)
- Errors are aggregated when both fail
- The resulting codec's name is "Alt[<first codec name>]"
- Both codecs must have compatible input and output types
- The first codec's encoder is always used
See Also ¶
- Alt: The curried, point-free version
- validate.MonadAlt: The underlying validation operation
- Either: For codecs that decode to Either[L, R] types
func Nil ¶
MakeNilType creates a Type that validates nil values. It accepts any input and validates that it is nil, returning a typed nil pointer.
Example:
nilType := codec.MakeNilType[string]()
result := nilType.Decode(nil) // Success: Right((*string)(nil))
result := nilType.Decode("not nil") // Failure: Left(errors)
func Regex ¶ added in v2.2.1
Regex creates a bidirectional codec for regex pattern matching with capture groups. This codec can match strings against a regular expression pattern and extract capture groups, then reconstruct the original string from the match data.
The codec uses prism.Match which contains:
- Before: Text before the match
- Groups: Capture groups (index 0 is the full match, 1+ are numbered capture groups)
- After: Text after the match
The codec:
- Decodes: Attempts to match the regex against the input string
- Encodes: Reconstructs the original string from a Match structure
- Validates: Ensures the string matches the regex pattern
Parameters:
- re: A compiled regular expression pattern
Returns:
- A Type[prism.Match, string, string] codec that handles regex matching
Example:
// Create a codec for matching numbers in text
numberRegex := regexp.MustCompile(`\d+`)
numberCodec := Regex(numberRegex)
// Decode a string with a number
validation := numberCodec.Decode("Price: 42 dollars")
// validation is Right(Match{Before: "Price: ", Groups: []string{"42"}, After: " dollars"})
// Encode a Match back to string
match := prism.Match{Before: "Price: ", Groups: []string{"42"}, After: " dollars"}
str := numberCodec.Encode(match)
// str is "Price: 42 dollars"
// Non-matching string fails validation
validation := numberCodec.Decode("no numbers here")
// validation is Left(ValidationError{...})
func RegexNamed ¶ added in v2.2.1
RegexNamed creates a bidirectional codec for regex pattern matching with named capture groups. This codec can match strings against a regular expression with named groups and extract them by name, then reconstruct the original string from the match data.
The codec uses prism.NamedMatch which contains:
- Before: Text before the match
- Groups: Map of named capture groups (name -> matched text)
- Full: The complete matched text
- After: Text after the match
The codec:
- Decodes: Attempts to match the regex against the input string
- Encodes: Reconstructs the original string from a NamedMatch structure
- Validates: Ensures the string matches the regex pattern with named groups
Parameters:
- re: A compiled regular expression with named capture groups (e.g., `(?P<name>pattern)`)
Returns:
- A Type[prism.NamedMatch, string, string] codec that handles named regex matching
Example:
// Create a codec for matching email addresses with named groups
emailRegex := regexp.MustCompile(`(?P<user>\w+)@(?P<domain>\w+\.\w+)`)
emailCodec := RegexNamed(emailRegex)
// Decode an email string
validation := emailCodec.Decode("john@example.com")
// validation is Right(NamedMatch{
// Before: "",
// Groups: map[string]string{"user": "john", "domain": "example.com"},
// Full: "john@example.com",
// After: ""
// })
// Encode a NamedMatch back to string
match := prism.NamedMatch{
Before: "",
Groups: map[string]string{"user": "john", "domain": "example.com"},
Full: "john@example.com",
After: "",
}
str := emailCodec.Encode(match)
// str is "john@example.com"
// Non-matching string fails validation
validation := emailCodec.Decode("not-an-email")
// validation is Left(ValidationError{...})
func String ¶
String creates a Type for string values. It validates that input is a string type and provides identity encoding/decoding. This is a simple type that accepts any input and validates it's a string.
Returns:
- A Type[string, string, any] that can validate, decode, and encode string values
Example:
stringType := codec.String()
result := stringType.Decode("hello") // Success: Right("hello")
result := stringType.Decode(123) // Failure: Left(validation errors)
encoded := stringType.Encode("world") // Returns: "world"
func TranscodeArray ¶ added in v2.1.19
func TranscodeArray[T, O, I any](item Type[T, O, I]) Type[[]T, []O, []I]
TranscodeArray creates a Type for array/slice values with strongly-typed input. Unlike Array which accepts any input type, TranscodeArray requires the input to be a slice of type []I, providing type safety at the input level.
This function validates each element of the input slice using the provided item Type, transforming []I -> []T during decoding and []T -> []O during encoding.
Type Parameters:
- T: The type of elements in the decoded array
- O: The type of elements in the encoded array
- I: The type of elements in the input array (must be a slice)
Parameters:
- item: A Type[T, O, I] that defines how to validate/encode individual elements
Returns:
- A Type[[]T, []O, []I] that can validate, decode, and encode array values
The function:
- Requires input to be exactly []I (not any)
- Validates each element using the item Type's validation logic
- Collects all validation errors from individual elements
- Provides detailed context for each element's position in error messages
- Maps the encode function over all elements during encoding
Example:
// Create a codec that transforms string slices to int slices
stringToInt := codec.MakeType[int, int, string](
"StringToInt",
func(s any) result.Result[int] { ... },
func(s string) codec.Validate[int] { ... },
func(i int) int { return i },
)
arrayCodec := codec.TranscodeArray(stringToInt)
// Decode: []string -> []int
result := arrayCodec.Decode([]string{"1", "2", "3"}) // Success: Right([1, 2, 3])
result := arrayCodec.Decode([]string{"1", "x", "3"}) // Failure: validation error at index 1
// Encode: []int -> []int
encoded := arrayCodec.Encode([]int{1, 2, 3}) // Returns: []int{1, 2, 3}
Use TranscodeArray when:
- You need type-safe input validation ([]I instead of any)
- You're transforming between different slice element types
- You want compile-time guarantees about input types
Use Array when:
- You need to accept various input types (any, reflect.Value, etc.)
- You're working with dynamic or unknown input types
func TranscodeEither ¶ added in v2.1.19
func TranscodeEither[L, R, OL, OR, IL, IR any](leftItem Type[L, OL, IL], rightItem Type[R, OR, IR]) Type[either.Either[L, R], either.Either[OL, OR], either.Either[IL, IR]]
TranscodeEither creates a Type for Either values with strongly-typed left and right branches. It validates and transforms Either[IL, IR] to Either[L, R] during decoding, and Either[L, R] to Either[OL, OR] during encoding.
This function is useful for handling sum types (discriminated unions) where a value can be one of two possible types. Each branch (Left and Right) is validated and transformed independently using its respective Type codec.
Type Parameters:
- L: The type of the decoded Left value
- R: The type of the decoded Right value
- OL: The type of the encoded Left value
- OR: The type of the encoded Right value
- IL: The type of the input Left value
- IR: The type of the input Right value
Parameters:
- leftItem: A Type[L, OL, IL] that defines how to validate/encode Left values
- rightItem: A Type[R, OR, IR] that defines how to validate/encode Right values
Returns:
- A Type[Either[L, R], Either[OL, OR], Either[IL, IR]] that can validate, decode, and encode Either values
The function:
- Validates Left values using leftItem's validation logic
- Validates Right values using rightItem's validation logic
- Preserves the Either structure (Left stays Left, Right stays Right)
- Provides context-aware error messages indicating which branch failed
- Transforms values through the respective codecs during encoding
Example:
// Create a codec for Either[string, int]
stringCodec := codec.String()
intCodec := codec.Int()
eitherCodec := codec.TranscodeEither(stringCodec, intCodec)
// Decode Left value
leftResult := eitherCodec.Decode(either.Left[int]("error"))
// Success: Right(Either.Left("error"))
// Decode Right value
rightResult := eitherCodec.Decode(either.Right[string](42))
// Success: Right(Either.Right(42))
// Encode Left value
encodedLeft := eitherCodec.Encode(either.Left[int]("error"))
// Returns: Either.Left("error")
// Encode Right value
encodedRight := eitherCodec.Encode(either.Right[string](42))
// Returns: Either.Right(42)
Use TranscodeEither when:
- You need to handle sum types or discriminated unions
- You want to validate and transform both branches of an Either independently
- You're working with error handling patterns (Left for errors, Right for success)
- You need type-safe transformations for both possible values
Common patterns:
- Error handling: Either[Error, Value]
- Optional with reason: Either[Reason, Value]
- Validation results: Either[ValidationError, ValidatedData]
func URL ¶ added in v2.2.1
URL creates a bidirectional codec for URL parsing and formatting. This codec can parse strings into *url.URL and encode *url.URL back to strings.
The codec:
- Decodes: Parses a string using url.Parse, validating URL syntax
- Encodes: Converts a *url.URL to its string representation using String()
- Validates: Ensures the input string is a valid URL format
Returns:
- A Type[*url.URL, string, string] codec that handles URL transformations
Example:
urlCodec := URL()
// Decode a string to URL
validation := urlCodec.Decode("https://example.com/path?query=value")
// validation is Right(*url.URL{...})
// Encode a URL to string
u, _ := url.Parse("https://example.com")
str := urlCodec.Encode(u)
// str is "https://example.com"
// Invalid URL fails validation
validation := urlCodec.Decode("not a valid url")
// validation is Left(ValidationError{...})
type Validate ¶
Validate is a function that validates input I to produce type A. It takes an input and returns a Reader that depends on the validation Context.
The Validate type is the core validation abstraction, defined as:
Reader[I, Decode[Context, 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
Example:
A Validate[string, int] takes a string and returns a context-aware function that validates and converts it to an integer.
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.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package validate provides functional validation primitives for building composable validators.
|
Package validate provides functional validation primitives for building composable validators. |
|
Package validation provides functional validation types and operations for the codec system.
|
Package validation provides functional validation types and operations for the codec system. |