Documentation
¶
Overview ¶
Package validate provides a unified validation framework for types that implement validation interfaces. It supports both context-aware and context-free validation patterns, allowing callers to validate arbitrary values in a type-safe manner without knowing their specific validation requirements.
Index ¶
- func Validate(ctx context.Context, value any) error
- func WantProblemErrors(ctx context.Context) bool
- func WithWantProblemErrors(ctx context.Context, wantProblemErrors bool) context.Context
- func WithWrappedError(ctx context.Context, wantWrapped bool) context.Context
- type HasValidate
- type HasValidateWithContext
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Validate ¶
Validate performs validation on a value by checking if it implements either HasValidate or HasValidateWithContext. If the value implements HasValidateWithContext, it calls Validate(ctx) with the provided context. If the value implements HasValidate, it calls Validate() without context. If the value implements neither interface or is nil, validation succeeds.
The function automatically wraps any validation errors with errors.ErrValidation, making it easy to identify validation failures using errors.Is(err, errors.ErrValidation).
Parameters:
- ctx: The context to pass to context-aware validators. Used for cancellation, deadlines, and carrying values.
- value: The value to validate. Can be any type including nil.
Returns:
- nil if validation succeeds or the value doesn't implement a validation interface
- An error wrapped with errors.ErrValidation if validation fails
Example:
type Config struct {
Port int
}
func (c Config) Validate() error {
if c.Port <= 0 {
return fmt.Errorf("port must be positive")
}
return nil
}
cfg := Config{Port: -1}
if err := validate.Validate(ctx, cfg); err != nil {
// err will be wrapped with errors.ErrValidation
log.Fatal(err)
}
Example ¶
ctx := context.Background()
cfg := exampleConfig{Port: 8080}
err := Validate(ctx, cfg)
if err != nil {
fmt.Println("validation failed:", err)
return
}
fmt.Println("validation succeeded")
Output: validation succeeded
Example (WithContext) ¶
ctx := context.Background()
req := exampleRequest{UserID: "user-123"}
err := Validate(ctx, req)
if err != nil {
fmt.Println("validation failed:", err)
return
}
fmt.Println("validation succeeded")
Output: validation succeeded
func WantProblemErrors ¶
WantProblemErrors retrieves the problem errors preference from the context. Returns true if the caller wants validation errors formatted as RFC-7807/RFC-9457 Problem Details, false otherwise.
If the preference has not been explicitly set via WithWantProblemErrors, this function returns false (the default), indicating that plain Go errors should be used.
This function is typically called from within a type's Validate() implementation to determine the appropriate error format for the current execution context.
Example:
type CreateUserRequest struct {
Email string
}
func (r CreateUserRequest) Validate(ctx context.Context) error {
if r.Email == "" {
if validate.WantProblemErrors(ctx) {
// Format as RFC-7807/RFC-9457 problem detail
return problem.BadRequest(ctx, problem.Detail("email is required"))
}
// Return plain error
return fmt.Errorf("email is required")
}
return nil
}
func WithWantProblemErrors ¶
WithWantProblemErrors returns a new context with the problem errors preference set. This configuration flag controls whether validation errors should be formatted as RFC-7807/RFC-9457 Problem Details for HTTP responses.
When wantProblemErrors is true:
- Validation errors should be wrapped with the problem package
- HTTP handlers can return structured JSON responses with status codes, details, and remediation
- Error responses follow the RFC-7807/RFC-9457 standard format
When wantProblemErrors is false (default):
- Validation errors are returned as plain Go errors
- Suitable for non-HTTP contexts or when simple error messages are preferred
This flag is typically set at the HTTP handler level and flows down through the call stack via context propagation, allowing validation logic to remain agnostic of the transport layer while still producing appropriate error formats for the caller.
Example:
func HandleCreateUser(c *fiber.Ctx) error {
ctx := validate.WithWantProblemErrors(c.Context(), true)
if err := validate.Validate(ctx, req); err != nil {
// err can be formatted as a problem.Problem for HTTP response
return problem.FromError(err)
}
// ... handle request
}
Example ¶
Example tests.
ctx := context.Background()
// Enable problem error formatting for HTTP handlers
ctx = WithWantProblemErrors(ctx, true)
// Check if the caller wants problem-formatted errors
if WantProblemErrors(ctx) {
fmt.Println("problem errors enabled")
} else {
fmt.Println("problem errors disabled")
}
Output: problem errors enabled
Example (Default) ¶
ctx := context.Background()
// By default, problem errors are disabled
if WantProblemErrors(ctx) {
fmt.Println("problem errors enabled")
} else {
fmt.Println("problem errors disabled")
}
Output: problem errors disabled
func WithWrappedError ¶
WithWrappedError returns a new context with the wrapped errors preference set. This configuration flag controls whether validation errors should be wrapped with additional context information using fmt.Errorf with %w format verb.
When wantWrapped is true (the default if not explicitly set):
- Validation errors should be wrapped with contextual information
- Error chains provide detailed traces for debugging
- Stack traces and error context flow through the call hierarchy
- Suitable for most use cases where debuggability is important
When wantWrapped is false:
- Validation errors are returned directly without additional wrapping
- Reduces allocation overhead in performance-critical paths
- Useful when error messages are already descriptive enough
- Simplifies error handling when deep error chains aren't needed
This flag is typically set at the service or handler level and propagates through the call stack via context, allowing low-level validation code to adapt its error handling strategy based on the caller's requirements.
Example:
func ValidateAtScale(ctx context.Context, items []Item) error {
// Disable wrapping for performance in high-throughput validation
ctx = validate.WithWrappedError(ctx, false)
for _, item := range items {
if err := item.Validate(ctx); err != nil {
return err // No wrapping overhead
}
}
return nil
}
Example ¶
ctx := context.Background()
// Disable error wrapping for performance-critical code
ctx = WithWrappedError(ctx, false)
// Perform validation - errors won't be wrapped with ErrValidation
invalidType := exampleValidType{Value: ""}
err := Validate(ctx, invalidType)
if err != nil {
fmt.Println("validation failed:", err)
}
Output: validation failed: value is required
Example (Enabled) ¶
ctx := context.Background()
// Enable error wrapping (this is the default)
ctx = WithWrappedError(ctx, true)
// Perform validation - errors will be wrapped with ErrValidation
invalidType := exampleValidType{Value: ""}
err := Validate(ctx, invalidType)
if err != nil {
// Error is wrapped, so it contains ErrValidation
fmt.Println("validation failed")
fmt.Println("is validation error:", errors.Is(err, commonErrors.ErrValidation))
}
Output: validation failed is validation error: true
Types ¶
type HasValidate ¶
type HasValidate interface {
// Validate checks the validity of the implementing type and returns an error if validation fails.
// This method should be idempotent and safe to call multiple times.
Validate() error
}
HasValidate defines the interface for types that can validate themselves without requiring a context. Types implementing this interface should return an error if validation fails, or nil if the value is valid. This is the simpler validation interface for types that don't need contextual information during validation.
func Func ¶
func Func(f func() error) HasValidate
Func wraps a validation function into a type that implements the HasValidate interface. This is useful when you have validation logic defined as a function and need to pass it to code that expects a HasValidate implementation.
The wrapped function will be called when Validate() is invoked on the returned type. If the provided function is nil, Validate() will return nil (validation succeeds).
Parameters:
- f: The validation function to wrap. Can be nil, in which case validation always succeeds.
Returns:
- A HasValidate implementation that delegates to the provided function
Example:
// Define validation logic as a function
validatePort := func() error {
if port < 1 || port > 65535 {
return fmt.Errorf("port %d is out of range", port)
}
return nil
}
// Wrap it to satisfy HasValidate interface
validator := validate.Func(validatePort)
// Now it can be used with validate.Validate
if err := validate.Validate(ctx, validator); err != nil {
log.Fatal(err)
}
type HasValidateWithContext ¶
type HasValidateWithContext interface {
// Validate checks the validity of the implementing type using the provided context and returns an error
// if validation fails. The context can be used for cancellation, timeout handling, or passing
// request-scoped values. This method should respect context cancellation and return promptly if
// ctx.Done() is signaled.
Validate(ctx context.Context) error
}
HasValidateWithContext defines the interface for types that require a context during validation. This interface is useful when validation needs to access external resources, respect cancellation, or requires deadline/timeout handling. Types implementing this interface should return an error if validation fails, or nil if the value is valid.
func FuncWithContext ¶
func FuncWithContext(f func(ctx context.Context) error) HasValidateWithContext
FuncWithContext wraps a context-aware validation function into a type that implements the HasValidateWithContext interface. This is useful when you have validation logic that requires a context (for cancellation, timeouts, or accessing external resources) and need to pass it to code that expects a HasValidateWithContext implementation.
The wrapped function will be called when Validate(ctx) is invoked on the returned type. If the provided function is nil, Validate() will return nil (validation succeeds).
Parameters:
- f: The context-aware validation function to wrap. Can be nil, in which case validation always succeeds.
Returns:
- A HasValidateWithContext implementation that delegates to the provided function
Example:
// Define validation logic that needs context
validateConnection := func(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
conn, err := db.PingContext(ctx)
if err != nil {
return fmt.Errorf("database connection failed: %w", err)
}
conn.Close()
return nil
}
// Wrap it to satisfy HasValidateWithContext interface
validator := validate.FuncWithContext(validateConnection)
// Now it can be used with validate.Validate
if err := validate.Validate(ctx, validator); err != nil {
log.Fatal(err)
}