376 lines
7.5 KiB
Markdown
376 lines
7.5 KiB
Markdown
# Access Patterns
|
|
|
|
This guide covers all methods for getting and setting configuration values, type conversions, and working with structured data.
|
|
|
|
**Always Register First**: Register paths before setting values
|
|
**Use Type Assertions**: After struct registration, types are guaranteed
|
|
|
|
## Getting Values
|
|
|
|
### Basic Get
|
|
|
|
```go
|
|
// Get returns (value, exists)
|
|
value, exists := cfg.Get("server.port")
|
|
if !exists {
|
|
log.Fatal("server.port not configured")
|
|
}
|
|
|
|
// Type assertion (safe after registration)
|
|
port := value.(int64)
|
|
```
|
|
|
|
### Type-Safe Access
|
|
|
|
When using struct registration, types are guaranteed:
|
|
|
|
```go
|
|
type Config struct {
|
|
Server struct {
|
|
Port int64 `toml:"port"`
|
|
Host string `toml:"host"`
|
|
} `toml:"server"`
|
|
}
|
|
|
|
cfg.RegisterStruct("", &Config{})
|
|
|
|
// After registration, type assertions are safe
|
|
port, _ := cfg.Get("server.port")
|
|
portNum := port.(int64) // Won't panic - type is enforced
|
|
```
|
|
|
|
### Get from Specific Source
|
|
|
|
```go
|
|
// Get value from specific source
|
|
envPort, exists := cfg.GetSource("server.port", config.SourceEnv)
|
|
if exists {
|
|
log.Printf("Port from environment: %v", envPort)
|
|
}
|
|
|
|
// Check all sources
|
|
sources := cfg.GetSources("server.port")
|
|
for source, value := range sources {
|
|
log.Printf("%s: %v", source, value)
|
|
}
|
|
```
|
|
|
|
### Struct Scanning
|
|
|
|
```go
|
|
// Scan into struct
|
|
var serverConfig struct {
|
|
Host string `toml:"host"`
|
|
Port int64 `toml:"port"`
|
|
TLS struct {
|
|
Enabled bool `toml:"enabled"`
|
|
Cert string `toml:"cert"`
|
|
} `toml:"tls"`
|
|
}
|
|
|
|
if err := cfg.Scan("server", &serverConfig); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Use structured data
|
|
log.Printf("Server: %s:%d", serverConfig.Host, serverConfig.Port)
|
|
```
|
|
|
|
### Target Population
|
|
|
|
```go
|
|
// Populate entire config struct
|
|
var config AppConfig
|
|
if err := cfg.Target(&config); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Or with builder pattern
|
|
var config AppConfig
|
|
cfg, _ := config.NewBuilder().
|
|
WithTarget(&config).
|
|
Build()
|
|
|
|
// Access directly
|
|
fmt.Println(config.Server.Port)
|
|
```
|
|
|
|
### Type-Aware Mode
|
|
|
|
```go
|
|
var conf AppConfig
|
|
|
|
cfg, _ := config.NewBuilder().
|
|
WithTarget(&conf).
|
|
Build()
|
|
|
|
// Get updated struct anytime
|
|
latest, err := cfg.AsStruct()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
appConfig := latest.(*AppConfig)
|
|
```
|
|
|
|
## Setting Values
|
|
|
|
### Basic Set
|
|
|
|
```go
|
|
// Set updates the highest priority source (default: CLI)
|
|
if err := cfg.Set("server.port", int64(9090)); err != nil {
|
|
log.Fatal(err) // Error if path not registered
|
|
}
|
|
```
|
|
|
|
### Set in Specific Source
|
|
|
|
```go
|
|
// Set value in specific source
|
|
cfg.SetSource("server.port", config.SourceEnv, "8080")
|
|
cfg.SetSource("debug", config.SourceCLI, true)
|
|
|
|
// File source typically set via LoadFile, but can be manual
|
|
cfg.SetSource("feature.enabled", config.SourceFile, true)
|
|
```
|
|
|
|
### Batch Updates
|
|
|
|
```go
|
|
// Multiple updates
|
|
updates := map[string]any{
|
|
"server.port": int64(9090),
|
|
"server.host": "0.0.0.0",
|
|
"database.maxconns": int64(50),
|
|
}
|
|
|
|
for path, value := range updates {
|
|
if err := cfg.Set(path, value); err != nil {
|
|
log.Printf("Failed to set %s: %v", path, err)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Type Conversions
|
|
|
|
The package uses mapstructure for flexible type conversion:
|
|
|
|
```go
|
|
// These all work for a string field
|
|
cfg.Set("name", "value") // Direct string
|
|
cfg.Set("name", 123) // Number → "123"
|
|
cfg.Set("name", true) // Boolean → "true"
|
|
|
|
// For int64 fields
|
|
cfg.Set("port", int64(8080)) // Direct
|
|
cfg.Set("port", "8080") // String → int64
|
|
cfg.Set("port", 8080.0) // Float → int64
|
|
cfg.Set("port", int(8080)) // int → int64
|
|
```
|
|
|
|
### Duration Handling
|
|
|
|
```go
|
|
type Config struct {
|
|
Timeout time.Duration `toml:"timeout"`
|
|
}
|
|
|
|
// All these work
|
|
cfg.Set("timeout", 30*time.Second) // Direct duration
|
|
cfg.Set("timeout", "30s") // String parsing
|
|
cfg.Set("timeout", "5m30s") // Complex duration
|
|
```
|
|
|
|
### Network Types
|
|
|
|
```go
|
|
type Config struct {
|
|
IP net.IP `toml:"ip"`
|
|
CIDR net.IPNet `toml:"cidr"`
|
|
URL url.URL `toml:"url"`
|
|
}
|
|
|
|
// Automatic parsing
|
|
cfg.Set("ip", "192.168.1.1")
|
|
cfg.Set("cidr", "10.0.0.0/8")
|
|
cfg.Set("url", "https://example.com:8080/path")
|
|
```
|
|
|
|
### Slice Handling
|
|
|
|
```go
|
|
type Config struct {
|
|
Tags []string `toml:"tags"`
|
|
Ports []int `toml:"ports"`
|
|
}
|
|
|
|
// Direct slice
|
|
cfg.Set("tags", []string{"prod", "stable"})
|
|
|
|
// Comma-separated string (from env/CLI)
|
|
cfg.Set("tags", "prod,stable,v2")
|
|
|
|
// Number arrays
|
|
cfg.Set("ports", []int{8080, 8081, 8082})
|
|
```
|
|
|
|
## Checking Configuration
|
|
|
|
### Path Registration
|
|
|
|
```go
|
|
// Check if path is registered
|
|
if _, exists := cfg.Get("server.port"); !exists {
|
|
log.Fatal("server.port not registered")
|
|
}
|
|
|
|
// Get all registered paths
|
|
paths := cfg.GetRegisteredPaths("server.")
|
|
for path := range paths {
|
|
log.Printf("Registered: %s", path)
|
|
}
|
|
|
|
// With default values
|
|
defaults := cfg.GetRegisteredPathsWithDefaults("")
|
|
for path, defaultVal := range defaults {
|
|
log.Printf("%s = %v (default)", path, defaultVal)
|
|
}
|
|
```
|
|
|
|
### Validation
|
|
|
|
```go
|
|
// Check required fields
|
|
if err := cfg.Validate("api.key", "database.url"); err != nil {
|
|
log.Fatal("Missing required config:", err)
|
|
}
|
|
|
|
// Custom validation
|
|
requiredPorts := []string{"server.port", "metrics.port"}
|
|
for _, path := range requiredPorts {
|
|
if val, exists := cfg.Get(path); exists {
|
|
if port := val.(int64); port < 1024 {
|
|
log.Fatalf("%s must be >= 1024", path)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Source Inspection
|
|
|
|
```go
|
|
// Debug specific value
|
|
path := "server.port"
|
|
log.Printf("=== %s ===", path)
|
|
log.Printf("Current: %v", cfg.Get(path))
|
|
|
|
sources := cfg.GetSources(path)
|
|
for source, value := range sources {
|
|
log.Printf(" %s: %v", source, value)
|
|
}
|
|
```
|
|
|
|
## Advanced Patterns
|
|
|
|
### Dynamic Configuration
|
|
|
|
```go
|
|
// Change configuration at runtime
|
|
func updatePort(cfg *config.Config, port int64) error {
|
|
if port < 1 || port > 65535 {
|
|
return fmt.Errorf("invalid port: %d", port)
|
|
}
|
|
return cfg.Set("server.port", port)
|
|
}
|
|
```
|
|
|
|
### Configuration Facade
|
|
|
|
```go
|
|
type ConfigFacade struct {
|
|
cfg *config.Config
|
|
}
|
|
|
|
func (f *ConfigFacade) ServerPort() int64 {
|
|
val, _ := f.cfg.Get("server.port")
|
|
return val.(int64)
|
|
}
|
|
|
|
func (f *ConfigFacade) SetServerPort(port int64) error {
|
|
return f.cfg.Set("server.port", port)
|
|
}
|
|
|
|
func (f *ConfigFacade) DatabaseURL() string {
|
|
val, _ := f.cfg.Get("database.url")
|
|
return val.(string)
|
|
}
|
|
```
|
|
|
|
### Default Fallbacks
|
|
|
|
```go
|
|
// Helper for optional configuration
|
|
func getOrDefault(cfg *config.Config, path string, defaultVal any) any {
|
|
if val, exists := cfg.Get(path); exists {
|
|
return val
|
|
}
|
|
return defaultVal
|
|
}
|
|
|
|
// Usage
|
|
timeout := getOrDefault(cfg, "timeout", 30*time.Second).(time.Duration)
|
|
```
|
|
|
|
## Thread Safety
|
|
|
|
All access methods are thread-safe:
|
|
|
|
```go
|
|
// Safe concurrent access
|
|
var wg sync.WaitGroup
|
|
|
|
// Multiple readers
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
port, _ := cfg.Get("server.port")
|
|
log.Printf("Port: %v", port)
|
|
}()
|
|
}
|
|
|
|
// Concurrent writes are safe too
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
cfg.Set("counter", atomic.AddInt64(&counter, 1))
|
|
}()
|
|
|
|
wg.Wait()
|
|
```
|
|
|
|
## Debugging
|
|
|
|
### View All Configuration
|
|
|
|
```go
|
|
// Debug output
|
|
fmt.Println(cfg.Debug())
|
|
|
|
// Dump as TOML
|
|
cfg.Dump() // Writes to stdout
|
|
```
|
|
|
|
### Clone for Testing
|
|
|
|
```go
|
|
// 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 |