config

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 29, 2025 License: Apache-2.0 Imports: 20 Imported by: 0

README

Config

Go Reference Go Report Card Go Version License

A powerful and versatile configuration management package for Go that simplifies handling application settings across different environments and formats.

📚 Complete Documentation →

Documentation

This README provides a quick overview. For comprehensive guides, tutorials, and API reference:

Features

  • Easy Integration: Simple and intuitive API
  • Flexible Sources: Files, environment variables, Consul, custom sources
  • Format Agnostic: JSON, YAML, TOML, and extensible codecs
  • Type Casting: Automatic type conversion (bool, int, float, time, duration)
  • Hierarchical Merging: Multiple sources merged with precedence
  • Struct Binding: Automatic mapping to Go structs
  • Built-in Validation: Struct methods, JSON Schemas, custom functions
  • Dot Notation: Easy nested configuration access
  • Configuration Dumping: Save effective configuration
  • Thread-Safe: Safe for concurrent access
  • Nil-Safe: Graceful handling of nil instances

Installation

go get rivaas.dev/config

Quick Start

package main

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

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

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

    port := cfg.Int("server.port")
    host := cfg.StringOr("server.host", "localhost")
    
    log.Printf("Server: %s:%d", host, port)
}

See more examples →

Learn More

Contributing

Contributions are welcome! Please see the main repository for contribution guidelines.

License

Apache License 2.0 - see LICENSE for details.

Documentation

Overview

Package config provides configuration management for Go applications.

The config package loads configuration data from multiple sources including files, environment variables, and remote systems like Consul. Configuration sources are merged in order, with later sources overriding earlier ones. All configuration keys are case-insensitive.

Key Features

  • Multiple configuration sources (files, environment variables, Consul)
  • Automatic format detection and decoding (JSON, YAML, TOML)
  • Struct binding with automatic type conversion
  • Validation using JSON Schema or custom validators
  • Case-insensitive key access with dot notation
  • Thread-safe configuration loading and access
  • Configuration dumping to files or custom destinations

Quick Start

Create a configuration instance with sources:

cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithEnv("APP_"),
)

Load the configuration:

if err := cfg.Load(context.Background()); err != nil {
    log.Fatal(err)
}

Access configuration values:

port := cfg.Int("server.port")
host := cfg.StringOr("server.host", "localhost")
debug := cfg.Bool("debug")

Configuration Sources

The package supports multiple configuration sources that can be combined:

Files with automatic format detection:

config.WithFile("config.yaml")     // Detects YAML
config.WithFile("config.json")     // Detects JSON
config.WithFile("config.toml")     // Detects TOML

Files with explicit format:

config.WithFileAs("config", codec.TypeYAML)

Environment variables with prefix:

config.WithEnv("APP_")  // Loads APP_SERVER_PORT as server.port

Consul key-value store:

config.WithConsul("production/service.yaml")

Raw content:

yamlData := []byte("port: 8080")
config.WithContent(yamlData, codec.TypeYAML)

Struct Binding

Bind configuration to a struct for type-safe access:

type AppConfig struct {
    Port    int           `config:"port"`
    Host    string        `config:"host"`
    Timeout time.Duration `config:"timeout"`
    Debug   bool          `config:"debug" default:"false"`
}

var appConfig AppConfig
cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithBinding(&appConfig),
)

if err := cfg.Load(context.Background()); err != nil {
    log.Fatal(err)
}

// Access typed fields directly
fmt.Printf("Server: %s:%d\n", appConfig.Host, appConfig.Port)

Validation

Validate configuration using struct methods:

type Config struct {
    Port int `config:"port"`
}

func (c *Config) Validate() error {
    if c.Port < 1 || c.Port > 65535 {
        return fmt.Errorf("port must be between 1 and 65535")
    }
    return nil
}

Validate using JSON Schema:

schema := []byte(`{
    "type": "object",
    "properties": {
        "port": {"type": "integer", "minimum": 1, "maximum": 65535}
    },
    "required": ["port"]
}`)

cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithJSONSchema(schema),
)

Validate using custom functions:

cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithValidator(func(values map[string]any) error {
        if port, ok := values["port"].(int); ok && port < 1 {
            return fmt.Errorf("invalid port: %d", port)
        }
        return nil
    }),
)

Accessing Configuration Values

Access values using type-specific methods:

// Basic types
port := cfg.Int("server.port")
host := cfg.String("server.host")
debug := cfg.Bool("debug")
rate := cfg.Float64("rate")

// With default values
host := cfg.StringOr("server.host", "localhost")
port := cfg.IntOr("server.port", 8080)

// Collections
tags := cfg.StringSlice("tags")
ports := cfg.IntSlice("ports")
metadata := cfg.StringMap("metadata")

// Time-related
timeout := cfg.Duration("timeout")
startTime := cfg.Time("start_time")

Using generic functions with error handling:

port, err := config.GetE[int](cfg, "server.port")
if err != nil {
    log.Fatalf("port configuration required: %v", err)
}

Configuration Dumping

Save the current configuration to a file:

cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithFileDumper("output.yaml"),
)

cfg.Load(context.Background())
cfg.Dump(context.Background())  // Writes to output.yaml

Thread Safety

Config is safe for concurrent use by multiple goroutines. Configuration loading and reading are protected by internal locks. Multiple goroutines can safely call Load() and access configuration values simultaneously.

Error Handling

The package provides detailed error information through ConfigError:

if err := cfg.Load(ctx); err != nil {
    var configErr *config.ConfigError
    if errors.As(err, &configErr) {
        fmt.Printf("Error in %s during %s: %v\n",
            configErr.Source, configErr.Operation, configErr.Err)
    }
}

Examples

See the examples directory for complete working examples demonstrating various configuration patterns and use cases including:

  • Basic configuration loading from files
  • Environment variable overrides
  • Struct binding with validation
  • JSON Schema validation
  • Custom validation functions
  • Configuration dumping
  • Consul integration

For more details, see the package documentation at https://pkg.go.dev/rivaas.dev/config

Example

Example demonstrates basic configuration usage.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	// Create config with YAML content
	yamlContent := []byte(`
server:
  host: localhost
  port: 8080
database:
  name: mydb
`)

	cfg, err := config.New(
		config.WithContent(yamlContent, codec.TypeYAML),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Load configuration
	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	// Access values
	fmt.Println(cfg.String("server.host"))
	fmt.Println(cfg.Int("server.port"))
	fmt.Println(cfg.String("database.name"))

}
Output:

localhost
8080
mydb
Example (EnvironmentVariables)

Example_environmentVariables demonstrates loading configuration from environment variables.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
)

