conform

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2025 License: MIT Imports: 15 Imported by: 0

README ΒΆ

Conform

Go Version License GitHub CI/CD Go Reference

The Pydantic for Go

Type-safe configuration loading, validation, and management in one elegant package.

Features β€’ Quick Start β€’ Documentation β€’ Examples

Go Report Card GoDoc


🎯 Why Conform?

Stop juggling multiple libraries. Conform unifies configuration loading, type conversion, and validation into a single, declarative interface.

Key Benefits
  • ⚑ Zero Boilerplate - Declare everything in struct tags, no manual parsing or validation code
  • πŸ”’ Type Safety - Full generics support ensures compile-time type checking
  • πŸš€ Production Ready - Built-in support for environment-specific configs, hot reload, and variable substitution
  • πŸ’‘ Developer Experience - Beautiful, actionable error messages that tell you exactly what's wrong
  • 🎨 Flexible - Support for multiple sources, custom validators, and converters
  • πŸ“¦ Lightweight - Minimal dependencies, fast performance
Before Conform ❌
// Load config
viper.SetConfigFile("config.yaml")
viper.ReadInConfig()

// Unmarshal
var cfg Config
viper.Unmarshal(&cfg)

// Validate
validate := validator.New()
if err := validate.Struct(cfg); err != nil {
    // Parse errors...
}

// Type conversion? Manual!
port, _ := strconv.Atoi(viper.GetString("port"))
timeout, _ := time.ParseDuration(viper.GetString("timeout"))
With Conform βœ…
type Config struct {
    Port     int           `conform:"env=PORT,default=8080,validate=gte:1024"`
    Timeout  time.Duration `conform:"env=TIMEOUT,default=30s"`
    Database string        `conform:"env=DB_URL,required,validate=url"`
}

cfg, err := conform.LoadGeneric[Config](conform.FromEnv())
// ✨ Done! Type-safe, validated, ready to use.

One struct tag. One function call. Zero boilerplate.

Perfect For
  • 🏒 Microservices - Type-safe configuration across services
  • πŸš€ Cloud-Native Apps - Environment-specific configs for Kubernetes, Docker
  • πŸ”§ CLI Tools - Easy configuration management
  • πŸ“Š APIs & Web Services - Fast, validated config loading
  • πŸ§ͺ Testing - Mock-friendly configuration loading

✨ Features

🎯 Core Features
Feature Description
🏷️ Declarative Configuration Everything in struct tags, zero boilerplate
πŸš€ Type-Safe Generics Full type safety with Go 1.21+ generics
πŸ”„ Multi-Source Support Environment variables, files (YAML/JSON/TOML), custom sources
βœ… Built-in Validation 20+ validators out of the box
πŸ”§ Smart Type Coercion Automatic conversion for complex types
πŸ“¦ Nested Structs Full support with automatic prefix handling
πŸ”₯ Hot Reload Watch for changes and reload automatically
πŸ’¬ Beautiful Errors Detailed error messages with suggestions
🌍 Environment-Specific Load different configs for dev/staging/prod
πŸ” Variable Substitution ${VAR_NAME:-default} syntax support
🎨 What Makes Conform Different?
Feature Conform Viper envconfig koanf
Type Safety βœ… Generics ❌ ❌ ❌
Validation βœ… Built-in ❌ Requires validator ❌ ⚠️ Basic
Error Messages βœ… Beautiful ❌ ❌ ⚠️ Basic
Hot Reload βœ… Built-in βœ… WatchConfig ❌ βœ…
Environment-Specific βœ… Built-in ⚠️ Manual ❌ βœ…
Variable Substitution βœ… Built-in ❌ ❌ βœ…
Declarative βœ… 100% ⚠️ Partial βœ… βœ…
Zero Boilerplate βœ… ❌ ⚠️ ⚠️

Note:

  • Viper requires separate validation library (e.g., go-playground/validator)
  • envconfig is minimal and focused only on environment variables
  • koanf is a modern alternative but lacks generics and built-in validation

πŸ“¦ Installation

Go Module
go get github.com/alicanli1995/conform
Import
import "github.com/alicanli1995/conform"
Requirements
  • Go 1.21 or higher
  • No external dependencies required (except for file format support: YAML, TOML)

πŸš€ Quick Start

Basic Example

Define your configuration struct with conform tags:

package main

