Basic Usage

Learn the fundamentals of loading and accessing configuration with Rivaas

This guide covers the essential operations for working with the config package. Learn how to load configuration files, access values, and handle errors.

Loading Configuration Files

The config package automatically detects file formats based on the file extension. Supported formats include JSON, YAML, and TOML.

Simple File Loading

package main

import (
    "context"
    "log"
    "rivaas.dev/config"
)

func main() {
    cfg := config.MustNew(
        config.WithFile("config.yaml"),
    )

    if err := cfg.Load(context.Background()); err != nil {
        log.Fatalf("failed to load config: %v", err)
    }
}

Multiple File Formats

You can load multiple configuration files of different formats:

cfg := config.MustNew(
    config.WithFile("config.yaml"),  // YAML format
    config.WithFile("config.json"),  // JSON format
    config.WithFile("config.toml"),  // TOML format
)

Environment Variables in Paths

You can use environment variables in file paths. This is useful when different environments use different directories:

// Use ${VAR} or $VAR in paths
cfg := config.MustNew(
    config.WithFile("${CONFIG_DIR}/app.yaml"),      // Expands to actual directory
    config.WithFile("${APP_ENV}/overrides.yaml"),   // e.g., "production/overrides.yaml"
)

This works with all path-based options: WithFile, WithFileAs, WithConsul, WithConsulAs, WithFileDumper, and WithFileDumperAs.

Built-in Format Support

The config package includes built-in codecs for common formats:

FormatExtensionCodec Type
JSON.jsoncodec.TypeJSON
YAML.yaml, .ymlcodec.TypeYAML
TOML.tomlcodec.TypeTOML
Environment Variables-codec.TypeEnvVar

Accessing Configuration Values

Once loaded, access configuration using dot notation and type-safe getters.

Dot Notation

Navigate nested configuration structures using dots:

// Given config: { "database": { "host": "localhost", "port": 5432 } }
host := cfg.String("database.host")      // "localhost"
port := cfg.Int("database.port")         // 5432

Type-Safe Getters

The config package provides type-safe getters for common data types:

// Basic types
stringVal := cfg.String("key")
intVal := cfg.Int("key")
boolVal := cfg.Bool("key")
floatVal := cfg.Float64("key")

// Time and duration
duration := cfg.Duration("timeout")
timestamp := cfg.Time("created_at")

// Collections
slice := cfg.StringSlice("tags")
mapping := cfg.StringMap("metadata")

Getters with Default Values

Use Or variants to provide fallback values:

host := cfg.StringOr("server.host", "localhost")
port := cfg.IntOr("server.port", 8080)
debug := cfg.BoolOr("debug", false)
timeout := cfg.DurationOr("timeout", 30*time.Second)

Generic Getters for Custom Types

For custom types or explicit error handling, use the generic GetE function:

// With error handling
port, err := config.GetE[int](cfg, "server.port")
if err != nil {
    log.Printf("invalid port: %v", err)
    port = 8080  // fallback
}

// For custom types
type DatabaseConfig struct {
    Host string
    Port int
}

dbConfig, err := config.GetE[DatabaseConfig](cfg, "database")
if err != nil {
    log.Fatalf("invalid database config: %v", err)
}

Error Handling

The config package provides comprehensive error handling through different getter variants.

Short Form (No Error)

Short methods return zero values for missing keys:

cfg.String("nonexistent")      // Returns ""
cfg.Int("nonexistent")         // Returns 0
cfg.Bool("nonexistent")        // Returns false
cfg.StringSlice("nonexistent") // Returns []string{}
cfg.StringMap("nonexistent")   // Returns map[string]any{}

This approach is ideal when you want simple access with sensible defaults.

Default Value Form (Or Methods)

Or methods provide explicit fallback values:

host := cfg.StringOr("host", "localhost")        // Returns "localhost" if missing
port := cfg.IntOr("port", 8080)                  // Returns 8080 if missing
debug := cfg.BoolOr("debug", false)              // Returns false if missing
timeout := cfg.DurationOr("timeout", 30*time.Second) // Returns 30s if missing

Error Returning Form (E Methods)

Use GetE for explicit error handling:

port, err := config.GetE[int](cfg, "server.port")
if err != nil {
    return fmt.Errorf("invalid port configuration: %w", err)
}

// Errors provide context
// Example: "config error: key 'server.port' not found"

ConfigError Structure

When errors occur during loading, they’re wrapped in ConfigError:

type ConfigError struct {
    Source    string // Where the error occurred (e.g., "source[0]")
    Field     string // Specific field with the error
    Operation string // Operation being performed (e.g., "load")
    Err       error  // Underlying error
}

Example error handling during load:

if err := cfg.Load(context.Background()); err != nil {
    // Error message includes context:
    // "config error in source[0] during load: file not found: config.yaml"
    log.Fatalf("configuration error: %v", err)
}

Nil-Safe Operations

All getter methods handle nil Config instances gracefully:

var cfg *config.Config  // nil

// Short methods return zero values (no panic)
cfg.String("key")       // Returns ""
cfg.Int("key")          // Returns 0
cfg.Bool("key")         // Returns false

// Error methods return errors
port, err := config.GetE[int](cfg, "key")
// err: "config instance is nil"

Complete Example

Putting it all together:

package main

import (
    "context"
    "log"
    "time"
    "rivaas.dev/config"
)

func main() {
    // Create config with file source
    cfg := config.MustNew(
        config.WithFile("config.yaml"),
    )

    // Load configuration
    if err := cfg.Load(context.Background()); err != nil {
        log.Fatalf("failed to load config: %v", err)
    }

    // Access values with different approaches
    
    // Simple access (with zero values for missing keys)
    host := cfg.String("server.host")
    
    // With defaults
    port := cfg.IntOr("server.port", 8080)
    debug := cfg.BoolOr("debug", false)
    
    // With error handling
    timeout, err := config.GetE[time.Duration](cfg, "server.timeout")
    if err != nil {
        log.Printf("using default timeout: %v", err)
        timeout = 30 * time.Second
    }
    
    log.Printf("Server: %s:%d (debug: %v, timeout: %v)", 
        host, port, debug, timeout)
}

Sample config.yaml:

server:
  host: localhost
  port: 8080
  timeout: 30s
debug: true

Next Steps

For complete API details, see the API Reference.