func main() {
	// In real usage, set environment variables like:
	// export APP_SERVER_HOST=localhost
	// export APP_SERVER_PORT=8080

	cfg, err := config.New(
		config.WithOSEnvVarSource("APP_"),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	// Access environment variables without the prefix
	// e.g., APP_SERVER_HOST becomes server.host
	fmt.Println("Environment variables loaded")
}
Output:

Environment variables loaded
Example (MultipleSources)

Example_multipleSources demonstrates merging multiple configuration sources.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	// Base configuration
	baseConfig := []byte(`
server:
  host: localhost
  port: 8080
`)

	// Override configuration
	overrideConfig := []byte(`
server:
  port: 9090
`)

	cfg, err := config.New(
		config.WithContent(baseConfig, codec.TypeYAML),
		config.WithContent(overrideConfig, codec.TypeYAML),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	// Later sources override earlier ones
	fmt.Println(cfg.String("server.host"))
	fmt.Println(cfg.Int("server.port"))
}
Output:

localhost
9090

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertConfigBool

func AssertConfigBool(t *testing.T, cfg *Config, key string, expected bool)

AssertConfigBool asserts that a boolean configuration value matches the expected value.

func AssertConfigInt

func AssertConfigInt(t *testing.T, cfg *Config, key string, expected int)

AssertConfigInt asserts that an integer configuration value matches the expected value.

func AssertConfigString

func AssertConfigString(t *testing.T, cfg *Config, key, expected string)

AssertConfigString asserts that a string configuration value matches the expected value.

func AssertConfigValue

func AssertConfigValue(t *testing.T, cfg *Config, key string, expected any)

AssertConfigValue asserts that a configuration value matches the expected value.

func Get

func Get[T any](c *Config, key string) T

Get returns the value associated with the given key as type T. If the key is not found or cannot be converted to type T, it returns the zero value of T. This generic function is useful for custom types or when you need type-safe access.

Example:

port := config.Get[int](cfg, "server.port")
timeout := config.Get[time.Duration](cfg, "timeout")
custom := config.Get[MyCustomType](cfg, "custom")

func GetE

func GetE[T any](c *Config, key string) (T, error)

GetE returns the value associated with the given key as type T, with error handling. If the key is not found, it returns an error. If the value cannot be converted to type T, it returns an error. This is useful when you need explicit error handling for missing or invalid configuration.

Example:

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

custom, err := config.GetE[MyCustomType](cfg, "custom")
if err != nil {
    return fmt.Errorf("failed to get custom config: %w", err)
}

func GetOr

func GetOr[T any](c *Config, key string, defaultVal T) T

GetOr returns the value associated with the given key as type T. If the key is not found or cannot be converted to type T, it returns the provided default value. The type T is inferred from the default value.

Example:

port := config.GetOr(cfg, "server.port", 8080)           // type inferred as int
host := config.GetOr(cfg, "server.host", "localhost")    // type inferred as string
timeout := config.GetOr(cfg, "timeout", 30*time.Second)  // type inferred as time.Duration

func TestDumper

func TestDumper() *mockDumper

TestDumper creates a mock dumper for testing.

func TestDumperWithError

func TestDumperWithError(err error) *mockDumper

TestDumperWithError creates a mock dumper that returns an error on Dump.

func TestJSONFile

func TestJSONFile(t *testing.T, content []byte) string

TestJSONFile creates a temporary JSON file with the given content. The file is automatically cleaned up when the test completes.

func TestTOMLFile

func TestTOMLFile(t *testing.T, content []byte) string

TestTOMLFile creates a temporary TOML file with the given content. The file is automatically cleaned up when the test completes.

func TestYAMLFile

func TestYAMLFile(t *testing.T, content []byte) string

TestYAMLFile creates a temporary YAML file with the given content. The file is automatically cleaned up when the test completes.

Types

type Config

type Config struct {
	// contains filtered or unexported fields
}

Config manages configuration data loaded from multiple sources. It provides thread-safe access to configuration values and supports binding to structs, validation, and dumping to files.

Config is safe for concurrent use by multiple goroutines.

func MustNew

func MustNew(options ...Option) *Config

MustNew creates a new Config instance with the provided options. It panics if any option returns an error. Use this in main() or initialization code where panic is acceptable. For cases where error handling is needed, use New() instead.

Example

ExampleMustNew demonstrates creating a configuration instance with panic on error.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
)

func main() {
	cfg := config.MustNew()
	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Config created successfully")
}
Output:

Config created successfully

func New

func New(options ...Option) (*Config, error)

New creates a new Config instance with the provided options. It iterates through the options and applies each one to the Config instance. If any of the options return an error, the errors are collected and returned.

Example

ExampleNew demonstrates creating a new configuration instance.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
)

func main() {
	cfg, err := config.New()
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Config created successfully")
}
Output:

Config created successfully

func TestConfig

func TestConfig(t *testing.T, opts ...Option) *Config

TestConfig creates a new Config instance with the given options for testing. It fails the test if creation fails.

func TestConfigFromJSONFile

func TestConfigFromJSONFile(t *testing.T, content []byte) *Config

TestConfigFromJSONFile creates a Config instance loaded from a temporary JSON file.

func TestConfigFromTOMLFile

func TestConfigFromTOMLFile(t *testing.T, content []byte) *Config

TestConfigFromTOMLFile creates a Config instance loaded from a temporary TOML file.

func TestConfigFromYAMLFile

func TestConfigFromYAMLFile(t *testing.T, content []byte) *Config

TestConfigFromYAMLFile creates a Config instance loaded from a temporary YAML file.