import (
    "fmt"
    "os"
    
    "github.com/alicanli1995/conform"
)

type Config struct {
    Port     int    `conform:"env=APP_PORT,default=8080,validate=gte:1024"`
    Host     string `conform:"env=APP_HOST,default=localhost,validate=hostname"`
    Database string `conform:"env=DATABASE_URL,required,validate=url"`
}

func main() {
    os.Setenv("APP_PORT", "3000")
    os.Setenv("DATABASE_URL", "postgres://localhost/mydb")
    
    cfg, err := conform.LoadGeneric[Config](conform.FromEnv())
    if err != nil {
        panic(err) // Beautiful error messages!
    }
    
    fmt.Printf("Server: %s:%d\n", cfg.Host, cfg.Port)
}

That's it! Your configuration is loaded, validated, and ready to use. πŸŽ‰

What Just Happened?
  1. βœ… Loaded from environment variables
  2. βœ… Converted types automatically (string β†’ int)
  3. βœ… Validated against rules (gte:1024, hostname, url)
  4. βœ… Applied defaults where needed
  5. βœ… Returned type-safe config struct
Try It Yourself
# Run the example
cd examples/basic && go run main.go

πŸ“– Documentation

Multi-Source Support

Load from multiple sources with automatic priority (first source wins):

cfg, err := conform.LoadGeneric[Config](
    conform.FromEnv(),                    // Highest priority
    conform.FromFile("secrets.json"),     // Second priority
    conform.FromFile("config.yaml"),      // Third priority
    conform.WithSource(&CustomSource{}),  // Custom source
)
// Priority: env > custom sources > file sources > defaults
Environment-Specific Configuration

Perfect for dev/staging/production environments:

type Config struct {
    Database struct {
        Host string `conform:"file=database.host,default=localhost"`
        Port int    `conform:"file=database.port,default=5432"`
    }
}

// Development
devCfg, _ := conform.LoadGeneric[Config](
    conform.WithEnvironment("development"),
    conform.FromFile("config.${ENV}.yaml"), // Loads config.development.yaml
)

// Production
prodCfg, _ := conform.LoadGeneric[Config](
    conform.WithEnvironment("production"),
    conform.FromFile("config.${ENV}.yaml"), // Loads config.production.yaml
)
Variable Substitution

Use ${VAR_NAME:-default} syntax in config values:

type Config struct {
    DatabaseURL string `conform:"env=DB_URL,default=postgres://${DB_USER:-postgres}:${DB_PASSWORD}@${DB_HOST:-localhost}:${DB_PORT:-5432}/${DB_NAME:-mydb}"`
    APIURL      string `conform:"env=API_URL,default=https://api.${ENV:-dev}.example.com"`
}
Smart Type Coercion

Automatic conversion for complex types:

type Config struct {
    // String "true" β†’ bool true
    Debug bool `conform:"env=DEBUG"`
    
    // String "30s" β†’ time.Duration
    Timeout time.Duration `conform:"env=TIMEOUT"`
    
    // String "1,2,3" β†’ []int{1,2,3}
    IDs []int `conform:"env=IDS,separator=,"`
    
    // String "1=one,2=two" β†’ map[int]string{1:"one", 2:"two"}
    Mapping map[int]string `conform:"env=MAPPING"`
    
    // String "2024-01-01" β†’ time.Time
    StartDate time.Time `conform:"env=START,format=2006-01-02"`
}
Beautiful Error Messages

Get detailed, actionable error messages:

cfg, err := conform.LoadGeneric[Config](conform.FromEnv())
if err != nil {
    fmt.Println(err)
    // Output:
    // ❌ Configuration validation failed:
    //
    // 1. Port (APP_PORT): value 80 is too small
    //    Got: 80
    //    Location: env var APP_PORT
    //    πŸ’‘ Suggestion: Use a value >= 1024 (e.g. 8080)
    //
    // 2. Database.URL (DB_URL): invalid URL format
    //    Got: "not-a-url"
    //    Expected: valid URL with scheme
    //    πŸ’‘ Suggestion: Format should be: https://example.com
}
Hot Reload

Watch for configuration changes automatically:

watcher, err := conform.Watch[Config](func(newCfg Config) {
    log.Printf("Config reloaded: %+v", newCfg)
    // Update your application state here
}, conform.FromEnv(), conform.FromFile("config.yaml"))

