v0.1.0 Release

This commit is contained in:
2025-11-08 07:16:48 -05:00
parent a66b684330
commit 00193cf096
38 changed files with 1167 additions and 802 deletions

View File

@ -398,10 +398,4 @@ cfg.Dump() // Writes to stdout
// Create isolated copy for testing
testCfg := cfg.Clone()
testCfg.Set("server.port", int64(0)) // Random port for tests
```
## See Also
- [Live Reconfiguration](reconfiguration.md) - Reacting to changes
- [Builder Pattern](builder.md) - Type-aware configuration
- [Environment Variables](env.md) - Environment value access
```

174
doc/architecture.md Normal file
View File

@ -0,0 +1,174 @@
# Config Package Architecture
## Overview
The `lixenwraith/config` package provides thread-safe configuration management with support for multiple sources, type-safe struct population, and live reconfiguration.
## Logical Architecture
```
┌──────────────────────────────────────────────────────────┐
│ User API Layer │
├────────────────┬────────────────┬────────────────────────┤
│ Builder │ Type-Safe │ Dynamic Access │
│ Pattern │ Struct API │ Key-Value API │
├────────────────┴────────────────┴────────────────────────┤
│ Core Config Engine │
│ • Path Registration • Source Merging • Thread Safety │
├──────────────────────────────────────────────────────────┤
│ Source Loaders │
│ File │ Environment │ CLI Arguments │ Defaults │
├──────────────────────────────────────────────────────────┤
│ Supporting Systems │
│ Validation │ Type Decode │ File Watch │ Error Handling │
└──────────────────────────────────────────────────────────┘
```
## Component Interactions
```
Builder Flow:
NewBuilder()
Configure (WithTarget, WithFile, WithEnvPrefix)
Build() → Register Paths → Load Sources → Merge Values
Config Instance
AsStruct() / Get() / Watch()
Value Resolution:
Path Request
Check Registration
Search Sources (CLI → Env → File → Default)
Type Conversion
Return Value
Live Reload:
File Change Detected
Debounce Timer
Reload File
Merge with Sources
Notify Watchers
```
## File Organization
### Core (`config.go`)
- **Config struct**: Thread-safe state management with atomic versioning
- **Initialization**: `New()`, `NewWithOptions()`
- **State Management**: `Get()`, `Set()`, `GetSource()`, `SetSource()`
- **Precedence Control**: `SetPrecedence()`, `GetPrecedence()`, `computeValue()`
- **Cache Management**: `AsStruct()`, `populateStruct()`, `invalidateCache()`
- **Source Operations**: `Reset()`, `ResetSource()`, `GetSources()`
### Registration (`register.go`)
- **Path Registration**: `Register()`, `RegisterWithEnv()`, `RegisterRequired()`, `Unregister()`
- **Struct Registration**: `RegisterStruct()`, `RegisterStructWithTags()`, `registerFields()`
- **Discovery**: `GetRegisteredPaths()`, `GetRegisteredPathsWithDefaults()`
- **Decoding Bridge**: `Scan()`, `ScanSource()` - delegates to decode.go
### Builder Pattern (`builder.go`)
- **Fluent API**: `NewBuilder()`, `Build()`, `MustBuild()`
- **Configuration**: `WithTarget()`, `WithDefaults()`, `WithFile()`, `WithEnvPrefix()`
- **Discovery Integration**: `WithFileDiscovery()`
- **Validation**: `WithValidator()`, `WithTypedValidator()`
- **Security**: `WithSecurityOptions()`
### Source Loading (`loader.go`)
- **Multi-Source Loading**: `Load()`, `LoadWithOptions()`
- **Individual Sources**: `LoadFile()`, `LoadEnv()`, `LoadCLI()`
- **Persistence**: `Save()`, `SaveSource()`, `atomicWriteFile()`
- **Environment Mapping**: `DiscoverEnv()`, `ExportEnv()`, `defaultEnvTransform()`
- **Parsing**: `parseArgs()`, `parseValue()`, `detectFileFormat()`
- **Security Checks**: Path traversal, file ownership, size limits
### Type System (`decode.go`)
- **Unified Decoding**: `unmarshal()` - single authoritative decoder
- **Hook Composition**: `getDecodeHook()`, `customDecodeHook()`
- **Type Converters**: `jsonNumberHookFunc()`, `stringToNetIPHookFunc()`, `stringToURLHookFunc()`
- **Navigation**: `navigateToPath()`, `normalizeMap()`
### File Watching (`watch.go`)
- **Auto-Reload**: `AutoUpdate()`, `AutoUpdateWithOptions()`, `StopAutoUpdate()`
- **Subscriptions**: `Watch()`, `WatchWithOptions()`, `WatchFile()`
- **Watcher State**: `watcher` struct with debouncing, polling, version tracking
- **Change Detection**: `checkAndReload()`, `performReload()`, `notifyWatchers()`
- **Resource Management**: Max watcher limits, graceful shutdown
### Convenience API (`convenience.go`)
- **Quick Setup**: `Quick()`, `QuickCustom()`, `MustQuick()`, `QuickTyped()`
- **Flag Integration**: `GenerateFlags()`, `BindFlags()`
- **Type-Safe Access**: `GetTyped()`, `GetTypedWithDefault()`, `ScanTyped()`
- **Utilities**: `Validate()`, `Debug()`, `Dump()`, `Clone()`
### File Discovery (`discovery.go`)
- **Options**: `FileDiscoveryOptions` struct with search strategies
- **XDG Compliance**: `getXDGConfigPaths()` for standard config locations
- **Defaults**: `DefaultDiscoveryOptions()` with sensible patterns
### Validation Library (`validator.go`)
- **Network**: `Port()`, `IPAddress()`, `IPv4Address()`, `IPv6Address()`
- **Numeric**: `Positive()`, `NonNegative()`, `Range()`
- **String**: `NonEmpty()`, `Pattern()`, `OneOf()`, `URLPath()`
### Error System (`error.go`)
- **Categories**: Sentinel errors for `errors.Is()` checking
- **Wrapping**: `wrapError()` maintains dual error chains
### Internal Utilities (`helper.go`)
- **Map Operations**: `flattenMap()`, `setNestedValue()`
- **Validation**: `isValidKeySegment()` for path validation
### Constants (`constant.go`)
- **Shared Constants**: Defines shared constants for timing, formats, limits, file watcher and discovery.
## Data Flow Patterns
### Configuration Loading
1. **Registration Phase**: Paths registered with types/defaults
2. **Source Collection**: Each source populates its layer
3. **Merge Phase**: Precedence determines final values
4. **Population Phase**: Struct populated via reflection
### Thread Safety Model
- **Read Operations**: Multiple concurrent readers via RLock
- **Write Operations**: Exclusive access via Lock
- **Atomic Updates**: Prepare → Lock → Swap → Unlock pattern
- **Version Tracking**: Atomic counter for cache invalidation
### Error Propagation
- **Wrapped Errors**: Category + specific error detail
- **Early Return**: Builder accumulates first error
- **Panic Mode**: MustBuild for fail-fast scenarios
## Extension Points
### Custom Types
Implement decode hooks in `customDecodeHook()`:
```go
func(f reflect.Type, t reflect.Type, data any) (any, error)
```
### Source Transformation
Environment variable mapping via `WithEnvTransform()`:
```go
func(path string) string // Return env var name
```
### Validation Layers
- Pre-decode: `WithValidator(func(*Config) error)`
- Post-decode: `WithTypedValidator(func(*YourType) error)`
### File Discovery
Search strategy via `WithFileDiscovery()`:
- CLI flag check → Env var → XDG paths → Current dir