func TestConfigLoaded

func TestConfigLoaded(t *testing.T, conf map[string]any) *Config

TestConfigLoaded creates and loads a Config instance with the given configuration. Note: Uses context.Background() for simplicity. For tests needing context control, create the config manually and call Load with t.Context().

func TestConfigWithBinding

func TestConfigWithBinding(t *testing.T, conf map[string]any, target any) *Config

TestConfigWithBinding creates a Config instance with the given configuration and binding target.

func TestConfigWithSource

func TestConfigWithSource(t *testing.T, conf map[string]any) *Config

TestConfigWithSource creates a new Config instance with a mock source for testing.

func TestConfigWithValidator

func TestConfigWithValidator(t *testing.T, conf map[string]any, validator func(map[string]any) error) *Config

TestConfigWithValidator creates a Config instance with the given configuration and validator.

func (*Config) Bool

func (c *Config) Bool(key string) bool

Bool returns the value associated with the given key as a boolean. If the value is not found or cannot be converted to a boolean, false is returned.

Example:

debug := cfg.Bool("debug")
Example

ExampleConfig_Bool demonstrates retrieving boolean values.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	jsonContent := []byte(`{"debug": true, "verbose": false}`)

	cfg, err := config.New(
		config.WithContent(jsonContent, codec.TypeJSON),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println(cfg.Bool("debug"))
	fmt.Println(cfg.Bool("verbose"))
}
Output:

true
false

func (*Config) BoolOr

func (c *Config) BoolOr(key string, defaultVal bool) bool

BoolOr returns the value associated with the given key as a boolean, or the default value if not found.

Example:

debug := cfg.BoolOr("debug", false)

func (*Config) Dump

func (c *Config) Dump(ctx context.Context) error

Dump writes the current configuration values to the registered dumpers.

Errors:

  • Returns error if ctx is nil
  • Returns error if any dumper fails to write the configuration

func (*Config) Duration

func (c *Config) Duration(key string) time.Duration

Duration returns the value associated with the given key as a time.Duration. If the value is not found or cannot be converted to a time.Duration, the zero value is returned.

Example:

timeout := cfg.Duration("timeout")

func (*Config) DurationOr

func (c *Config) DurationOr(key string, defaultVal time.Duration) time.Duration

DurationOr returns the value associated with the given key as a time.Duration, or the default value if not found.

Example:

timeout := cfg.DurationOr("timeout", 30*time.Second)

func (*Config) Float64

func (c *Config) Float64(key string) float64

Float64 returns the value associated with the given key as a float64. If the value is not found or cannot be converted to a float64, 0.0 is returned.

Example:

rate := cfg.Float64("rate")

func (*Config) Float64Or

func (c *Config) Float64Or(key string, defaultVal float64) float64

Float64Or returns the value associated with the given key as a float64, or the default value if not found.

Example:

rate := cfg.Float64Or("rate", 0.5)

func (*Config) Get

func (c *Config) Get(key string) any

Get returns the value associated with the given key as an any type. If the key is not found, it returns nil.

Example

ExampleConfig_Get demonstrates retrieving configuration values.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	yamlContent := []byte(`
settings:
  enabled: true
  count: 42
`)

	cfg, err := config.New(
		config.WithContent(yamlContent, codec.TypeYAML),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println(cfg.Get("settings.enabled"))
	fmt.Println(cfg.Get("settings.count"))
}
Output:

true
42

func (*Config) Int

func (c *Config) Int(key string) int

Int returns the value associated with the given key as an int. If the value is not found or cannot be converted to an int, 0 is returned.

Example:

port := cfg.Int("server.port")
Example

ExampleConfig_Int demonstrates retrieving integer values.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	jsonContent := []byte(`{"port": 8080, "workers": 4}`)

	cfg, err := config.New(
		config.WithContent(jsonContent, codec.TypeJSON),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println(cfg.Int("port"))
	fmt.Println(cfg.Int("workers"))
}
Output:

8080
4

func (*Config) Int64

func (c *Config) Int64(key string) int64

Int64 returns the value associated with the given key as an int64. If the value is not found or cannot be converted to an int64, 0 is returned.