// Thread-safe access
cfg := watcher.Get()

// Stop watching
defer watcher.Stop()
Custom Validators

Register your own validation rules:

conform.RegisterValidator("strong_password", func(val interface{}, params []string) error {
    str := val.(string)
    if len(str) < 12 {
        return fmt.Errorf("password must be at least 12 characters")
    }
    if !hasSpecialChar(str) {
        return fmt.Errorf("password must contain special character")
    }
    return nil
})

type Config struct {
    Password string `conform:"env=PASSWORD,validate=strong_password"`
}
Custom Converters

Convert to custom types:

type CustomType string

conform.RegisterConverter(
    reflect.TypeOf(CustomType("")),
    func(s string) (interface{}, error) {
        return CustomType("custom_" + s), nil
    },
)

type Config struct {
    Custom CustomType `conform:"env=CUSTOM"`
}
Nested Configuration

Full support for nested structs with automatic prefix handling:

type DatabaseConfig struct {
    Host string `conform:"env=HOST,default=localhost"`
    Port int    `conform:"env=PORT,default=5432"`
}

type AppConfig struct {
    Name     string         `conform:"env=APP_NAME,default=MyApp"`
    Database DatabaseConfig `conform:"prefix=DB_"`
}

// Environment variables:
// DB_HOST=db.example.com
// DB_PORT=5432
File Configuration

Support for YAML, JSON, and TOML:

type Config struct {
    Server struct {
        Host string `conform:"file=server.host,default=localhost"`
        Port int    `conform:"file=server.port,default=8080"`
    }
}

// YAML
cfg, _ := conform.LoadGeneric[Config](conform.FromFile("config.yaml"))

// TOML
cfg, _ := conform.LoadGeneric[Config](conform.FromFile("config.toml"))

// JSON
cfg, _ := conform.LoadGeneric[Config](conform.FromFile("config.json"))

πŸ“š Tag Reference

Source Tags
Tag Description Example
env=VAR_NAME Load from environment variable env=APP_PORT
file=key.path Load from config file (dot notation) file=database.host
default=value Default value if not found default=8080
required Field is required (error if missing) required
prefix=PREFIX_ Prefix for nested structs prefix=DB_
Type Conversion Tags
Tag Description Example
format=layout Format for time.Time format=2006-01-02
separator=, Separator for slices separator=|
Validation Tags
Tag Description Example
validate=rule:param Validation rules validate=gte:1024,lte:65535

βœ… Built-in Validators

Numeric Validators
  • min:value - Minimum value/length
  • max:value - Maximum value/length
  • gte:value - Greater than or equal
  • lte:value - Less than or equal
  • eq:value - Equal to
  • ne:value - Not equal to
String Validators
  • email - Valid email address
  • url - Valid URL (use url:https for HTTPS only)
  • ip - Valid IP address (IPv4 or IPv6)
  • hostname - Valid hostname
  • port - Valid port number (1-65535)
  • alphanum - Only letters and digits
  • alpha - Only letters
  • numeric - Only digits
  • regex:pattern - Match regex pattern
  • oneof:val1:val2 - One of the specified values
  • len:length - Exact length
Password Validators
  • has_upper - Contains uppercase letter
  • has_lower - Contains lowercase letter
  • has_digit - Contains digit
  • has_special - Contains special character
General
  • required - Field is required

πŸ”§ CLI Tool

Note: CLI tool is currently in development. For now, use the programmatic API for validation.

Validate configuration files programmatically:

cfg, err := conform.LoadGeneric[Config](
    conform.FromFile("config.yaml"),
    conform.FromEnv(),
)
if err != nil {
    fmt.Println(err) // Beautiful error messages
}

πŸ“š Examples

Comprehensive examples available in the examples directory:

Example Description Link
πŸš€ Basic Usage Getting started with Conform View
🎯 Generic API Type-safe loading with generics View
πŸ“„ File Configuration YAML, JSON, TOML examples View
🌍 Environment-Specific Dev/staging/prod configs View
⚑ Advanced Features Complex scenarios View
πŸ”§ Custom Extensions Custom converters & validators View
πŸ”₯ Hot Reload Dynamic configuration View
πŸ’¬ Error Handling Beautiful error messages View
🏒 Real-World Production-ready example View
Run All Examples
make run-examples

πŸ—ΊοΈ Roadmap