View File

@ -370,9 +370,4 @@ if err != nil {
}
```
For panic on error use `MustBuild()`
## See Also
- [Environment Variables](env.md) - Environment configuration details
- [Live Reconfiguration](reconfiguration.md) - File watching with builder
For panic on error use `MustBuild()`

View File

@ -185,9 +185,4 @@ if err != nil {
log.Fatal("Configuration error:", err)
}
}
```
## See Also
- [Environment Variables](env.md) - Environment variable handling
- [Access Patterns](access.md) - Retrieving parsed values
```

157
doc/config-llm-guide.md Normal file
View File

@ -0,0 +1,157 @@
# lixenwraith/config LLM Usage Guide
This guide details the `lixenwraith/config` package for thread-safe Go configuration. It supports multiple sources (files, environment, CLI), type-safe struct population, and live reconfiguration.
## Quick Start: Recommended Usage
The recommended pattern uses the **Builder** with a **target struct**. This provides compile-time type safety and eliminates the need for runtime type assertions.
```go
package main
import (
"fmt"
"log"
"os"
"github.com/lixenwraith/config"
)
// 1. Define your application's configuration struct.
type AppConfig struct {
Server struct {
Host string `toml:"host"`
Port int `toml:"port"`
} `toml:"server"`
Debug bool `toml:"debug"`
}
func main() {
// 2. Create a target instance with defaults.
// Method A: Direct initialization (cleaner for simple defaults)
target := &AppConfig{}
target.Server.Host = "localhost"
target.Server.Port = 8080
// Method B: Use WithDefaults() for explicit separation (shown below)
// 3. Use the builder to configure and load all sources.
cfg, err := config.NewBuilder().
WithTarget(target). // Enable type-safe mode and register struct fields.
// WithDefaults(&AppConfig{...}), // Optional: Override defaults from WithTarget.
WithFile("config.toml"). // Load from file (supports .toml, .json, .yaml).
WithEnvPrefix("APP_"). // Load from environment (e.g., APP_SERVER_PORT).
WithArgs(os.Args[1:]). // Load from command-line flags (e.g., --server.port=9090).
Build() // Build the final config object.
if err != nil {
log.Fatalf("Config build failed: %v", err)
}
// 4. Access the fully populated, type-safe struct.
// The `target` variable is now populated with the final merged values.
fmt.Printf("Running on %s:%d\n", target.Server.Host, target.Server.Port)
// Or, retrieve the updated struct at any time (e.g., after a live reload).
latest, _ := cfg.AsStruct()
latestConfig := latest.(*AppConfig)
fmt.Printf("Debug mode: %v\n", latestConfig.Debug)
}
```
## Supported Formats: TOML, JSON, YAML
The package supports multiple file formats. The format is auto-detected from the file extension (`.toml`, `.json`, `.yaml`, `.yml`) or file content.
* To specify a format explicitly, use `Builder.WithFileFormat("json")`.
* The default struct tag for field mapping is `toml`, but can be changed with `Builder.WithTagName("json")`.
## Builder Pattern
The `Builder` is the primary way to construct a `Config` instance.
```go
// NewBuilder creates a new configuration builder.
func NewBuilder() *Builder
// Build finalizes configuration; returns the first of any accumulated errors.
func (b *Builder) Build() (*Config, error)
// MustBuild is like Build but panics on fatal errors.
func (b *Builder) MustBuild() *Config
```
### Builder Methods
* `WithTarget(target any)`: **(Recommended)** Enables type-safe mode. Registers fields from the target struct and allows access via `AsStruct()`. The target's initial values are used as defaults unless `WithDefaults` is also called.
* `WithDefaults(defaults any)`: Explicitly sets a struct containing default values. Overrides any defaults from `WithTarget`.
* `WithFile(path string)`: Sets the configuration file path.
* `WithFileDiscovery(opts FileDiscoveryOptions)`: Enables automatic config file discovery (searches CLI flags, env vars, XDG paths, and current directory).
* `WithArgs(args []string)`: Sets the command-line arguments to parse (e.g., `--server.port=9090`).
* `WithEnvPrefix(prefix string)`: Sets the global environment variable prefix (e.g., `MYAPP_`).
* `WithSources(sources ...Source)`: Overrides the default source precedence order.
* `WithTypedValidator(fn any)`: **(Recommended for validation)** Adds a type-safe validation function that runs *after* the target struct is populated. The function signature must be `func(c *YourConfigType) error`.
* `WithValidator(fn ValidatorFunc)`: Adds a validation function that runs *before* type-safe population, operating on the raw `*Config` object.
* `WithTagName(tagName string)`: Sets the primary struct tag for field mapping (`"toml"`, `"json"`, `"yaml"`).
* `WithPrefix(prefix string)`: Adds a prefix to all registered paths from a struct.
* `WithFileFormat(format string)`: Explicitly sets the file format (`"toml"`, `"json"`, `"yaml"`, `"auto"`).
* `WithSecurityOptions(opts SecurityOptions)`: Sets security options for file loading (path traversal, file size limits).
* `WithEnvTransform(fn EnvTransformFunc)`: Sets a custom environment variable mapping function.
* `WithEnvWhitelist(paths ...string)`: Limits environment variable loading to a specific set of paths.
## Type-Safe Access & Population
These are the **preferred methods** for accessing configuration data.
* `AsStruct() (any, error)`: After using `Builder.WithTarget()`, this method returns the populated, type-safe target struct. This is the primary way to access config after initialization or live reload.
* `Scan(basePath string, target any)`: Populates a struct with values from a specific config path (e.g., `cfg.Scan("server", &serverConf)`).
* `GetTyped[T](c *Config, path string) (T, error)`: Retrieves a single value and decodes it to type `T`, handling type conversion automatically.
* `ScanTyped[T](c *Config, basePath ...string) (*T, error)`: A generic wrapper around `Scan` that allocates, populates, and returns a pointer to a struct of type `T`.
## Live Reconfiguration
Enable automatic reloading of configuration when the source file changes.
* `AutoUpdate()`: Enables file watching and automatic reloading with default options.
* `AutoUpdateWithOptions(opts WatchOptions)`: Enables reloading with custom options (e.g., poll interval, debounce).
* `StopAutoUpdate()`: Stops the file watcher.
* `Watch() <-chan string`: Returns a channel that receives the paths of changed values.
* `WatchFile(filePath string, formatHint ...string)`: Switches the watcher to a new file at runtime.
* `IsWatching() bool`: Returns `true` if the file watcher is active.
The watch channel also receives special notifications: `"file_deleted"`, `"permissions_changed"`, `"reload_error:..."`.
## Source Precedence
The default order of precedence (highest to lowest) is:
1. **CLI**: Command-line arguments (`--server.port=9090`)
2. **Env**: Environment variables (`MYAPP_SERVER_PORT=8888`)
3. **File**: Configuration file (`config.toml`)
4. **Default**: Values registered from a struct.
This order can be changed via `Builder.WithSources()` or `Config.SetPrecedence()`.
## Dynamic / Legacy Value Access
These methods are for dynamic key-value access and should be **avoided when a type-safe struct can be used**. They require runtime type assertions.
* `Get(path string) (any, bool)`: Retrieves the final merged value. The `bool` indicates if the path was registered. Requires a type assertion, e.g., `port := val.(int64)`.
* `Set(path string, value any)`: Updates a value in the highest priority source. The path must be registered first.
* `GetSource(path string, source Source) (any, bool)`: Retrieves a value from a specific source layer.
* `SetSource(path string, source Source, value any)`: Sets a value for a specific source layer.
## API Reference Summary
### Core Types
* `Config`: The primary thread-safe configuration manager.
* `Source`: A configuration source (`SourceCLI`, `SourceEnv`, `SourceFile`, `SourceDefault`).
* `LoadOptions`: Options for loading configuration from multiple sources.
* `Builder`: Fluent API for constructing a `Config` instance.
### Core Methods
* `New() *Config`: Creates a new `Config` instance.
* `Register(path string, defaultValue any)`: Registers a path with a default value.
* `RegisterStruct(prefix string, structWithDefaults any)`: Recursively registers fields from a struct using `toml` tags.
* `Validate(required ...string)`: Checks that all specified required paths have been set from a non-default source.
* `Save(path string)`: Atomically saves the current merged configuration state to a file.
* `Clone() *Config`: Creates a deep copy of the configuration state.
* `Debug() string`: Returns a formatted string of all values for debugging.

View File

@ -99,6 +99,19 @@ cfg.RegisterWithEnv("server.port", 8080, "PORT")
cfg.RegisterWithEnv("database.url", "localhost", "DATABASE_URL")
```
## Using the `env` Tag
Structs can specify explicit environment variable names using the `env` tag:
```go
type Config struct {
Server struct {
Port int `toml:"port" env:"PORT"` // Uses $PORT
Host string `toml:"host" env:"SERVER_HOST"` // Uses $SERVER_HOST
} `toml:"server"`
}
```
## Environment Variable Whitelist
Limit which paths can be set via environment:
@ -186,10 +199,4 @@ cfg, _ := config.NewBuilder().
config.SourceDefault,
).
Build()
```
## See Also
- [Command Line](cli.md) - CLI argument handling
- [File Configuration](file.md) - Configuration file formats
- [Access Patterns](access.md) - Retrieving values
```