Example:

maxSize := cfg.Int64("max_size")

func (*Config) Int64Or

func (c *Config) Int64Or(key string, defaultVal int64) int64

Int64Or returns the value associated with the given key as an int64, or the default value if not found.

Example:

maxSize := cfg.Int64Or("max_size", 1024)

func (*Config) IntOr

func (c *Config) IntOr(key string, defaultVal int) int

IntOr returns the value associated with the given key as an int, or the default value if not found.

Example:

port := cfg.IntOr("server.port", 8080)

func (*Config) IntSlice

func (c *Config) IntSlice(key string) []int

IntSlice returns the value associated with the given key as a slice of integers. If the value is not found or cannot be converted to a slice of integers, an empty slice is returned.

Example:

ports := cfg.IntSlice("ports")

func (*Config) IntSliceOr

func (c *Config) IntSliceOr(key string, defaultVal []int) []int

IntSliceOr returns the value associated with the given key as a slice of integers, or the default value if not found.

Example:

ports := cfg.IntSliceOr("ports", []int{8080, 8081})

func (*Config) Load

func (c *Config) Load(ctx context.Context) error

Load loads configuration data from the registered sources and merges it into the internal values map. The method validates the configuration data before atomically updating the internal state. Load is safe to call concurrently.

Errors:

  • Returns error if ctx is nil
  • Returns ConfigError if any source fails to load
  • Returns ConfigError if JSON schema validation fails
  • Returns ConfigError if custom validators fail
  • Returns ConfigError if binding or struct validation fails

func (*Config) String

func (c *Config) String(key string) string

String returns the value associated with the given key as a string. If the value is not found or cannot be converted to a string, an empty string is returned.

Example:

host := cfg.String("server.host")
Example

ExampleConfig_String demonstrates retrieving string values.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	jsonContent := []byte(`{"name": "MyApp", "env": "production"}`)

	cfg, err := config.New(
		config.WithContent(jsonContent, codec.TypeJSON),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println(cfg.String("name"))
	fmt.Println(cfg.String("env"))
}
Output:

MyApp
production

func (*Config) StringMap

func (c *Config) StringMap(key string) map[string]any

StringMap returns the value associated with the given key as a map[string]any. If the value is not found or cannot be converted to a map[string]any, an empty map is returned.

Example:

metadata := cfg.StringMap("metadata")
Example

ExampleConfig_StringMap demonstrates retrieving string maps.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	yamlContent := []byte(`
metadata:
  author: John Doe
  version: 1.0.0
`)

	cfg, err := config.New(
		config.WithContent(yamlContent, codec.TypeYAML),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	metadata := cfg.StringMap("metadata")
	fmt.Println(metadata["author"])
	fmt.Println(metadata["version"])
}
Output:

John Doe
1.0.0

func (*Config) StringMapOr

func (c *Config) StringMapOr(key string, defaultVal map[string]any) map[string]any

StringMapOr returns the value associated with the given key as a map[string]any, or the default value if not found.

Example:

metadata := cfg.StringMapOr("metadata", map[string]any{"version": "1.0"})

func (*Config) StringOr

func (c *Config) StringOr(key, defaultVal string) string

StringOr returns the value associated with the given key as a string, or the default value if not found.

Example:

host := cfg.StringOr("server.host", "localhost")

func (*Config) StringSlice

func (c *Config) StringSlice(key string) []string

StringSlice returns the value associated with the given key as a slice of strings. If the value is not found or cannot be converted to a slice of strings, an empty slice is returned.

Example:

tags := cfg.StringSlice("tags")
Example

ExampleConfig_StringSlice demonstrates retrieving string slices.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	yamlContent := []byte(`
tags:
  - web
  - api
  - backend
`)

	cfg, err := config.New(
		config.WithContent(yamlContent, codec.TypeYAML),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	tags := cfg.StringSlice("tags")
	fmt.Printf("%v\n", tags)
}
Output:

[web api backend]

func (*Config) StringSliceOr

func (c *Config) StringSliceOr(key string, defaultVal []string) []string

