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()
	}
}
▶ Open Go Playground

Copy the code above and paste to run

© 2026 ByteLearn.dev. Free courses for developers. · Privacy