View File

@ -301,10 +301,4 @@ if err := cfg.Scan("database", &dbCfg); err != nil {
3. **Validate After Load**: Add validators to check loaded values
4. **Handle Missing Files**: Missing config files often aren't fatal
5. **Use Atomic Saves**: The built-in Save method is atomic
6. **Document Structure**: Comment your TOML files thoroughly
## See Also
- [Live Reconfiguration](reconfiguration.md) - Automatic file reloading
- [Builder Pattern](builder.md) - File discovery options
- [Access Patterns](access.md) - Working with loaded values
6. **Document Structure**: Comment your TOML files thoroughly

View File

@ -1,302 +0,0 @@
# lixenwraith/config LLM Usage Guide
Thread-safe configuration management for Go applications with multi-source support, type safety, and live reconfiguration.
Use default configuration and behavior if applicable, unless explicitly required.
## Core Types
### Config
```go
// Primary configuration manager. All operations are thread-safe.
type Config struct {
// Internal fields - thread-safe configuration store
}
```
### Source
```go
// Represents a configuration source, used to define load precedence.
type Source string
const (
SourceDefault Source = "default"
SourceFile Source = "file"
SourceEnv Source = "env"
SourceCLI Source = "cli"
)
```
### LoadOptions
```go
type LoadOptions struct {
Sources []Source // Precedence order (first = highest)
EnvPrefix string // Prepended to env var names
EnvTransform EnvTransformFunc // Custom path→env mapping
LoadMode LoadMode // Uses default behavior, do not configure
EnvWhitelist map[string]bool // Limit env paths (nil = all)
SkipValidation bool // Skip path validation
}
type EnvTransformFunc func(path string) string
type LoadMode int // LoadModeReplace (default) or LoadModeMerge
```
## Error Types
```go
var (
ErrConfigNotFound = errors.New("configuration file not found")
ErrCLIParse = errors.New("failed to parse command-line arguments")
ErrEnvParse = errors.New("failed to parse environment variables")
ErrValueSize = fmt.Errorf("value size exceeds maximum %d bytes", MaxValueSize)
)
const MaxValueSize = 1024 * 1024 // 1MB
```
## Core Methods
### Creation
```go
// New creates a new Config instance with default options.
func New() *Config
// NewWithOptions creates a new Config instance with custom load options.
func NewWithOptions(opts LoadOptions) *Config
func DefaultLoadOptions() LoadOptions
```
### Registration
```go
// Register makes a configuration path known with a default value; required before use.
func (c *Config) Register(path string, defaultValue any) error
// RegisterStruct recursively registers fields from a struct using `toml` tags by default.
func (c *Config) RegisterStruct(prefix string, structWithDefaults any) error
// RegisterStructWithTags is like RegisterStruct but allows custom tag names ("json", "yaml").
func (c *Config) RegisterStructWithTags(prefix string, structWithDefaults any, tagName string) error
// RegisterWithEnv registers a path with an explicit environment variable mapping.
func (c *Config) RegisterWithEnv(path string, defaultValue any, envVar string) error
// Unregister removes a configuration path and all its children.
func (c *Config) Unregister(path string) error
```
Only default `toml` tags must be used unless support of other types are explicitly requested.
Path registration is required before setting values. Paths use dot notation (e.g., "server.port").
### Value Access
```go
// Get retrieves the final merged value; the bool indicates if the path was registered.
func (c *Config) Get(path string) (any, bool)
// GetSource retrieves a value from a specific source layer.
func (c *Config) GetSource(path string, source Source) (any, bool)
// GetSources returns all sources that have a value for the given path.
func (c *Config) GetSources(path string) map[Source]any
```
The returned `any` type requires type assertion, e.g., `port := val.(int64)`.
### Value Modification
```go
// Set updates a value in the highest priority source (default: CLI). Path must be registered.
func (c *Config) Set(path string, value any) error
// SetSource sets a value for a specific source layer.
func (c *Config) SetSource(path string, source Source, value any) error
// SetLoadOptions updates the load options, recomputing all current values.
func (c *Config) SetLoadOptions(opts LoadOptions) error
```
### Loading
```go
// Load reads configuration from a TOML file and merges overrides from command-line arguments.
func (c *Config) Load(filePath string, args []string) error
// LoadWithOptions loads configuration from multiple sources with custom options.
func (c *Config) LoadWithOptions(filePath string, args []string, opts LoadOptions) error
// LoadFile loads configuration values from a TOML file into the File source.
func (c *Config) LoadFile(path string) error
// LoadEnv loads values from environment variables into the Env source.
func (c *Config) LoadEnv(prefix string) error
// LoadCLI loads values from command-line arguments into the CLI source.
func (c *Config) LoadCLI(args []string) error
```
### Scanning & Population
```go
// Scan populates a struct from a specific config path (e.g., "server").
func (c *Config) Scan(basePath string, target any) error
// ScanSource decodes configuration from specific source
func (c *Config) ScanSource(basePath string, source Source, target any) error
// Target populates a struct from the root of the config; alias for Scan("", target).
func (c *Config) Target(out any) error
// AsStruct retrieves the pre-configured target struct (see Builder.WithTarget).
func (c *Config) AsStruct() (any, error)
```
Populates structs using mapstructure with automatic type conversion.
### Persistence
```go
// Save atomically saves the current merged configuration state to a TOML file.
func (c *Config) Save(path string) error
// SaveSource atomically saves values from only a specific source to a TOML file.
func (c *Config) SaveSource(path string, source Source) error
```
Atomic file writes in TOML format.
### State Management
```go
// Reset clears all non-default values from all sources.
func (c *Config) Reset()
// ResetSource clears all values from a specific source.
func (c *Config) ResetSource(source Source)
// Clone creates a deep copy of the configuration state.
func (c *Config) Clone() *Config
```
### Inspection
```go
// GetRegisteredPaths returns all registered paths matching a prefix.
func (c *Config) GetRegisteredPaths(prefix string) map[string]bool
// Validate checks that all specified required paths have been set.
func (c *Config) Validate(required ...string) error
// Debug returns a formatted string of all values and their sources for debugging.
func (c *Config) Debug() string
```
### Environment
```go
// DiscoverEnv discovers environment variables matching a prefix.
func (c *Config) DiscoverEnv(prefix string) map[string]string
// ExportEnv exports the current configuration as environment variables
func (c *Config) ExportEnv(prefix string) map[string]string
```
## Builder Pattern
### Builder
```go
type Builder struct {
// Internal builder state
}
type ValidatorFunc func(c *Config) error
```
### Builder Methods
```go
// NewBuilder creates a new configuration builder.
func NewBuilder() *Builder
// Build finalizes configuration; returns the first of any accumulated errors.
func (b *Builder) Build() (*Config, error)
// WithDefaults sets the struct containing default values.
func (b *Builder) WithDefaults(defaults any) *Builder
// WithTarget enables type-aware mode for AsStruct() and registers struct fields.
func (b *Builder) WithTarget(target any) *Builder
// WithTagName sets the primary struct tag for field mapping: "toml", "json", "yaml".
func (b *Builder) WithTagName(tagName string) *Builder
// WithSources sets the precedence order for configuration sources.
func (b *Builder) WithSources(sources ...Source) *Builder
// WithPrefix adds a prefix to all registered paths from a struct.
func (b *Builder) WithPrefix(prefix string) *Builder
// WithEnvPrefix sets the global environment variable prefix.
func (b *Builder) WithEnvPrefix(prefix string) *Builder
// WithFile sets the configuration file path to be loaded.
func (b *Builder) WithFile(path string) *Builder
// WithArgs sets the command-line arguments to be parsed.
func (b *Builder) WithArgs(args []string) *Builder
// WithValidator adds a validation function that runs after loading.
func (b *Builder) WithValidator(fn ValidatorFunc) *Builder
// WithEnvTransform sets a custom environment variable mapping function.
func (b *Builder) WithSources(sources ...Source) *Builder
// WithEnvTransform sets a custom environment variable mapping function.
func (b *Builder) WithEnvTransform(fn EnvTransformFunc) *Builder
// WithFileDiscovery enables automatic config file discovery
func (b *Builder) WithFileDiscovery(opts FileDiscoveryOptions) *Builder
```
### FileDiscoveryOptions
```go
type FileDiscoveryOptions struct {
Name string // Base name without extension
Extensions []string // Extensions to try in order
Paths []string // Custom search paths
EnvVar string // Environment variable for path
CLIFlag string // CLI flag for path
UseXDG bool // Search XDG directories
UseCurrentDir bool // Search current directory
}
func DefaultDiscoveryOptions(appName string) FileDiscoveryOptions
```
## Live Reconfiguration
### AutoUpdate
```go
// AutoUpdate enables automatic configuration reloading on file changes with default options.
func (c *Config) AutoUpdate()
// AutoUpdateWithOptions enables reloading with custom options.
func (c *Config) AutoUpdateWithOptions(opts WatchOptions)
// StopAutoUpdate stops the file watcher and cleans up resources.
func (c *Config) StopAutoUpdate()
// IsWatching returns true if the file watcher is active.
func (c *Config) IsWatching() bool
```
### Watch
```go
// Watch returns a channel that receives paths of changed values.
func (c *Config) Watch() <-chan string
// WatcherCount returns the number of active watch subscribers.
func (c *Config) WatcherCount() int
```
Channel receives paths of changed values or special notifications: `"file_deleted"`, `"permissions_changed"`, `"reload_error:*"`.
### WatchOptions
```go
type WatchOptions struct {
PollInterval time.Duration // File check interval (min 100ms)
Debounce time.Duration // Delay after changes
MaxWatchers int // Concurrent watch limit
ReloadTimeout time.Duration // Reload operation timeout
VerifyPermissions bool // Check permission changes
}
func DefaultWatchOptions() WatchOptions
```
## Type System
### Supported Types
- Basic: `bool`, `int64`, `float64`, `string`
- Time: `time.Duration`, `time.Time`
- Network: `net.IP`, `net.IPNet`, `url.URL`
- Slices: Any slice type with comma-separated parsing
- Complex: Any type via mapstructure decode hooks
### Type Conversion
All integer types are stored as `int64`, and floats as `float64`. String inputs from sources like environment variables or CLI arguments are automatically parsed to the target registered type. Custom types supported via decode hooks.
### Struct Tags
The `WithTagName` builder method sets the primary tag used for mapping paths.
```go
type Config struct {
// Uses the tag set by WithTagName (default "toml") for path name.
// The `env` tag provides an explicit environment variable override.
Port int64 `toml:"port" env:"PORT"`
Timeout time.Duration `toml:"timeout"`
// Slices are populated from comma-separated strings (env/CLI) or arrays (file).
Tags []string `toml:"tags"`
}
```
## Thread Safety
All methods are thread-safe. Concurrent reads and writes are synchronized internally.
## Path Validation
- Paths use dot notation: "server.port", "database.connections.max"
- Segments must be valid identifiers: `[A-Za-z0-9_-]+`
- No leading/trailing dots or empty segments
## Source Precedence
Default order (highest to lowest):
1. CLI arguments
2. Environment variables
3. Configuration file
4. Default values
Precedence is configurable via `Builder.WithSources()` or `LoadOptions.Sources`.

View File

@ -106,7 +106,7 @@ type Config struct {
// Type assertions are safe after registration
port, _ := cfg.Get("port")
portNum := port.(int64) // Safe - type is guaranteed
portNum := port.(int64) // Type matches registration
```
## Error Handling
@ -124,6 +124,30 @@ if err != nil {
}
```
### Error Categories
The package uses structured error categories for better error handling. Check errors using `errors.Is()`:
```go
if err := cfg.LoadFile("config.toml"); err != nil {
switch {
case errors.Is(err, config.ErrConfigNotFound):
// Config file doesn't exist, use defaults
case errors.Is(err, config.ErrFileAccess):
// Permission denied or file access issues
case errors.Is(err, config.ErrFileFormat):
// Invalid TOML/JSON/YAML syntax
case errors.Is(err, config.ErrTypeMismatch):
// Value type doesn't match registered type
case errors.Is(err, config.ErrValidation):
// Validation failed
default:
log.Fatal(err)
}
}
```
See [Errors](./error.go) for the complete list of error categories and description.
## Common Patterns
### Required Fields
@ -139,10 +163,26 @@ if err := cfg.Validate("api.key", "database.url"); err != nil {
}
```
### Type-Safe Validation
```go
cfg, _ := config.NewBuilder().
WithTarget(&Config{}).
WithTypedValidator(func(c *Config) error {
if c.Server.Port < 1024 {
return fmt.Errorf("port must be >= 1024")
}
return nil
}).
Build()
```
See [Validation](validator.md) for more validation options.
### Using Different Struct Tags
```go
// Use JSON tags instead of TOML
// Use JSON tags instead of default TOML
type Config struct {
Server struct {
Host string `json:"host"`
@ -153,7 +193,7 @@ type Config struct {
cfg, _ := config.NewBuilder().
WithTarget(&Config{}).
WithTagName("json").
WithFile("config.toml").
WithFile("config.json").
Build()
```
@ -172,5 +212,4 @@ for source, value := range sources {
## Next Steps
- [Builder Pattern](builder.md) - Advanced configuration options
- [Environment Variables](env.md) - Detailed environment variable handling
- [Access Patterns](access.md) - All ways to get and set values

View File

@ -346,10 +346,4 @@ go func() {
}
}
}()
```
## See Also
- [File Configuration](file.md) - File format and loading
- [Access Patterns](access.md) - Reacting to changed values
- [Builder Pattern](builder.md) - Setting up watching with builder
```

175
doc/validator.md Normal file
View File

@ -0,0 +1,175 @@
# Validation
The config package provides flexible validation through function injection and bundled validators for common scenarios.
## Validation Stages
The package supports two validation stages when using the Builder pattern:
### Pre-Population Validation (`WithValidator`)
Runs on raw configuration values before struct population:
```go
cfg, _ := config.NewBuilder().
WithDefaults(defaults).
WithValidator(func(c *config.Config) error {
// Check required paths exist
return c.Validate("api.key", "database.url")
}).
Build()
```
### Post-Population Validation (`WithTypedValidator`)
Type-safe validation after struct is populated:
```go
type AppConfig struct {
Server struct {
Port int64 `toml:"port"`
} `toml:"server"`
}
cfg, _ := config.NewBuilder().
WithTarget(&AppConfig{}).
WithTypedValidator(func(cfg *AppConfig) error {
// No type assertion needed - cfg.Server.Port is int64
if cfg.Server.Port < 1024 || cfg.Server.Port > 65535 {
return fmt.Errorf("port %d outside valid range", cfg.Server.Port)
}
return nil
}).
Build()
```
## Bundled Validators
The package includes common validation functions in the `config` package:
### Numeric Validators
```go
// Port validates TCP/UDP port range (1-65535)
config.Port(8080) // returns nil
// Positive validates positive numbers
config.Positive(42) // returns nil
config.Positive(-1) // returns error
// NonNegative validates non-negative numbers
config.NonNegative(0) // returns nil
config.NonNegative(-5) // returns error
// Range creates min/max validators
portValidator := config.Range(1024, 65535)
err := portValidator(8080) // returns nil
```
### String Validators
```go
// NonEmpty validates non-empty strings
config.NonEmpty("hello") // returns nil
config.NonEmpty("") // returns error
// Pattern creates regex validators
emailPattern := config.Pattern(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`)
err := emailPattern("user@example.com") // returns nil
// URLPath validates URL path format
config.URLPath("/api/v1") // returns nil
config.URLPath("api/v1") // returns error (must start with /)
```
### Network Validators
```go
// IPAddress validates any IP address
config.IPAddress("192.168.1.1") // returns nil
config.IPAddress("::1") // returns nil
// IPv4Address validates IPv4 only
config.IPv4Address("192.168.1.1") // returns nil
config.IPv4Address("::1") // returns error
// IPv6Address validates IPv6 only
config.IPv6Address("2001:db8::1") // returns nil
config.IPv6Address("192.168.1.1") // returns error
```
### Enum Validators
```go
// OneOf creates allowed values validator
logLevel := config.OneOf("debug", "info", "warn", "error")
err := logLevel("info") // returns nil
err = logLevel("trace") // returns error
```
## Integration Example
```go
type ServerConfig struct {
Host string `toml:"host"`
Port int64 `toml:"port"`
Mode string `toml:"mode"`
}
cfg, err := config.NewBuilder().
WithTarget(&ServerConfig{}).
WithFile("config.toml").
WithTypedValidator(func(c *ServerConfig) error {
// Use bundled validators
if err := config.IPAddress(c.Host); err != nil {
return fmt.Errorf("invalid host: %w", err)
}
if err := config.Port(c.Port); err != nil {
return fmt.Errorf("invalid port: %w", err)
}
modeValidator := config.OneOf("development", "production")
if err := modeValidator(c.Mode); err != nil {
return fmt.Errorf("invalid mode: %w", err)
}
return nil
}).
Build()
```
## Integration with go-playground/validator
The package works seamlessly with `go-playground/validator`:
```go
import "github.com/go-playground/validator/v10"
type ServerConfig struct {
Host string `toml:"host" validate:"required,hostname"`
Port int `toml:"port" validate:"required,min=1024,max=65535"`
TLS struct {
Enabled bool `toml:"enabled"`
Cert string `toml:"cert" validate:"required_if=Enabled true"`
Key string `toml:"key" validate:"required_if=Enabled true"`
} `toml:"tls"`
}
// Load configuration
cfg, _ := config.NewBuilder().
WithDefaults(&ServerConfig{}).
Build()
// Get populated struct
serverCfg, _ := cfg.AsStruct()
// Validate with go-playground/validator
validate := validator.New()
if err := validate.Struct(serverCfg); err != nil {
return err // Validation errors
}
```
Tags don't conflict since each package uses different tag names (`toml`/`json`/`yaml` for config, `validate` for validator).