StringSliceOr returns the value associated with the given key as a slice of strings, or the default value if not found.

Example:

tags := cfg.StringSliceOr("tags", []string{"default"})

func (*Config) Time

func (c *Config) Time(key string) time.Time

Time returns the value associated with the given key as a time.Time. If the value is not found or cannot be converted to a time.Time, the zero value is returned.

Example:

startTime := cfg.Time("start_time")

func (*Config) TimeOr

func (c *Config) TimeOr(key string, defaultVal time.Time) time.Time

TimeOr returns the value associated with the given key as a time.Time, or the default value if not found.

Example:

startTime := cfg.TimeOr("start_time", time.Now())

func (*Config) Values

func (c *Config) Values() *map[string]any

Values returns a pointer to the internal values map of the Config instance. The map is protected by a read lock, which is acquired and released within this method. This method is used to safely access the internal values map.

type ConfigError

type ConfigError struct {
	Source    string // The source where the error occurred (e.g., "source[0]", "json-schema", "binding")
	Field     string // The specific field where the error occurred (optional)
	Operation string // The operation being performed (e.g., "load", "validate", "bind", "merge")
	Err       error  // The underlying error
}

ConfigError represents a configuration error with detailed context. It provides information about where the error occurred (source, field), what operation was being performed, and the underlying error.

func NewConfigError

func NewConfigError(source, operation string, err error) *ConfigError

NewConfigError creates a new ConfigError with the provided context. This is a convenience function for creating ConfigError instances.

func NewConfigFieldError

func NewConfigFieldError(source, field, operation string, err error) *ConfigError

NewConfigFieldError creates a new ConfigError with field information. This is useful when the error is specific to a particular configuration field.

func (*ConfigError) Error

func (e *ConfigError) Error() string

Error returns a formatted error message with context information. If Field is provided, it includes the field in the error message.

func (*ConfigError) Unwrap

func (e *ConfigError) Unwrap() error

Unwrap returns the underlying error, allowing for error chain inspection. This enables the use of errors.Is() and errors.As() with ConfigError.

type Dumper

type Dumper interface {
	// Dump writes the configuration values to a destination.
	// The values map should not be modified by implementations.
	Dump(ctx context.Context, values *map[string]any) error
}

Dumper defines the interface for configuration dumpers. Implementations write configuration data to various destinations such as files or remote services.

Dump must be safe to call concurrently.

type MockCodec

type MockCodec struct {
	MockDecoder
	MockEncoder
}

MockCodec is a test codec that implements both Encoder and Decoder.

func NewMockCodec

func NewMockCodec(decodeFunc func([]byte, any) error, encodeFunc func(any) ([]byte, error)) *MockCodec

NewMockCodec creates a new mock codec for testing.

type MockDecoder

type MockDecoder struct {
	DecodeFunc func(data []byte, v any) error
}

MockDecoder is a test decoder that can be configured to return specific values or errors.

func (*MockDecoder) Decode

func (m *MockDecoder) Decode(data []byte, v any) error

Decode implements the codec.Decoder interface.

type MockEncoder

type MockEncoder struct {
	EncodeFunc func(v any) ([]byte, error)
}

MockEncoder is a test encoder that can be configured to return specific values or errors.

func (*MockEncoder) Encode

func (m *MockEncoder) Encode(v any) ([]byte, error)

Encode implements the codec.Encoder interface.

type Option

type Option func(c *Config) error

Option is a functional option that can be used to configure a Config instance.

func WithBinding

func WithBinding(v any) Option

WithBinding returns an Option that configures the Config instance to bind configuration data to a struct.

Example

ExampleWithBinding demonstrates binding configuration to a struct.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	type ServerConfig struct {
		Host string `config:"host"`
		Port int    `config:"port"`
	}

	type Config struct {
		Server ServerConfig `config:"server"`
	}

	yamlContent := []byte(`
server:
  host: localhost
  port: 8080
`)

	var appConfig Config
	cfg, err := config.New(
		config.WithContent(yamlContent, codec.TypeYAML),
		config.WithBinding(&appConfig),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s:%d\n", appConfig.Server.Host, appConfig.Server.Port)
}
Output:

localhost:8080

func WithConsul

func WithConsul(path string) Option

WithConsul returns an Option that configures the Config instance to load configuration data from a Consul server. The format is automatically detected from the path extension. For custom formats, use WithConsulAs instead. Required environment variables:

  • CONSUL_HTTP_ADDR: The address of the Consul server (e.g., "http://localhost:8500")
  • CONSUL_HTTP_TOKEN: The access token for authentication with Consul (optional)

Example:

cfg := config.MustNew(
    config.WithConsul("production/service.yaml"),  // Auto-detects YAML
)

func WithConsulAs

func WithConsulAs(path string, codecType codec.Type) Option

WithConsulAs returns an Option that configures the Config instance to load configuration data from a Consul server with explicit format. Use this when you need to override the format detection. Required environment variables:

  • CONSUL_HTTP_ADDR: The address of the Consul server (e.g., "http://localhost:8500")
  • CONSUL_HTTP_TOKEN: The access token for authentication with Consul (optional)

Example:

cfg := config.MustNew(
    config.WithConsulAs("production/service", codec.TypeJSON),
)

func WithConsulSource

func WithConsulSource(path string, codecType codec.Type) Option

WithConsulSource returns an Option that configures the Config instance to load configuration data from a Consul server. The path parameter specifies the key path in Consul's key-value store to load configuration from. The codecType parameter specifies the codec type (e.g., JSON, YAML) to use for decoding the configuration data. Required environment variables:

  • CONSUL_HTTP_ADDR: The address of the Consul server (e.g., "http://localhost:8500")
  • CONSUL_HTTP_TOKEN: The access token for authentication with Consul (optional)

func WithContent

func WithContent(data []byte, codecType codec.Type) Option

WithContent returns an Option that configures the Config instance to load configuration data from a byte slice. The codecType parameter specifies the format of the data (e.g., codec.TypeJSON, codec.TypeYAML).

Example:

yamlContent := []byte("server:\n  port: 8080")
cfg := config.MustNew(
    config.WithContent(yamlContent, codec.TypeYAML),
)
Example

ExampleWithContent demonstrates loading configuration from byte content.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	jsonContent := []byte(`{
		"app": {
			"name": "MyApp",
			"version": "1.0.0"
		}
	}`)

	cfg, err := config.New(
		config.WithContent(jsonContent, codec.TypeJSON),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println(cfg.String("app.name"))
	fmt.Println(cfg.String("app.version"))
}
Output:

MyApp
1.0.0

func WithContentSource

func WithContentSource(data []byte, codecType codec.Type) Option

WithContentSource returns an Option that configures the Config instance to load configuration data from a byte slice.

func WithDumper

func WithDumper(dumper Dumper) Option

WithDumper adds a dumper to the configuration loader.

func WithEnv

func WithEnv(prefix string) Option

WithEnv returns an Option that configures the Config instance to load configuration data from environment variables. The prefix parameter specifies the prefix for the environment variables to be loaded. Environment variables are converted to lowercase and underscores create nested structures.

Example:

cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithEnv("APP_"),  // Loads APP_SERVER_PORT as server.port
)

func WithFile

func WithFile(path string) Option

WithFile returns an Option that configures the Config instance to load configuration data from a file. The format is automatically detected from the file extension (.yaml, .yml, .json, .toml). For files without extensions or custom formats, use WithFileAs instead.

Example:

cfg := config.MustNew(
    config.WithFile("config.yaml"),     // Automatically detects YAML
    config.WithFile("override.json"),   // Automatically detects JSON
)

func WithFileAs

func WithFileAs(path string, codecType codec.Type) Option

WithFileAs returns an Option that configures the Config instance to load configuration data from a file with explicit format. Use this when the file doesn't have an extension or when you need to override the format detection.

Example:

cfg := config.MustNew(
    config.WithFileAs("config", codec.TypeYAML),      // No extension, specify YAML
    config.WithFileAs("config.dat", codec.TypeJSON),  // Wrong extension, specify JSON
)

func WithFileDumper

func WithFileDumper(path string) Option

