e4.0.0 Refactored, file watcher and improved builder, doc update
This commit is contained in:
159
config.go
159
config.go
@ -7,9 +7,14 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Max config item value size to prevent misuse
|
||||
const MaxValueSize = 1024 * 1024 // 1MB
|
||||
|
||||
// Errors
|
||||
var (
|
||||
// ErrConfigNotFound indicates the specified configuration file was not found.
|
||||
@ -19,65 +24,13 @@ var (
|
||||
ErrCLIParse = errors.New("failed to parse command-line arguments")
|
||||
|
||||
// ErrEnvParse indicates that parsing environment variables failed.
|
||||
// TODO: use in loader:loadEnv or remove
|
||||
ErrEnvParse = errors.New("failed to parse environment variables")
|
||||
|
||||
// ErrValueSize indicates a value larger than MaxValueSize
|
||||
ErrValueSize = fmt.Errorf("value size exceeds maximum %d bytes", MaxValueSize)
|
||||
)
|
||||
|
||||
// Source represents a configuration source
|
||||
type Source string
|
||||
|
||||
const (
|
||||
SourceDefault Source = "default"
|
||||
SourceFile Source = "file"
|
||||
SourceEnv Source = "env"
|
||||
SourceCLI Source = "cli"
|
||||
)
|
||||
|
||||
// LoadMode defines how configuration sources are processed
|
||||
type LoadMode int
|
||||
|
||||
const (
|
||||
// LoadModeReplace completely replaces values (default behavior)
|
||||
LoadModeReplace LoadMode = iota
|
||||
|
||||
// LoadModeMerge merges maps/structs instead of replacing
|
||||
LoadModeMerge
|
||||
)
|
||||
|
||||
// EnvTransformFunc converts a configuration path to an environment variable name
|
||||
type EnvTransformFunc func(path string) string
|
||||
|
||||
// LoadOptions configures how configuration is loaded from multiple sources
|
||||
type LoadOptions struct {
|
||||
// Sources defines the precedence order (first = highest priority)
|
||||
// Default: [SourceCLI, SourceEnv, SourceFile, SourceDefault]
|
||||
Sources []Source
|
||||
|
||||
// EnvPrefix is prepended to environment variable names
|
||||
// Example: "MYAPP_" transforms "server.port" to "MYAPP_SERVER_PORT"
|
||||
EnvPrefix string
|
||||
|
||||
// EnvTransform customizes how paths map to environment variables
|
||||
// If nil, uses default transformation (dots to underscores, uppercase)
|
||||
EnvTransform EnvTransformFunc
|
||||
|
||||
// LoadMode determines how values are merged
|
||||
LoadMode LoadMode
|
||||
|
||||
// EnvWhitelist limits which paths are checked for env vars (nil = all)
|
||||
EnvWhitelist map[string]bool
|
||||
|
||||
// SkipValidation skips path validation during load
|
||||
SkipValidation bool
|
||||
}
|
||||
|
||||
// DefaultLoadOptions returns the standard load options
|
||||
func DefaultLoadOptions() LoadOptions {
|
||||
return LoadOptions{
|
||||
Sources: []Source{SourceCLI, SourceEnv, SourceFile, SourceDefault},
|
||||
LoadMode: LoadModeReplace,
|
||||
}
|
||||
}
|
||||
|
||||
// configItem holds configuration values from different sources
|
||||
type configItem struct {
|
||||
defaultValue any
|
||||
@ -85,14 +38,31 @@ type configItem struct {
|
||||
currentValue any // Computed value based on precedence
|
||||
}
|
||||
|
||||
// Config manages application configuration loaded from multiple sources.
|
||||
// structCache manages the typed representation of configuration
|
||||
type structCache struct {
|
||||
target any // User-provided struct pointer
|
||||
targetType reflect.Type // Cached type for validation
|
||||
version int64 // Version for invalidation
|
||||
populated bool // Whether cache is valid
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Config manages application configuration. It can be used in two primary ways:
|
||||
// 1. As a dynamic key-value store, accessed via methods like Get(), String(), and Int64()
|
||||
// 2. As a source for a type-safe struct, populated via BuildAndScan() or AsStruct()
|
||||
type Config struct {
|
||||
items map[string]configItem
|
||||
mutex sync.RWMutex
|
||||
options LoadOptions // Current load options
|
||||
fileData map[string]any // Cached file data
|
||||
envData map[string]any // Cached env data
|
||||
cliData map[string]any // Cached CLI data
|
||||
items map[string]configItem
|
||||
mutex sync.RWMutex
|
||||
options LoadOptions // Current load options
|
||||
fileData map[string]any // Cached file data
|
||||
envData map[string]any // Cached env data
|
||||
cliData map[string]any // Cached CLI data
|
||||
version atomic.Int64
|
||||
structCache *structCache
|
||||
|
||||
// File watching support
|
||||
watcher *watcher
|
||||
configFilePath string // Track loaded file path
|
||||
}
|
||||
|
||||
// New creates and initializes a new Config instance.
|
||||
@ -142,9 +112,7 @@ func (c *Config) computeValue(path string, item configItem) any {
|
||||
return item.defaultValue
|
||||
}
|
||||
|
||||
// Get retrieves a configuration value using the path.
|
||||
// It returns the current value based on configured precedence.
|
||||
// The second return value indicates if the path was registered.
|
||||
// Get retrieves a configuration value using the path and indicator if the path was registered
|
||||
func (c *Config) Get(path string) (any, bool) {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
@ -172,8 +140,9 @@ func (c *Config) GetSource(path string, source Source) (any, bool) {
|
||||
}
|
||||
|
||||
// Set updates a configuration value for the given path.
|
||||
// It sets the value in the highest priority source (typically CLI).
|
||||
// Returns an error if the path is not registered.
|
||||
// It sets the value in the highest priority source from the configured Sources.
|
||||
// By default, this is SourceCLI. Returns an error if the path is not registered.
|
||||
// To set a value in a specific source, use SetSource instead.
|
||||
func (c *Config) Set(path string, value any) error {
|
||||
return c.SetSource(path, c.options.Sources[0], value)
|
||||
}
|
||||
@ -206,6 +175,7 @@ func (c *Config) SetSource(path string, source Source, value any) error {
|
||||
c.cliData[path] = value
|
||||
}
|
||||
|
||||
c.invalidateCache() // Invalidate cache after changes
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -242,6 +212,8 @@ func (c *Config) Reset() {
|
||||
item.currentValue = item.defaultValue
|
||||
c.items[path] = item
|
||||
}
|
||||
|
||||
c.invalidateCache() // Invalidate cache after changes
|
||||
}
|
||||
|
||||
// ResetSource clears all values from a specific source
|
||||
@ -265,4 +237,55 @@ func (c *Config) ResetSource(source Source) {
|
||||
item.currentValue = c.computeValue(path, item)
|
||||
c.items[path] = item
|
||||
}
|
||||
|
||||
c.invalidateCache() // Invalidate cache after changes
|
||||
}
|
||||
|
||||
// Override Set methods to invalidate cache
|
||||
func (c *Config) invalidateCache() {
|
||||
c.version.Add(1)
|
||||
}
|
||||
|
||||
// AsStruct returns the populated struct if in type-aware mode
|
||||
func (c *Config) AsStruct() (any, error) {
|
||||
if c.structCache == nil || c.structCache.target == nil {
|
||||
return nil, fmt.Errorf("no target struct configured")
|
||||
}
|
||||
|
||||
c.structCache.mu.RLock()
|
||||
currentVersion := c.version.Load()
|
||||
needsUpdate := !c.structCache.populated || c.structCache.version != currentVersion
|
||||
c.structCache.mu.RUnlock()
|
||||
|
||||
if needsUpdate {
|
||||
if err := c.populateStruct(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c.structCache.target, nil
|
||||
}
|
||||
|
||||
// Target populates the provided struct with current configuration
|
||||
func (c *Config) Target(out any) error {
|
||||
return c.Scan("", out)
|
||||
}
|
||||
|
||||
// populateStruct updates the cached struct representation using unified unmarshal
|
||||
func (c *Config) populateStruct() error {
|
||||
c.structCache.mu.Lock()
|
||||
defer c.structCache.mu.Unlock()
|
||||
|
||||
currentVersion := c.version.Load()
|
||||
if c.structCache.populated && c.structCache.version == currentVersion {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.unmarshal("", "", c.structCache.target); err != nil {
|
||||
return fmt.Errorf("failed to populate struct cache: %w", err)
|
||||
}
|
||||
|
||||
c.structCache.version = currentVersion
|
||||
c.structCache.populated = true
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user