Upcoming Features
  • πŸ” Secret Management - HashiCorp Vault, AWS Secrets Manager, Azure Key Vault integration
  • πŸ”„ Remote Configuration - etcd integration for distributed config management
  • πŸ“Š JSON Schema Validation - External schema validation support
  • πŸ“ Config Documentation - Auto-generate config docs from struct definitions
  • πŸ” Config Diff/Compare - Track and compare configuration changes
  • πŸ“ˆ Metrics & Observability - Prometheus metrics for config usage monitoring

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


Made with ❀️ for the Go community

⭐ Star us on GitHub β€’ πŸ“¦ pkg.go.dev β€’ πŸ“– Documentation β€’ πŸ’¬ Issues β€’ πŸ› Report Bug

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func Load ΒΆ

func Load(target interface{}, opts ...Option) error

Load loads configuration into the target struct

func LoadGeneric ΒΆ

func LoadGeneric[T any](opts ...Option) (*T, error)

LoadGeneric loads configuration using generics

func RegisterConverter ΒΆ

func RegisterConverter(t reflect.Type, fn convert.ConvertFunc)

RegisterConverter registers a global converter

func RegisterValidator ΒΆ

func RegisterValidator(name string, fn func(value interface{}, params []string) error)

RegisterValidator registers a global validator

func TestLoadGeneric_EnvironmentSpecific ΒΆ

func TestLoadGeneric_EnvironmentSpecific(t *testing.T)

func TestLoadGeneric_VariableSubstitution ΒΆ

func TestLoadGeneric_VariableSubstitution(t *testing.T)

func TestLoadGeneric_VariableSubstitutionWithDefaults ΒΆ

func TestLoadGeneric_VariableSubstitutionWithDefaults(t *testing.T)

Types ΒΆ

type Config ΒΆ

type Config struct {
	Source      source.Source
	FileLoader  *loader.FileLoader
	Converter   *convert.Converter
	Validator   *validate.Validator
	FileSources []source.Source
	Sources     []source.Source
	Environment string
	Variables   map[string]string
}

Config holds the configuration for loading

type ErrorList ΒΆ

type ErrorList struct {
	Errors []FieldError
}

ErrorList collects multiple field errors

func (*ErrorList) Error ΒΆ

func (e *ErrorList) Error() string

type FieldError ΒΆ

type FieldError struct {
	Field      string
	FieldPath  string
	Value      interface{}
	Message    string
	Key        string
	Location   string
	Suggestion string
}

FieldError represents an error for a specific field

type Option ΒΆ

type Option func(*Config)

Option is a configuration option

func FromConsul ΒΆ

func FromConsul(path string) Option

FromConsul creates an option that loads from Consul

func FromEnv ΒΆ

func FromEnv() Option

FromEnv creates an option that loads from environment variables

func FromFile ΒΆ

func FromFile(filePath string) Option

FromFile creates an option that loads from a file

func WithConverter ΒΆ

func WithConverter(conv *convert.Converter) Option

WithConverter sets a custom converter

func WithEnvironment ΒΆ

func WithEnvironment(env string) Option

WithEnvironment sets the environment name for environment-specific configs

func WithFile ΒΆ

func WithFile(filePath string) Option

func WithFileLoader ΒΆ

func WithFileLoader(fileLoader *loader.FileLoader) Option

WithFileLoader sets a file loader

func WithSource ΒΆ

func WithSource(s source.Source) Option

WithSource sets a custom source

func WithValidator ΒΆ

func WithValidator(v *validate.Validator) Option

WithValidator sets a custom validator

func WithVariables ΒΆ

func WithVariables(vars map[string]string) Option

WithVariables sets custom variables for substitution in config values

type Watcher ΒΆ

type Watcher[T any] struct {
	// contains filtered or unexported fields
}

func Watch ΒΆ

func Watch[T any](onReload func(T), opts ...Option) (*Watcher[T], error)

func (*Watcher[T]) Get ΒΆ

func (w *Watcher[T]) Get() T

func (*Watcher[T]) Stop ΒΆ

func (w *Watcher[T]) Stop()

Directories ΒΆ

Path Synopsis
cmd
examples
advanced command
basic command
custom command
customvalidator command
environment command
errors command
fileconfig command
generic command
hotreload command
realworld command

Jump to

Keyboard shortcuts

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