v0.1.0 Release
This commit is contained in:
@ -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
174
doc/architecture.md
Normal 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
|
||||
@ -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()`
|
||||
@ -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
157
doc/config-llm-guide.md
Normal 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.
|
||||
21
doc/env.md
21
doc/env.md
@ -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
|
||||
```
|
||||
@ -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
|
||||
302
doc/llm-guide.md
302
doc/llm-guide.md
@ -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`.
|
||||
@ -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
|
||||
@ -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
175
doc/validator.md
Normal 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).
|
||||
Reference in New Issue
Block a user