Files
config/doc/access.md

7.5 KiB

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

// 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:

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

// 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

// 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

// 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

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

// 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

// 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

// 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:

// 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

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

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

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

// 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

// 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

// 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

// 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

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

// 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:

// 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

// Debug output
fmt.Println(cfg.Debug())

// Dump as TOML
cfg.Dump()  // Writes to stdout

Clone for Testing

// Create isolated copy for testing
testCfg := cfg.Clone()
testCfg.Set("server.port", int64(0))  // Random port for tests

See Also