WithFileDumper returns an Option that configures the Config instance to dump configuration data to a file. The format is automatically detected from the file extension (.yaml, .yml, .json, .toml). For files without extensions or custom formats, use WithFileDumperAs instead.

Example:

cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithFileDumper("output.yaml"),  // Auto-detects YAML
)

func WithFileDumperAs

func WithFileDumperAs(path string, codecType codec.Type) Option

WithFileDumperAs returns an Option that configures the Config instance to dump configuration data to a file with explicit format. Use this when the file doesn't have an extension or when you need to override the format detection.

Example:

cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithFileDumperAs("output", codec.TypeYAML),  // No extension, specify YAML
)

func WithFileSource

func WithFileSource(path string, codecType codec.Type) Option

WithFileSource returns an Option that configures the Config instance to load configuration data from a file.

Example

ExampleWithFileSource demonstrates loading configuration from a file.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	// Create a temporary config file (in real code, use an actual file path)
	cfg, err := config.New(
		config.WithContent([]byte(`{"name": "example"}`), codec.TypeJSON),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println(cfg.String("name"))
}
Output:

example

func WithJSONSchema

func WithJSONSchema(schema []byte) Option

WithJSONSchema adds a JSON Schema for validation.

func WithOSEnvVarSource

func WithOSEnvVarSource(prefix string) Option

WithOSEnvVarSource returns an Option that configures the Config instance to load configuration data from environment variables. The prefix parameter specifies the prefix for the environment variables to be loaded.

func WithSource

func WithSource(loader Source) Option

WithSource adds a source to the configuration loader.

func WithTag

func WithTag(tagName string) Option

WithTag sets a custom struct tag name for binding (default: "config"). This allows you to use a different tag name if "config" conflicts with other libraries.

Example:

type Config struct {
    Port int `cfg:"port"`  // Using custom tag
}

cfg := config.MustNew(
    config.WithFile("config.yaml"),
    config.WithBinding(&appConfig),
    config.WithTag("cfg"),  // Use "cfg" instead of "config"
)

func WithValidator

func WithValidator(fn func(map[string]any) error) Option

WithValidator adds a custom validation function.

Example

ExampleWithValidator demonstrates using a custom validator.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/config"
	"rivaas.dev/config/codec"
)

func main() {
	yamlContent := []byte(`name: myapp`)

	cfg, err := config.New(
		config.WithContent(yamlContent, codec.TypeYAML),
		config.WithValidator(func(cfgMap map[string]any) error {
			// Custom validation logic
			if _, ok := cfgMap["name"]; !ok {
				return fmt.Errorf("name is required")
			}
			return nil
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := cfg.Load(context.Background()); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Validation passed")
}
Output:

Validation passed

type Source

type Source interface {
	// Load loads configuration data from the source.
	// It returns a map containing the configuration key-value pairs.
	// Keys are normalized to lowercase for case-insensitive access.
	Load(ctx context.Context) (map[string]any, error)
}

Source defines the interface for configuration sources. Implementations load configuration data from various locations such as files, environment variables, or remote services.

Load must be safe to call concurrently.

func TestSource

func TestSource(conf map[string]any) Source

TestSource creates a mock source for testing with the given configuration map.

func TestSourceWithError

func TestSourceWithError(err error) Source

TestSourceWithError creates a mock source that returns an error on Load.

type Validator

type Validator interface {
	Validate() error
}

Validator is an interface for structs that can validate their own configuration.

type Watcher

type Watcher interface {
	// Watch starts watching for changes to configuration data.
	// It blocks until the context is cancelled or an error occurs.
	Watch(ctx context.Context) error
}

Watcher defines the interface for watching configuration changes. Implementations monitor configuration sources for changes and notify when updates occur.

Directories

Path Synopsis
Package codec provides functionality for encoding and decoding data.
Package codec provides functionality for encoding and decoding data.
Package dumper provides configuration dumper implementations.
Package dumper provides configuration dumper implementations.
examples
basic command
comprehensive command
environment command
mixed command
Package source provides configuration source implementations.
Package source provides configuration source implementations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL