1 - API Reference
Complete API documentation for the Config type and methods
Complete API reference for the Config struct and all its methods.
Types
Config
type Config struct {
// contains filtered or unexported fields
}
Main configuration container. Thread-safe for concurrent read operations and loading.
Key properties:
- Thread-safe for concurrent
Load() and getter operations. - Nil-safe. All getter methods handle nil instances gracefully.
- Hierarchical data storage with dot notation support.
ConfigError
type ConfigError struct {
Source string // Where the error occurred (e.g., "source[0]", "json-schema")
Field string // Specific field with the error (optional)
Operation string // Operation being performed (e.g., "load", "validate")
Err error // Underlying error
}
Error type providing detailed context about configuration errors.
Example error messages:
config error in source[0] during load: file not found: config.yaml
config error in json-schema during validate: server.port: must be >= 1
config error in binding during bind: failed to decode configuration
Initialization Functions
New
func New(options ...Option) (*Config, error)
Creates a new Config instance with the given options. Returns an error if any option fails.
Parameters:
options - Variable number of Option functions.
Returns:
*Config - Initialized configuration instance.error - Error if initialization fails.
Example:
cfg, err := config.New(
config.WithFile("config.yaml"),
config.WithEnv("APP_"),
)
if err != nil {
log.Fatalf("failed to create config: %v", err)
}
Use when: You need explicit error handling. Recommended for libraries.
MustNew
func MustNew(options ...Option) *Config
Creates a new Config instance with the given options. Panics if any option fails.
Parameters:
options - Variable number of Option functions
Returns:
*Config - Initialized configuration instance
Panics: If any option returns an error
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithEnv("APP_"),
)
Use when: In main() or initialization code where panic is acceptable.
Lifecycle Methods
Load
func (c *Config) Load(ctx context.Context) error
Loads configuration from all configured sources, merges them, and runs validation.
Parameters:
ctx - Context for cancellation and deadlines (must not be nil)
Returns:
error - ConfigError if loading, merging, or validation fails
Behavior:
- Loads data from all sources sequentially
- Merges data hierarchically (later sources override earlier ones)
- Runs JSON Schema validation (if configured)
- Runs custom validation functions (if configured)
- Binds to struct (if configured)
- Runs struct
Validate() method (if implemented)
Example:
if err := cfg.Load(context.Background()); err != nil {
log.Fatalf("failed to load config: %v", err)
}
Thread-safety: Safe for concurrent calls (uses internal locking).
Dump
func (c *Config) Dump(ctx context.Context) error
Writes the current configuration state to all configured dumpers.
Parameters:
ctx - Context for cancellation and deadlines (must not be nil)
Returns:
error - Error if any dumper fails
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithFileDumper("output.yaml"),
)
cfg.Load(context.Background())
cfg.Dump(context.Background()) // Writes to output.yaml
Use cases: Debugging, configuration snapshots, generating configuration files.
Getter Methods
Get
func (c *Config) Get(key string) any
Retrieves the value at the given key path. Returns nil for missing keys.
Parameters:
key - Dot-separated path (e.g., “server.port”)
Returns:
any - Value at the key, or nil if not found
Nil-safe: Returns nil if Config instance is nil.
Example:
value := cfg.Get("server.port")
if port, ok := value.(int); ok {
fmt.Printf("Port: %d\n", port)
}
String
func (c *Config) String(key string) string
Retrieves a string value at the given key.
Returns: Empty string "" if key not found or on nil instance.
Example:
host := cfg.String("server.host") // "" if missing
Int
func (c *Config) Int(key string) int
Retrieves an integer value at the given key.
Returns: 0 if key not found or on nil instance.
Int64
func (c *Config) Int64(key string) int64
Retrieves an int64 value at the given key.
Returns: 0 if key not found or on nil instance.
Float64
func (c *Config) Float64(key string) float64
Retrieves a float64 value at the given key.
Returns: 0.0 if key not found or on nil instance.
Bool
func (c *Config) Bool(key string) bool
Retrieves a boolean value at the given key.
Returns: false if key not found or on nil instance.
Duration
func (c *Config) Duration(key string) time.Duration
Retrieves a time.Duration value at the given key. Supports duration strings like “30s”, “5m”, “1h”.
Returns: 0 if key not found or on nil instance.
Example:
timeout := cfg.Duration("server.timeout") // Parses "30s" to 30 * time.Second
Time
func (c *Config) Time(key string) time.Time
Retrieves a time.Time value at the given key.
Returns: Zero time (time.Time{}) if key not found or on nil instance.
StringSlice
func (c *Config) StringSlice(key string) []string
Retrieves a string slice at the given key.
Returns: Empty slice []string{} (not nil) if key not found or on nil instance.
Example:
hosts := cfg.StringSlice("servers") // []string{} if missing
IntSlice
func (c *Config) IntSlice(key string) []int
Retrieves an integer slice at the given key.
Returns: Empty slice []int{} (not nil) if key not found or on nil instance.
StringMap
func (c *Config) StringMap(key string) map[string]any
Retrieves a map at the given key.
Returns: Empty map map[string]any{} (not nil) if key not found or on nil instance.
Example:
metadata := cfg.StringMap("metadata") // map[string]any{} if missing
Getter Methods with Defaults
StringOr
func (c *Config) StringOr(key, defaultVal string) string
Retrieves a string value or returns the default if not found.
Example:
host := cfg.StringOr("server.host", "localhost")
IntOr
func (c *Config) IntOr(key string, defaultVal int) int
Retrieves an integer value or returns the default if not found.
Example:
port := cfg.IntOr("server.port", 8080)
Int64Or
func (c *Config) Int64Or(key string, defaultVal int64) int64
Retrieves an int64 value or returns the default if not found.
Float64Or
func (c *Config) Float64Or(key string, defaultVal float64) float64
Retrieves a float64 value or returns the default if not found.
BoolOr
func (c *Config) BoolOr(key string, defaultVal bool) bool
Retrieves a boolean value or returns the default if not found.
Example:
debug := cfg.BoolOr("debug", false)
DurationOr
func (c *Config) DurationOr(key string, defaultVal time.Duration) time.Duration
Retrieves a duration value or returns the default if not found.
Example:
timeout := cfg.DurationOr("timeout", 30*time.Second)
TimeOr
func (c *Config) TimeOr(key string, defaultVal time.Time) time.Time
Retrieves a time.Time value or returns the default if not found.
StringSliceOr
func (c *Config) StringSliceOr(key string, defaultVal []string) []string
Retrieves a string slice or returns the default if not found.
IntSliceOr
func (c *Config) IntSliceOr(key string, defaultVal []int) []int
Retrieves an integer slice or returns the default if not found.
StringMapOr
func (c *Config) StringMapOr(key string, defaultVal map[string]any) map[string]any
Retrieves a map or returns the default if not found.
Generic Getter Functions
GetE
func GetE[T any](c *Config, key string) (T, error)
Generic getter that returns the value and an error. Useful for custom types and explicit error handling.
Type parameters:
Parameters:
c - Config instancekey - Dot-separated path
Returns:
T - Value at the key (zero value if error)error - Error if key not found, type mismatch, or nil instance
Example:
port, err := config.GetE[int](cfg, "server.port")
if err != nil {
log.Printf("invalid port: %v", err)
port = 8080
}
// Custom type
type DatabaseConfig struct {
Host string
Port int
}
dbConfig, err := config.GetE[DatabaseConfig](cfg, "database")
GetOr
func GetOr[T any](c *Config, key string, defaultVal T) T
Generic getter that returns the value or a default if not found.
Example:
port := config.GetOr(cfg, "server.port", 8080)
Get
func Get[T any](c *Config, key string) T
Generic getter that returns the value or zero value if not found.
Example:
port := config.Get[int](cfg, "server.port") // 0 if missing
Data Access Methods
Values
func (c *Config) Values() *map[string]any
Returns a pointer to the internal configuration map.
Returns: nil if Config instance is nil
Warning: Direct modification of the returned map is not recommended. Use for read-only operations.
Example:
values := cfg.Values()
if values != nil {
fmt.Printf("Config data: %+v\n", *values)
}
Nil-Safety Guarantees
All getter methods handle nil Config instances gracefully:
var cfg *config.Config // nil
// Short methods return zero values
cfg.String("key") // Returns ""
cfg.Int("key") // Returns 0
cfg.Bool("key") // Returns false
cfg.StringSlice("key") // Returns []string{}
cfg.StringMap("key") // Returns map[string]any{}
// Error methods return errors
port, err := config.GetE[int](cfg, "key")
// err: "config instance is nil"
Thread Safety
Thread-safe operations:
Load() - Uses internal locking- All getter methods - Read-only operations are safe
- Multiple goroutines can call
Load() and getters concurrently
Not thread-safe:
- Concurrent modification during initialization
- Direct modification of values returned by
Values()
Error Handling Patterns
Pattern 1: Simple Access
port := cfg.Int("server.port") // Use zero value as implicit default
Pattern 2: Explicit Defaults
port := cfg.IntOr("server.port", 8080) // Explicit default
Pattern 3: Error Handling
port, err := config.GetE[int](cfg, "server.port")
if err != nil {
return fmt.Errorf("invalid port: %w", err)
}
Pattern 4: Load Errors
if err := cfg.Load(context.Background()); err != nil {
var configErr *config.ConfigError
if errors.As(err, &configErr) {
log.Printf("Config error in %s during %s: %v",
configErr.Source, configErr.Operation, configErr.Err)
}
return err
}
| Operation | Complexity | Notes |
|---|
Get(key) | O(n) | n = depth of dot notation path |
String(key), Int(key), etc. | O(n) | Uses Get() internally |
Load() | O(s × m) | s = number of sources, m = data size |
Dump() | O(d × m) | d = number of dumpers, m = data size |
Next Steps
2 - Options Reference
Complete reference for all configuration option functions
Comprehensive documentation of all option functions used to configure Config instances.
Option Type
type Option func(*Config) error
Options are functions that configure a Config instance during initialization. They are passed to New() or MustNew().
Environment Variable Expansion
All path-based options (WithFile, WithFileAs, WithConsul, WithConsulAs, WithFileDumper, WithFileDumperAs) support environment variable expansion in paths. This makes it easy to use different paths based on your environment.
Supported syntax:
${VAR} - Braced variable name$VAR - Simple variable name
Note: Shell-style defaults like ${VAR:-default} are NOT supported. Set defaults in your code before calling the option.
Examples:
// Environment-based Consul path
config.WithConsul("${APP_ENV}/service.yaml")
// When APP_ENV=production, expands to: "production/service.yaml"
// Config directory from environment
config.WithFile("${CONFIG_DIR}/app.yaml")
// When CONFIG_DIR=/etc/myapp, expands to: "/etc/myapp/app.yaml"
// Multiple variables
config.WithFile("${REGION}/${ENV}/settings.yaml")
// When REGION=us-west and ENV=staging, expands to: "us-west/staging/settings.yaml"
// Output directory
config.WithFileDumper("${LOG_DIR}/effective-config.yaml")
// When LOG_DIR=/var/log, expands to: "/var/log/effective-config.yaml"
Handling unset variables:
If an environment variable is not set, it expands to an empty string:
// If APP_ENV is not set:
config.WithConsul("${APP_ENV}/service.yaml") // Expands to: "/service.yaml"
To provide defaults, set them in your code:
if os.Getenv("APP_ENV") == "" {
os.Setenv("APP_ENV", "development")
}
config.WithConsul("${APP_ENV}/service.yaml") // Uses "development" if not set
Source Options
Source options specify where configuration data comes from.
WithFile
func WithFile(path string) Option
Loads configuration from a file with automatic format detection based on extension.
Parameters:
path - Path to configuration file.
Supported extensions:
.json - JSON format..yaml, .yml - YAML format..toml - TOML format.
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithFile("config.json"),
)
Error conditions:
- File does not exist (error occurs during
Load(), not initialization) - Extension not recognized
WithFileAs
func WithFileAs(path string, codecType codec.Type) Option
Loads configuration from a file with explicit format specification.
Parameters:
path - Path to configuration file.codecType - Codec type like codec.TypeYAML or codec.TypeJSON.
Example:
cfg := config.MustNew(
config.WithFileAs("config.txt", codec.TypeYAML),
config.WithFileAs("settings.conf", codec.TypeJSON),
)
Use when: File extension doesn’t match its format.
WithEnv
func WithEnv(prefix string) Option
Loads configuration from environment variables with the given prefix.
Parameters:
prefix - Prefix to filter environment variables (e.g., “APP_”, “MYAPP_”)
Naming convention:
PREFIX_KEY → keyPREFIX_SECTION_KEY → section.keyPREFIX_A_B_C → a.b.c
Example:
cfg := config.MustNew(
config.WithEnv("MYAPP_"),
)
// Environment: MYAPP_SERVER_PORT=8080
// Maps to: server.port = 8080
See also: Environment Variables Guide
WithConsul
func WithConsul(path string) Option
Loads configuration from HashiCorp Consul. The format is detected from the file extension.
Works without Consul: If CONSUL_HTTP_ADDR isn’t set, this option does nothing. This means you can run your app locally without Consul. When you deploy to production, just set the environment variable and Consul will be used.
Parameters:
path - Consul key path (format detected from extension)
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithConsul("production/service.json"), // Skipped in dev, used in prod
)
Environment variables:
CONSUL_HTTP_ADDR - Consul server address (required for Consul to work)CONSUL_HTTP_TOKEN - Access token for authentication (optional)
WithConsulAs
func WithConsulAs(path string, codecType codec.Type) Option
Loads configuration from Consul with explicit format. Use this when the key path doesn’t have an extension.
Works without Consul: Like WithConsul, this option does nothing if CONSUL_HTTP_ADDR isn’t set. Your code works the same in dev and prod.
Parameters:
path - Consul key pathcodecType - Codec type (like codec.TypeYAML or codec.TypeJSON)
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithConsulAs("config/app", codec.TypeYAML), // No extension in key
)
Environment variables:
CONSUL_HTTP_ADDR - Consul server address (required for Consul to work)CONSUL_HTTP_TOKEN - Access token for authentication (optional)
WithContent
func WithContent(data []byte, codecType codec.Type) Option
Loads configuration from a byte slice.
Parameters:
data - Configuration data as bytescodecType - Codec type for decoding
Example:
configData := []byte(`{"server": {"port": 8080}}`)
cfg := config.MustNew(
config.WithContent(configData, codec.TypeJSON),
)
Use cases:
- Testing
- Dynamic configuration
- Embedded configuration
WithSource
func WithSource(loader Source) Option
Adds a custom configuration source.
Parameters:
loader - Custom source implementing the Source interface
Source interface:
type Source interface {
Load(ctx context.Context) (map[string]any, error)
}
Example:
type CustomSource struct{}
func (s *CustomSource) Load(ctx context.Context) (map[string]any, error) {
return map[string]any{"key": "value"}, nil
}
cfg := config.MustNew(
config.WithSource(&CustomSource{}),
)
Validation Options
Validation options enable configuration validation.
WithBinding
func WithBinding(v any) Option
Binds configuration to a Go struct and optionally validates it.
Parameters:
v - Pointer to struct to bind configuration to
Example:
type Config struct {
Port int `config:"port"`
}
var cfg Config
config := config.MustNew(
config.WithFile("config.yaml"),
config.WithBinding(&cfg),
)
Validation: If the struct implements Validate() error, it will be called after binding.
Requirements:
- Must pass a pointer to the struct
- Struct fields must have
config:"name" tags
See also: Struct Binding Guide
WithTag
func WithTag(tagName string) Option
Changes the struct tag name used for binding (default: “config”).
Parameters:
tagName - Tag name to use instead of “config”
Example:
type Config struct {
Port int `yaml:"port"`
}
var cfg Config
config := config.MustNew(
config.WithTag("yaml"),
config.WithBinding(&cfg),
)
Use when: You want to reuse existing struct tags (e.g., json, yaml).
WithValidator
func WithValidator(fn func(map[string]any) error) Option
Registers a custom validation function for the configuration map.
Parameters:
fn - Validation function that receives the merged configuration
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithValidator(func(data map[string]any) error {
port, ok := data["port"].(int)
if !ok || port <= 0 {
return errors.New("port must be a positive integer")
}
return nil
}),
)
Timing: Validation runs after sources are merged, before struct binding.
Multiple validators: You can register multiple validators; all will be executed.
WithJSONSchema
func WithJSONSchema(schema []byte) Option
Validates configuration against a JSON Schema.
Parameters:
schema - JSON Schema as bytes
Example:
schemaBytes, _ := os.ReadFile("schema.json")
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithJSONSchema(schemaBytes),
)
Schema validation:
See also: Validation Guide
Dumper Options
Dumper options specify where to write configuration.
WithFileDumper
func WithFileDumper(path string) Option
Writes configuration to a file with automatic format detection.
Parameters:
path - Output file path (format detected from extension)
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithEnv("APP_"),
config.WithFileDumper("effective-config.yaml"),
)
cfg.Load(context.Background())
cfg.Dump(context.Background()) // Writes to effective-config.yaml
Default permissions: 0644 (owner read/write, group/others read)
WithFileDumperAs
func WithFileDumperAs(path string, codecType codec.Type) Option
Writes configuration to a file with explicit format specification.
Parameters:
path - Output file pathcodecType - Codec type for encoding
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithFileDumperAs("output.json", codec.TypeJSON),
)
WithDumper
func WithDumper(dumper Dumper) Option
Adds a custom configuration dumper.
Parameters:
dumper - Custom dumper implementing the Dumper interface
Dumper interface:
type Dumper interface {
Dump(ctx context.Context, data map[string]any) error
}
Example:
type CustomDumper struct{}
func (d *CustomDumper) Dump(ctx context.Context, data map[string]any) error {
// Write data somewhere
return nil
}
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithDumper(&CustomDumper{}),
)
Option Composition
Options are applied in the order they are passed to New() or MustNew():
cfg := config.MustNew(
// 1. Load base config
config.WithFile("config.yaml"),
// 2. Load environment-specific config
config.WithFile("config.prod.yaml"),
// 3. Override with environment variables (highest priority)
config.WithEnv("APP_"),
// 4. Set up validation
config.WithJSONSchema(schemaBytes),
config.WithValidator(customValidation),
// 5. Bind to struct
config.WithBinding(&appConfig),
// 6. Set up dumper
config.WithFileDumper("effective-config.yaml"),
)
Source Precedence
When multiple sources are configured, later sources override earlier ones:
cfg := config.MustNew(
config.WithFile("config.yaml"), // Priority 1 (lowest)
config.WithFile("config.prod.yaml"), // Priority 2
config.WithEnv("APP_"), // Priority 3 (highest)
)
Validation Order
Validation happens in this sequence during Load():
- Load and merge all sources
- JSON Schema validation (if configured)
- Custom validation functions (if configured)
- Struct binding (if configured)
- Struct
Validate() method (if implemented)
Common Patterns
Pattern 1: Basic Configuration
cfg := config.MustNew(
config.WithFile("config.yaml"),
)
Pattern 2: Environment Override
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithEnv("APP_"),
)
Pattern 3: Multi-Environment
env := os.Getenv("APP_ENV")
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithFile("config."+env+".yaml"),
config.WithEnv("APP_"),
)
Pattern 4: With Validation
var appConfig AppConfig
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithEnv("APP_"),
config.WithBinding(&appConfig),
)
Pattern 5: Production Setup
var appConfig AppConfig
schemaBytes, _ := os.ReadFile("schema.json")
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithEnv("APP_"),
config.WithJSONSchema(schemaBytes),
config.WithBinding(&appConfig),
config.WithFileDumper("effective-config.yaml"),
)
Next Steps
3 - Codecs Reference
Built-in codecs for configuration format support and type conversion
Complete reference for built-in codecs and guidance on creating custom codecs.
Codec Interface
type Codec interface {
Encode(v any) ([]byte, error)
Decode(data []byte, v any) error
}
Codecs handle encoding and decoding of configuration data between different formats.
JSON Codec
Type: codec.TypeJSON
Import: rivaas.dev/config/codec
Handles JSON format encoding and decoding.
Capabilities:
File extensions:
Example:
import "rivaas.dev/config/codec"
cfg := config.MustNew(
config.WithFileAs("config.txt", codec.TypeJSON),
)
Features:
- Standard Go
encoding/json implementation - Preserves JSON types (numbers, strings, booleans, arrays, objects)
- Pretty-printed output when encoding
YAML Codec
Type: codec.TypeYAML
Import: rivaas.dev/config/codec
Handles YAML format encoding and decoding.
Capabilities:
File extensions:
Example:
cfg := config.MustNew(
config.WithFile("config.yaml"),
)
Features:
- Uses gopkg.in/yaml.v3
- Supports YAML 1.2 features
- Handles anchors and aliases
- Preserves indentation on encoding
Common YAML types:
string_value: "hello"
number_value: 42
boolean_value: true
duration_value: 30s
list_value:
- item1
- item2
map_value:
key1: value1
key2: value2
TOML Codec
Type: codec.TypeTOML
Import: rivaas.dev/config/codec
Handles TOML format encoding and decoding.
Capabilities:
File extensions:
Example:
cfg := config.MustNew(
config.WithFile("config.toml"),
)
Features:
Sample TOML:
[server]
host = "localhost"
port = 8080
[database]
host = "db.example.com"
port = 5432
Environment Variable Codec
Type: codec.TypeEnvVar
Import: rivaas.dev/config/codec
Handles environment variable format.
Capabilities:
- ❌ Encode (returns error)
- ✅ Decode
Example:
cfg := config.MustNew(
config.WithEnv("APP_"),
)
Format:
PREFIX_SECTION_KEY=value
PREFIX_A_B_C=nested
Transformation:
- Strips prefix
- Converts to lowercase
- Splits by underscores
- Creates nested structure
See: Environment Variables Guide
Built-in Caster Codecs
Caster codecs provide automatic type conversion for getter methods.
Boolean Caster
Type: codec.TypeCasterBool
Converts values to bool.
Supported inputs:
true, "true", "True", "TRUE", 1, "1" → truefalse, "false", "False", "FALSE", 0, "0" → false
Example:
debug := cfg.Bool("debug") // Uses BoolCaster internally
Integer Casters
Convert values to integer types.
| Type | Codec | Target Type |
|---|
codec.TypeCasterInt | Int | int |
codec.TypeCasterInt8 | Int8 | int8 |
codec.TypeCasterInt16 | Int16 | int16 |
codec.TypeCasterInt32 | Int32 | int32 |
codec.TypeCasterInt64 | Int64 | int64 |
Supported inputs:
- Integer values:
42, 100 - String integers:
"42", "100" - Float values:
42.0 → 42 - String floats:
"42.0" → 42
Example:
port := cfg.Int("server.port") // Uses IntCaster
timeout := cfg.Int64("timeout_ms") // Uses Int64Caster
Unsigned Integer Casters
Convert values to unsigned integer types.
| Type | Codec | Target Type |
|---|
codec.TypeCasterUint | Uint | uint |
codec.TypeCasterUint8 | Uint8 | uint8 |
codec.TypeCasterUint16 | Uint16 | uint16 |
codec.TypeCasterUint32 | Uint32 | uint32 |
codec.TypeCasterUint64 | Uint64 | uint64 |
Supported inputs:
- Positive integers:
42, 100 - String integers:
"42", "100"
Float Casters
Convert values to floating-point types.
| Type | Codec | Target Type |
|---|
codec.TypeCasterFloat32 | Float32 | float32 |
codec.TypeCasterFloat64 | Float64 | float64 |
Supported inputs:
- Float values:
3.14, 2.5 - String floats:
"3.14", "2.5" - Integer values:
42 → 42.0 - String integers:
"42" → 42.0
Example:
ratio := cfg.Float64("ratio")
String Caster
Type: codec.TypeCasterString
Converts any value to string.
Supported inputs:
- String values:
"hello" → "hello" - Numbers:
42 → "42" - Booleans:
true → "true" - Any value with
String() method
Example:
value := cfg.String("key") // Uses StringCaster internally
Time Caster
Type: codec.TypeCasterTime
Converts values to time.Time.
Supported inputs:
- RFC3339 strings:
"2025-01-01T00:00:00Z" - ISO8601 strings:
"2025-01-01T00:00:00+00:00" - Unix timestamps:
1672531200
Example:
createdAt := cfg.Time("created_at")
Formats tried (in order):
time.RFC3339 - "2006-01-02T15:04:05Z07:00"time.RFC3339Nano - "2006-01-02T15:04:05.999999999Z07:00""2006-01-02" - Date only- Unix timestamp (integer)
Duration Caster
Type: codec.TypeCasterDuration
Converts values to time.Duration.
Supported inputs:
- Duration strings:
"30s", "5m", "1h", "2h30m" - Integer nanoseconds:
30000000000 → 30s - Float seconds:
2.5 → 2.5s
Example:
timeout := cfg.Duration("timeout") // "30s" → 30 * time.Second
Duration units:
ns - nanosecondsus or µs - microsecondsms - millisecondss - secondsm - minutesh - hours
Codec Capabilities Table
| Codec | Encode | Decode | Auto-Detect | Extensions |
|---|
| JSON | ✅ | ✅ | ✅ | .json |
| YAML | ✅ | ✅ | ✅ | .yaml, .yml |
| TOML | ✅ | ✅ | ✅ | .toml |
| EnvVar | ❌ | ✅ | ❌ | - |
| Bool | ✅ | ✅ | ❌ | - |
| Int* | ✅ | ✅ | ❌ | - |
| Uint* | ✅ | ✅ | ❌ | - |
| Float* | ✅ | ✅ | ❌ | - |
| String | ✅ | ✅ | ❌ | - |
| Time | ✅ | ✅ | ❌ | - |
| Duration | ✅ | ✅ | ❌ | - |
The config package automatically detects formats based on file extensions:
cfg := config.MustNew(
config.WithFile("config.json"), // Auto-detects JSON
config.WithFile("config.yaml"), // Auto-detects YAML
config.WithFile("config.toml"), // Auto-detects TOML
)
Detection rules:
- Check file extension
- Look up registered decoder for that extension
- Use codec if found, error if not
Override auto-detection:
cfg := config.MustNew(
config.WithFileAs("settings.txt", codec.TypeYAML),
)
Custom Codecs
Registering Custom Codecs
import "rivaas.dev/config/codec"
func init() {
codec.RegisterEncoder("myformat", MyCodec{})
codec.RegisterDecoder("myformat", MyCodec{})
}
Registration functions:
func RegisterEncoder(name string, encoder Codec)
func RegisterDecoder(name string, decoder Codec)
Custom Codec Example
type MyCodec struct{}
func (c MyCodec) Encode(v any) ([]byte, error) {
data, ok := v.(map[string]any)
if !ok {
return nil, fmt.Errorf("expected map[string]any, got %T", v)
}
// Your encoding logic
var buf bytes.Buffer
// ... write to buf ...
return buf.Bytes(), nil
}
func (c MyCodec) Decode(data []byte, v any) error {
target, ok := v.(*map[string]any)
if !ok {
return fmt.Errorf("expected *map[string]any, got %T", v)
}
// Your decoding logic
result := make(map[string]any)
// ... parse data into result ...
*target = result
return nil
}
func init() {
codec.RegisterEncoder("myformat", MyCodec{})
codec.RegisterDecoder("myformat", MyCodec{})
}
See: Custom Codecs Guide
Common Patterns
cfg := config.MustNew(
config.WithFile("config.yaml"), // YAML
config.WithFile("secrets.json"), // JSON
config.WithFile("extra.toml"), // TOML
)
cfg := config.MustNew(
config.WithFileAs("config.txt", codec.TypeYAML),
)
Pattern 3: Content Source
yamlData := []byte(`server: {port: 8080}`)
cfg := config.MustNew(
config.WithContent(yamlData, codec.TypeYAML),
)
Pattern 4: Custom Codec
import _ "yourmodule/xmlcodec" // Registers custom codec
cfg := config.MustNew(
config.WithFileAs("config.xml", "xml"),
)
Type Conversion Examples
String to Duration
timeout := cfg.Duration("timeout") // 30 * time.Second
String to Int
port := cfg.Int("port") // 8080
String to Bool
debug := cfg.Bool("debug") // true
String to Time
created: "2025-01-01T00:00:00Z"
created := cfg.Time("created") // time.Time
Error Handling
Decode Errors
if err := cfg.Load(context.Background()); err != nil {
// Error format:
// "config error in source[0] during load: yaml: unmarshal error"
log.Printf("Failed to decode: %v", err)
}
Encode Errors
if err := cfg.Dump(context.Background()); err != nil {
// Error format:
// "config error in dumper[0] during dump: json: unsupported type"
log.Printf("Failed to encode: %v", err)
}
Type Conversion Errors
// For error-returning methods
port, err := config.GetE[int](cfg, "server.port")
if err != nil {
log.Printf("Invalid port: %v", err)
}
- JSON: Fast, minimal overhead
- YAML: Moderate overhead (parsing complexity)
- TOML: Fast, strict typing
- Casters: Minimal overhead, optimized for common cases
Next Steps
4 - Troubleshooting
Common issues, solutions, and frequently asked questions
Solutions to common problems and frequently asked questions about the config package.
Configuration Loading Issues
File Not Found
Problem: Configuration file cannot be found.
config error in source[0] during load: open config.yaml: no such file or directory
Solutions:
- Check file path: Ensure the path is correct relative to where your application runs.
// Use absolute path if needed
cfg := config.MustNew(
config.WithFile("/absolute/path/to/config.yaml"),
)
- Check working directory: Verify your application’s working directory.
wd, _ := os.Getwd()
fmt.Printf("Working directory: %s\n", wd)
- Make file optional: Handle missing files gracefully.
cfg, err := config.New(
config.WithFile("config.yaml"),
)
if err != nil {
log.Printf("Config file not found, using defaults: %v", err)
// Use defaults
}
Problem: File extension doesn’t match a known format.
config error in source[0] during load: no decoder registered for extension .conf
Solutions:
- Use explicit format:
cfg := config.MustNew(
config.WithFileAs("config.conf", codec.TypeYAML),
)
- Register custom codec:
import _ "yourmodule/mycodec" // Registers .conf format
Parse Errors
Problem: Configuration file has syntax errors.
config error in source[0] during load: yaml: unmarshal error
Solutions:
- Validate YAML/JSON syntax: Use online validators or linters
- Check indentation: YAML is indentation-sensitive
- Quote strings: Quote values with special characters
# Bad
url: http://example.com:8080
# Good
url: "http://example.com:8080"
Struct Binding Issues
Struct Not Populating
Problem: Struct fields remain at zero values after loading.
Solutions:
- Pass pointer to struct:
// Wrong
cfg := config.MustNew(config.WithBinding(myConfig))
// Correct
cfg := config.MustNew(config.WithBinding(&myConfig))
- Check struct tags:
// Config file: server.port = 8080
type Config struct {
// Wrong - doesn't match config structure
Port int `config:"port"`
// Correct - matches nested structure
Server struct {
Port int `config:"port"`
} `config:"server"`
}
- Verify tag names match config keys:
# config.yaml
server:
host: localhost
port: 8080
type Config struct {
Server struct {
Host string `config:"host"` // Must match "host" in YAML
Port int `config:"port"` // Must match "port" in YAML
} `config:"server"` // Must match "server" in YAML
}
- Export struct fields: Fields must be exported (start with uppercase)
// Wrong - unexported fields won't be populated
type Config struct {
port int `config:"port"`
}
// Correct - exported field
type Config struct {
Port int `config:"port"`
}
Type Mismatch Errors
Problem: Configuration value type doesn’t match struct field type.
Solutions:
- Use compatible types: Ensure types can be converted
# config.yaml
port: "8080" # String
type Config struct {
Port int `config:"port"` // Will be converted from string
}
- Check slice vs scalar: Don’t mix slice and scalar values
# Wrong - port is an array but struct expects int
ports:
- 8080
- 8081
type Config struct {
Ports []int `config:"ports"` // Correct - expects slice
}
Validation Errors
Problem: Struct validation fails.
config error in binding during validate: port must be positive
Solutions:
- Check validation logic:
func (c *Config) Validate() error {
if c.Port <= 0 {
return fmt.Errorf("port must be positive, got %d", c.Port)
}
return nil
}
Provide helpful error messages: Include the actual value in error
Check validation order: Validation runs after binding
Environment Variable Issues
Environment Variables Not Loading
Problem: Environment variables are not being picked up.
Solutions:
- Check prefix: Ensure environment variables have the correct prefix
# Wrong - missing prefix
export SERVER_PORT=8080
# Correct - with MYAPP_ prefix
export MYAPP_SERVER_PORT=8080
cfg := config.MustNew(
config.WithEnv("MYAPP_"), // Must match prefix
)
- Verify environment variables are set:
- Check variable names: Use underscores for nesting
# Maps to server.port
export MYAPP_SERVER_PORT=8080
# Maps to database.primary.host
export MYAPP_DATABASE_PRIMARY_HOST=localhost
Environment Variable Mapping Issues
Problem: Environment variables aren’t mapping to the right config keys.
Solutions:
- Understand naming convention:
| Environment Variable | Config Path |
|---|
MYAPP_SERVER_PORT | server.port |
MYAPP_FOO_BAR_BAZ | foo.bar.baz |
MYAPP_FOO__BAR | foo.bar (double underscore) |
- Check case sensitivity: Environment variables are converted to lowercase
export MYAPP_SERVER_PORT=8080 # Becomes: server.port
- Test mapping:
cfg := config.MustNew(
config.WithEnv("MYAPP_"),
)
cfg.Load(context.Background())
// Print effective configuration
values := cfg.Values()
fmt.Printf("Config: %+v\n", *values)
Type Conflicts
Problem: Environment variable creates conflict between scalar and nested.
export MYAPP_FOO=scalar
export MYAPP_FOO_BAR=nested
Solution: Nested structures take precedence. Result is foo.bar = "nested", scalar foo is overwritten.
Best practice: Don’t create such conflicts; structure your configuration hierarchically.
Validation Issues
Schema Validation Failures
Problem: JSON Schema validation fails.
config error in json-schema during validate: server.port: must be >= 1
Solutions:
Check schema requirements: Ensure configuration meets schema constraints
Debug with schema validator: Use online JSON Schema validators
Provide all required fields:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"required": ["server", "database"]
}
Custom Validation Errors
Problem: Custom validation function fails.
Solutions:
- Add detailed error messages:
config.WithValidator(func(data map[string]any) error {
port, ok := data["port"].(int)
if !ok {
return fmt.Errorf("port must be an integer, got %T", data["port"])
}
if port < 1 || port > 65535 {
return fmt.Errorf("port must be 1-65535, got %d", port)
}
return nil
})
- Check data types: Values in map might not be expected type
// Type assertion with check
if port, ok := data["port"].(int); ok {
// Use port
}
Slow Configuration Loading
Problem: Configuration loading takes too long.
Solutions:
Reduce source count: Combine configuration files when possible
Avoid remote sources in hot paths: Cache remote configuration
Profile loading:
start := time.Now()
err := cfg.Load(context.Background())
log.Printf("Config load time: %v", time.Since(start))
- Load once: Load configuration during initialization, not per-request
Memory Usage
Problem: High memory usage.
Solutions:
Don’t keep multiple Config instances: Reuse single instance
Clear unnecessary dumpers: Only use dumpers when needed
// Development only
if debug {
cfg = config.MustNew(
config.WithFile("config.yaml"),
config.WithFileDumper("debug-config.yaml"),
)
}
Common Misconceptions
Q: Why don’t changes to config files take effect?
A: Configuration is loaded once during Load(). It’s not automatically reloaded when files change.
Solution: Reload configuration explicitly:
// Reload configuration
if err := cfg.Load(context.Background()); err != nil {
log.Printf("Failed to reload: %v", err)
}
Q: Why does my config work locally but not in Docker?
A: Likely a path or working directory issue.
Solutions:
- Use absolute paths in Docker:
cfg := config.MustNew(
config.WithFile("/app/config/config.yaml"),
)
- Set working directory in Dockerfile:
WORKDIR /app
COPY config.yaml .
- Use environment variables for container configuration:
cfg := config.MustNew(
config.WithFile("config.yaml"), // Defaults
config.WithEnv("APP_"), // Override in container
)
Q: Can I modify configuration at runtime?
A: The Config instance is read-only after loading. You need to reload to pick up changes.
Pattern for dynamic updates:
type ConfigManager struct {
cfg *config.Config
mu sync.RWMutex
}
func (cm *ConfigManager) Reload(ctx context.Context) error {
cm.mu.Lock()
defer cm.mu.Unlock()
return cm.cfg.Load(ctx)
}
func (cm *ConfigManager) Get(key string) any {
cm.mu.RLock()
defer cm.mu.RUnlock()
return cm.cfg.Get(key)
}
FAQ
Q: Is Config thread-safe?
A: Yes, Load() and all getter methods are thread-safe.
Q: What happens with nil Config instances?
A: Getter methods return zero values, error methods return errors. No panics.
Q: Can I load from multiple sources?
A: Yes, sources are merged with later sources overriding earlier ones.
Q: How do I handle secrets?
A:
- Use environment variables for secrets (not config files)
- Use secret management systems (Vault, AWS Secrets Manager)
- Never commit secrets to version control
A: Yes, using WithTag():
type Config struct {
Port int `json:"port"`
}
cfg := config.MustNew(
config.WithTag("json"),
config.WithBinding(&myConfig),
)
Q: How do I debug configuration loading?
A:
- Use
WithFileDumper() to see merged config - Print values after loading
- Check error messages for source context
cfg := config.MustNew(
config.WithFile("config.yaml"),
config.WithEnv("APP_"),
config.WithFileDumper("debug-config.yaml"),
)
if err := cfg.Load(context.Background()); err != nil {
log.Printf("Load error: %v", err)
}
cfg.Dump(context.Background()) // Writes to debug-config.yaml
values := cfg.Values()
fmt.Printf("Loaded config: %+v\n", *values)
Q: What’s the difference between Get, GetE, and GetOr?
A:
Get[T]() - Returns value or zero value (no error)GetE[T]() - Returns value and errorGetOr[T]() - Returns value or provided default
Q: Can I use config without struct binding?
A: Yes, use getter methods directly:
cfg := config.MustNew(
config.WithFile("config.yaml"),
)
cfg.Load(context.Background())
port := cfg.Int("server.port")
host := cfg.String("server.host")
Q: How do I validate required fields?
A: Use struct validation:
func (c *Config) Validate() error {
if c.Database.Host == "" {
return errors.New("database.host is required")
}
return nil
}
Configuration access:
- Getter methods: O(n) where n = dot notation depth
- Direct
Get(): O(n) - No caching of individual keys
Best practices:
- Load configuration once at startup
- Cache frequently accessed values in local variables
- Use struct binding for best performance
Thread safety overhead:
- Minimal locking overhead
- Read operations are concurrent
- Write operations (Load) use exclusive lock
Getting Help
If you encounter issues not covered here:
- Check the Configuration Guide
- Review API Reference
- Search GitHub Issues
- Ask in the community forums
When reporting issues, include:
- Go version
- Config package version
- Minimal reproducible example
- Error messages
- Expected vs actual behavior