Config Validator
Validates a configuration struct and returns wrapped custom errors. Demonstrates errors.Is, errors.As, and error chaining to inspect the full error chain.
package main
import (
"errors"
"fmt"
"strings"
)
var (
ErrRequired = errors.New("field is required")
ErrOutOfRange = errors.New("value out of range")
)
type ValidationError struct {
Field string
Value any
Message string
Err error
}
func (v *ValidationError) Error() string {
return fmt.Sprintf("validation failed for %q (value: %v): %s", v.Field, v.Value, v.Message)
}
func (v *ValidationError) Unwrap() error {
return v.Err
}
type Config struct {
Host string
Port int
Workers int
LogLevel string
}
func validateConfig(cfg Config) error {
var errs []error
if strings.TrimSpace(cfg.Host) == "" {
errs = append(errs, &ValidationError{
Field: "Host", Value: cfg.Host,
Message: "host cannot be empty",
Err: ErrRequired,
})
}
if cfg.Port < 1 || cfg.Port > 65535 {
errs = append(errs, &ValidationError{
Field: "Port", Value: cfg.Port,
Message: "must be between 1 and 65535",
Err: ErrOutOfRange,
})
}
if cfg.Workers < 1 || cfg.Workers > 128 {
errs = append(errs, &ValidationError{
Field: "Workers", Value: cfg.Workers,
Message: "must be between 1 and 128",
Err: ErrOutOfRange,
})
}
validLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true}
if !validLevels[strings.ToLower(cfg.LogLevel)] {
errs = append(errs, &ValidationError{
Field: "LogLevel", Value: cfg.LogLevel,
Message: "must be one of: debug, info, warn, error",
Err: ErrRequired,
})
}
if len(errs) > 0 {
return fmt.Errorf("config validation failed: %w", errors.Join(errs...))
}
return nil
}
func main() {
configs := []Config{
{Host: "localhost", Port: 8080, Workers: 4, LogLevel: "info"},
{Host: "", Port: 99999, Workers: 0, LogLevel: "verbose"},
{Host: "prod.example.com", Port: 443, Workers: 256, LogLevel: "warn"},
}
for i, cfg := range configs {
fmt.Printf("Config #%d: %+v\n", i+1, cfg)
err := validateConfig(cfg)
if err == nil {
fmt.Println(" Status: VALID\n")
continue
}
fmt.Printf(" Status: INVALID\n")
fmt.Printf(" Error: %v\n", err)
// Use errors.Is to check for specific sentinel errors
if errors.Is(err, ErrRequired) {
fmt.Println(" Contains: missing required field(s)")
}
if errors.Is(err, ErrOutOfRange) {
fmt.Println(" Contains: out-of-range value(s)")
}
// Use errors.As to extract the ValidationError details
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf(" First failing field: %s\n", valErr.Field)
}
fmt.Println()
}
}