xconfig

package module
v0.3.3 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2026 License: MIT Imports: 18 Imported by: 2

README

xConfig

Go Reference Go Version Go Report Card License

A lightweight, zero-dependency, and highly extensible configuration management library for Go applications.

Features

  • Zero Dependencies - No external dependencies in the core library
  • Plugin-Based Architecture - Mix and match only the configuration sources you need
  • Type-Safe - Strongly typed configuration with struct tags
  • Multiple Sources - Support for defaults, environment variables, command-line flags, and config files
  • HashiCorp Vault - Native integration with batch loading, token renewal, auto-retry, and metrics
  • Background Refresh - Real-time config updates without restart via Refreshable plugins
  • Nested Structures - Full support for nested configuration structs
  • Rich Type Support - All basic Go types, time.Duration, and custom types via encoding.TextUnmarshaler
  • Validation - Built-in validation support through plugins
  • Documentation Generation - Auto-generate markdown documentation for your configuration

AI Agent Skills

This repository includes AI agent skills with documentation and usage examples for all packages. Install them with the skills CLI:

go install github.com/sxwebdev/skills/cmd/skills@latest
skills init
skills repo add sxwebdev/xconfig

Installation

go get github.com/sxwebdev/xconfig

Quick Start

package main

import (
    "fmt"
    "log"

    "github.com/sxwebdev/xconfig"
)

type Config struct {
    Host     string `default:"localhost" env:"HOST" flag:"host" usage:"Server host address"`
    Port     int    `default:"8080" env:"PORT" flag:"port" usage:"Server port"`
    Debug    bool   `default:"false" env:"DEBUG" flag:"debug" usage:"Enable debug mode"`
    Database struct {
        Host     string `default:"localhost" env:"DB_HOST" usage:"Database host"`
        Port     int    `default:"5432" env:"DB_PORT" usage:"Database port"`
        Name     string `default:"myapp" env:"DB_NAME" usage:"Database name"`
        Password string `vault:"true" env:"DB_PASSWORD" secret:"true" usage:"Database password"`
    }
}

func main() {
    cfg := &Config{}

    // Load configuration from defaults, env vars, and flags
    _, err := xconfig.Load(cfg)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Server: %s:%d\n", cfg.Host, cfg.Port)
    fmt.Printf("Database: %s:%d/%s\n", cfg.Database.Host, cfg.Database.Port, cfg.Database.Name)
}

Usage

Basic Configuration Loading

The Load function provides the most common configuration pattern, automatically enabling:

  1. Default values from struct tags
  2. Custom defaults via SetDefaults() method
  3. Configuration files (if provided)
  4. Environment variables
  5. Command-line flags
  6. Custom plugins (Vault, etc.) — highest priority
type AppConfig struct {
    APIKey    string `default:"dev-key" env:"API_KEY" flag:"api-key"`
    Timeout   int    `default:"30" env:"TIMEOUT" flag:"timeout"`
    EnableLog bool   `default:"true" env:"ENABLE_LOG" flag:"enable-log"`
}

cfg := &AppConfig{}
_, err := xconfig.Load(cfg)
Loading from Configuration Files

xConfig supports multiple configuration file formats through decoders:

import (
    "encoding/json"

    "github.com/sxwebdev/xconfig"
    "github.com/sxwebdev/xconfig/plugins/loader"
    "github.com/sxwebdev/xconfig/decoders/xconfigyaml"
    "github.com/sxwebdev/xconfig/decoders/xconfigdotenv"
)

type Config struct {
    Server struct {
        Host string `json:"host"`
        Port int    `json:"port"`
    } `json:"server"`
}

cfg := &Config{}

// Create loader with JSON decoder
l, err := loader.NewLoader(map[string]loader.Unmarshal{
    "json": json.Unmarshal,
    "yaml": xconfigyaml.New().Unmarshal,
    "env":  xconfigdotenv.New().Unmarshal,
})
if err != nil {
    log.Fatal(err)
}

// Add configuration file (optional=false means file must exist)
err = l.AddFile("config.json", false)
if err != nil {
    log.Fatal(err)
}

_, err = xconfig.Load(cfg, xconfig.WithLoader(l))
Environment Variables with Prefix
type Config struct {
    APIKey string `env:"API_KEY"`
    Secret string `env:"SECRET"`
}

cfg := &Config{}

// All env vars will be prefixed with "MYAPP_"
// So it will look for: MYAPP_API_KEY and MYAPP_SECRET
_, err := xconfig.Load(cfg, xconfig.WithEnvPrefix("MYAPP"))
Custom Defaults with SetDefaults

Implement the SetDefaults() method to programmatically set default values:

type Config struct {
    Host string
    Port int
    URLs []string
}

func (c *Config) SetDefaults() {
    c.Host = "localhost"
    c.Port = 8080
    c.URLs = []string{"https://api.example.com", "https://backup.example.com"}
}

cfg := &Config{}
_, err := xconfig.Load(cfg)
// cfg.Host will be "localhost" unless overridden by env or flags
HashiCorp Vault Integration

Use the vault tag to load secrets from HashiCorp Vault with automatic token renewal, batch loading, auto-retry on 401/403, and metrics callback:

import (
    "github.com/sxwebdev/xconfig"
    "github.com/sxwebdev/xconfig/sourcers/xconfigvault"
)

type Config struct {
    Host       string `default:"localhost" env:"HOST"`
    DBPassword string `vault:"true" env:"DB_PASSWORD" secret:"true"`
    APIKey     string `vault:"true" env:"API_KEY" secret:"true"`
}

// Create Vault client (supports Token, AppRole, Kubernetes, UserPass, LDAP auth)
vaultClient, err := xconfigvault.New(&xconfigvault.Config{
    Address:    os.Getenv("VAULT_ADDR"),
    Auth:       xconfigvault.WithKubernetes("my-service-role"),
    SecretPath: "kv/myservice/config",
    Metrics:    xconfigvault.MetricsFunc(func(e xconfigvault.Event) {
        // Monitor auth failures, retries, etc.
        promCounter.WithLabelValues(string(e.Type)).Inc()
    }),
})
if err != nil {
    log.Fatal(err)
}
defer vaultClient.Close()

cfg := &Config{}
xc, err := xconfig.Load(cfg, xconfig.WithPlugins(vaultClient.Plugin()))

The Vault plugin:

  • Runs last in the plugin chain (maximum priority over env, flags, defaults)
  • Batch-loads all secrets in a single HTTP request
  • Automatically renews tokens in the background
  • Retries on 401/403 with token refresh
  • Emits operational events via MetricsCallback

The vault:"true" tag marks a field to be sourced from Vault. The secret:"true" tag is independent — it marks a field as sensitive (for masking in logs/docs).

Background Config Refresh

Plugins implementing Refreshable support real-time config updates without restart:

xc.StartRefresh(ctx, 1*time.Minute, func(changes []plugins.FieldChange) {
    for _, c := range changes {
        log.Printf("config changed: %s %q -> %q", c.FieldName, c.OldValue, c.NewValue)
        if c.FieldName == "Database.Password" {
            reconnectDB(c.NewValue)
        }
    }
})
defer xc.StopRefresh()

FieldChange.FieldName contains the full field path (e.g., Database.Postgres.Password). Any plugin implementing plugins.Refreshable participates in the refresh cycle automatically.

Secret Management (Legacy)

The secret plugin loads sensitive data from a custom provider function:

import "github.com/sxwebdev/xconfig/plugins/secret"

type Config struct {
    DBPassword string `secret:"DATABASE_PASSWORD"`
}

secretProvider := func(name string) (string, error) {
    return fetchFromVault(name)
}

cfg := &Config{}
_, err := xconfig.Load(cfg, xconfig.WithPlugins(secret.New(secretProvider)))

For new projects, prefer the Vault plugin which provides batch loading, token renewal, and background refresh out of the box.

Validation

Add validation to ensure your configuration meets requirements:

import (
    "fmt"
    "github.com/sxwebdev/xconfig"
    "github.com/sxwebdev/xconfig/plugins/validate"
)

type Config struct {
    Port int    `default:"8080"`
    Host string `default:"localhost"`
}

// Implement Validate method
func (c *Config) Validate() error {
    if c.Port < 1 || c.Port > 65535 {
        return fmt.Errorf("port must be between 1 and 65535")
    }
    if c.Host == "" {
        return fmt.Errorf("host cannot be empty")
    }
    return nil
}

cfg := &Config{}

// Validation happens automatically after loading
_, err := xconfig.Load(cfg)
if err != nil {
    log.Fatal(err) // Will fail if validation fails
}

You can also use external validators:

import (
    "github.com/go-playground/validator/v10"
    "github.com/sxwebdev/xconfig/plugins/validate"
)

type Config struct {
    Email string `validate:"required,email"`
    Age   int    `validate:"gte=0,lte=130"`
}

cfg := &Config{}

v := validator.New()
_, err := xconfig.Load(cfg, xconfig.WithPlugins(
    validate.New(func(a any) error {
        return v.Struct(a)
    }),
))
Selective Plugin Loading

Control which plugins are enabled:

cfg := &Config{}

// Skip certain plugins
_, err := xconfig.Load(cfg,
    xconfig.WithSkipDefaults(),         // Don't load from 'default' tags
    xconfig.WithSkipEnv(),              // Don't load from environment
    xconfig.WithSkipFlags(),            // Don't load from command-line flags
    xconfig.WithSkipCustomDefaults(),   // Don't call SetDefaults()
    xconfig.WithDisallowUnknownFields(), // Fail if config files contain unknown fields
)

Unknown Fields Validation: Enable WithDisallowUnknownFields() to detect typos and configuration errors in JSON/YAML files. When enabled, loading will fail if any fields in the config files don't match your struct definition. Use xconfig.GetUnknownFields() to retrieve unknown fields without failing.

Documentation Generation

Generate markdown documentation for your configuration:

type Config struct {
    Host   string `default:"localhost" usage:"Server host address"`
    Port   int    `default:"8080" usage:"Server port number"`
    APIKey string `secret:"API_KEY" usage:"API authentication key"`
}

cfg := &Config{}

markdown, err := xconfig.GenerateMarkdown(cfg)
if err != nil {
    log.Fatal(err)
}

// Save to file
os.WriteFile("CONFIG.md", []byte(markdown), 0644)
Usage Information

Get runtime configuration information:

cfg := &Config{}
c, err := xconfig.Load(cfg)
if err != nil {
    log.Fatal(err)
}

usage, err := c.Usage()
if err != nil {
    log.Fatal(err)
}

fmt.Println(usage)

Available Struct Tags

Tag Description Example
default Default value for the field default:"8080"
env Environment variable name env:"PORT"
flag Command-line flag name flag:"port"
secret Marks field as sensitive (metadata) secret:"true"
vault Field sourced from HashiCorp Vault vault:"true"
usage Description for documentation/help usage:"Server port"
xconfig Override field name in flat structure xconfig:"custom_name"

Available Plugins

Plugin Description
defaults Load values from default struct tags
customdefaults Call SetDefaults() method if implemented
env Load values from environment variables
flag Load values from command-line flags
loader Load from configuration files (JSON, YAML, etc.)
secret Mark fields as sensitive, load from custom providers
validate Validate configuration after loading
xconfigvault HashiCorp Vault: batch loading, token renewal, retry, refresh

Custom Plugins

Create your own plugins by implementing the Plugin interface with either Walker or Visitor:

import (
    "github.com/sxwebdev/xconfig/flat"
    "github.com/sxwebdev/xconfig/plugins"
)

type myPlugin struct {
    fields flat.Fields
}

// Visitor interface - called once with all fields
func (p *myPlugin) Visit(fields flat.Fields) error {
    p.fields = fields
    // Setup phase: register metadata, validate structure, etc.
    return nil
}

// Parse is called to actually load configuration
func (p *myPlugin) Parse() error {
    for _, field := range p.fields {
        // Load configuration for each field
    }
    return nil
}

// Use your custom plugin
cfg := &Config{}
_, err := xconfig.Custom(cfg, &myPlugin{})

Supported Types

  • All basic Go types: string, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64
  • time.Duration
  • Slices of supported types: []string, []int, etc.
  • Any type implementing encoding.TextUnmarshaler

Examples

See the examples directory for more complete examples.

License

MIT License

Documentation

Overview

Package xconfig provides a lightweight, zero-dependency, and highly extensible configuration management library for Go applications.

Overview

xconfig enables you to build type-safe configuration for your applications using a plugin-based architecture. Mix and match only the configuration sources you need: defaults, environment variables, command-line flags, configuration files, secret providers, and more. Plugins implementing plugins.Refreshable support background config refresh for real-time updates without restart.

Quick Start

Define your configuration as a struct with tags:

type Config struct {
    Host     string `default:"localhost" env:"HOST" flag:"host" usage:"Server host"`
    Port     int    `default:"8080" env:"PORT" flag:"port" usage:"Server port"`
    Debug    bool   `default:"false" env:"DEBUG" flag:"debug" usage:"Debug mode"`
    Database struct {
        Host     string `default:"localhost" env:"DB_HOST" usage:"Database host"`
        Port     int    `default:"5432" env:"DB_PORT" usage:"Database port"`
        Password string `vault:"true" env:"DB_PASSWORD" secret:"true" usage:"Database password"`
    }
}

Load configuration from multiple sources:

cfg := &Config{}
_, err := xconfig.Load(cfg)
if err != nil {
    log.Fatal(err)
}

The Load function automatically processes configuration in this order:

  1. Default values from struct tags
  2. Custom defaults via SetDefaults() method
  3. Configuration files (if provided)
  4. Environment variables
  5. Command-line flags
  6. Custom plugins (vault, etc.) — highest priority

Configuration Sources

## Default Values

Use the "default" tag to specify default values:

type Config struct {
    Port    int    `default:"8080"`
    Timeout int    `default:"30"`
    Enabled bool   `default:"true"`
}

## Environment Variables

Use the "env" tag to bind fields to environment variables:

type Config struct {
    APIKey string `env:"API_KEY"`
    Secret string `env:"SECRET"`
}

Add a prefix to all environment variables:

_, err := xconfig.Load(cfg, xconfig.WithEnvPrefix("MYAPP"))
// Will look for: MYAPP_API_KEY, MYAPP_SECRET

## Command-Line Flags

Use the "flag" tag to bind fields to command-line flags:

type Config struct {
    Host string `flag:"host" usage:"Server hostname"`
    Port int    `flag:"port" usage:"Server port"`
}

## Configuration Files

Load configuration from JSON, YAML, TOML, or any other format:

import (
    "encoding/json"
    "github.com/sxwebdev/xconfig/plugins/loader"
)

l, err := loader.NewLoader(map[string]loader.Unmarshal{
    ".json": json.Unmarshal,
    ".yaml": yaml.Unmarshal,
})
if err != nil {
    log.Fatal(err)
}

err = l.AddFile("config.json", false)
if err != nil {
    log.Fatal(err)
}

_, err = xconfig.Load(cfg, xconfig.WithLoader(l))

## Custom Defaults

Implement SetDefaults() for programmatic default values:

type Config struct {
    URLs []string
    Port int
}

func (c *Config) SetDefaults() {
    c.URLs = []string{"https://api.example.com"}
    c.Port = 8080
}

HashiCorp Vault Integration

Use the vault plugin to load secrets from HashiCorp Vault with automatic token renewal, batch loading, auto-retry, and background refresh:

import "github.com/sxwebdev/xconfig/sourcers/xconfigvault"

type Config struct {
    DBPassword string `vault:"true" env:"DB_PASSWORD" secret:"true"`
    APIKey     string `vault:"true" env:"API_KEY" secret:"true"`
}

vaultClient, err := xconfigvault.New(&xconfigvault.Config{
    Address:    os.Getenv("VAULT_ADDR"),
    Auth:       xconfigvault.WithKubernetes("my-service-role"),
    SecretPath: "kv/myservice/config",
})
defer vaultClient.Close()

cfg := &Config{}
xc, err := xconfig.Load(cfg, xconfig.WithPlugins(vaultClient.Plugin()))

The vault plugin runs last and has maximum priority over all other sources. Use vault:"true" to mark fields sourced from Vault. The secret:"true" tag is independent — it marks a field as sensitive (for masking in logs/docs).

Background Config Refresh

Plugins implementing plugins.Refreshable support background updates. Call [Config.StartRefresh] to periodically re-fetch values from external sources:

xc.StartRefresh(ctx, 1*time.Minute, func(changes []plugins.FieldChange) {
    for _, c := range changes {
        log.Printf("config changed: %s %q -> %q", c.FieldName, c.OldValue, c.NewValue)
    }
})
defer xc.StopRefresh()

FieldChange.FieldName contains the full field path (e.g., "Database.Postgres.Password").

Secret Management

Use the secret tag to mark fields as sensitive. The secret plugin can also load values from a custom provider:

import "github.com/sxwebdev/xconfig/plugins/secret"

type Config struct {
    DBPassword string `secret:"DATABASE_PASSWORD"`
    APIToken   string `secret:"API_TOKEN"`
}

secretProvider := func(name string) (string, error) {
    return fetchFromVault(name)
}

_, err := xconfig.Load(cfg, xconfig.WithPlugins(secret.New(secretProvider)))

Validation

Add validation by implementing the Validate() method:

type Config struct {
    Port int `default:"8080"`
}

func (c *Config) Validate() error {
    if c.Port < 1 || c.Port > 65535 {
        return fmt.Errorf("invalid port: %d", c.Port)
    }
    return nil
}

Or use external validators with the validate plugin:

import (
    "github.com/go-playground/validator/v10"
    "github.com/sxwebdev/xconfig/plugins/validate"
)

type Config struct {
    Email string `validate:"required,email"`
    Age   int    `validate:"gte=0,lte=130"`
}

v := validator.New()
_, err := xconfig.Load(cfg, xconfig.WithPlugins(
    validate.New(func(a any) error {
        return v.Struct(a)
    }),
))

Available Tags

The following struct tags are supported:

  • default: Default value for the field
  • env: Environment variable name
  • flag: Command-line flag name
  • secret: Marks field as sensitive (metadata for masking/docs)
  • vault: Field sourced from HashiCorp Vault (vault:"true")
  • usage: Description for documentation and help text
  • xconfig: Override field name in flat structure

Supported Types

xconfig supports all basic Go types, time.Duration, slices of basic types, and any type implementing encoding.TextUnmarshaler:

  • string, bool
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • time.Duration
  • []string, []int, []float64, etc.
  • Custom types via encoding.TextUnmarshaler

Custom Plugins

Create custom plugins by implementing the Plugin interface with either Walker or Visitor. For background refresh support, also implement Refreshable:

import (
    "github.com/sxwebdev/xconfig/flat"
    "github.com/sxwebdev/xconfig/plugins"
)

type myPlugin struct {
    fields flat.Fields
}

func (p *myPlugin) Visit(fields flat.Fields) error {
    p.fields = fields
    return nil
}

func (p *myPlugin) Parse() error {
    // Load configuration for each field
    return nil
}

// Optional: implement Refreshable for background updates
func (p *myPlugin) Refresh(ctx context.Context) ([]plugins.FieldChange, error) {
    // Re-fetch and return changes
    return nil, nil
}

// Use your plugin
_, err := xconfig.Custom(cfg, &myPlugin{})

Documentation Generation

Generate markdown documentation for your configuration:

markdown, err := xconfig.GenerateMarkdown(cfg)
if err != nil {
    log.Fatal(err)
}
os.WriteFile("CONFIG.md", []byte(markdown), 0644)

Options

Control which plugins are enabled:

_, err := xconfig.Load(cfg,
    xconfig.WithSkipDefaults(),      // Skip 'default' tags
    xconfig.WithSkipEnv(),            // Skip environment variables
    xconfig.WithSkipFlags(),          // Skip command-line flags
    xconfig.WithEnvPrefix("MYAPP"),   // Add prefix to env vars
    xconfig.WithPlugins(myPlugin),    // Add custom plugins
)

For more information and examples, see: https://github.com/sxwebdev/xconfig

Package xconfig provides advanced command line flags supporting defaults, env vars, and config structs.

Index

Constants

This section is empty.

Variables

View Source
var ErrUsage = plugins.ErrUsage

Functions

func GenerateMarkdown

func GenerateMarkdown(cfg any, opts ...Option) (string, error)

func GetUnknownFields

func GetUnknownFields(c Config) map[string][]string

GetUnknownFields returns all unknown fields found in configuration files. Returns a map where keys are file paths and values are slices of unknown field paths. This function is useful for debugging configuration issues or logging warnings about extra fields that are not used.

Types

type Config

type Config interface {
	// Parse will call the parse method of all the added pluginss in the order
	// that the pluginss were registered, it will return early as soon as any
	// plugins fails.
	// You must call this before using the config value.
	Parse() error

	// Usage provides a simple usage message based on the meta data registered
	// by the pluginss.
	Usage() (string, error)

	// Options returns the options for the config.
	Options() *options

	// Fields returns the flat fields that have been processed by plugins.
	Fields() flat.Fields

	// StartRefresh starts a background goroutine that periodically calls Refresh
	// on all plugins implementing plugins.Refreshable. The onChange callback is
	// invoked with the list of changed fields whenever a refresh detects changes.
	StartRefresh(ctx context.Context, interval time.Duration, onChange func([]plugins.FieldChange))

	// StopRefresh stops the background refresh goroutine and waits for it to finish.
	StopRefresh()
	// contains filtered or unexported methods
}

Config is the config manager.

func Custom

func Custom(conf any, ps ...plugins.Plugin) (Config, error)

Custom returns a new Config. The conf must be a pointer to a struct.

func Load

func Load(conf any, opts ...Option) (Config, error)

Load creates a xconfig manager with defaults, environment variables, and flags (in that order) and optionally file loaders based on the provided Files map and parses them right away.

type Option

type Option func(*options)

func WithDisallowUnknownFields

func WithDisallowUnknownFields() Option

func WithEnvPrefix

func WithEnvPrefix(prefix string) Option

func WithLoader

func WithLoader(loader *loader.Loader) Option

func WithPlugins

func WithPlugins(plugins ...plugins.Plugin) Option

func WithSkipCustomDefaults

func WithSkipCustomDefaults() Option

func WithSkipDefaults

func WithSkipDefaults() Option

func WithSkipEnv

func WithSkipEnv() Option

func WithSkipFiles

func WithSkipFiles() Option

func WithSkipFlags

func WithSkipFlags() Option

Directories

Path Synopsis
decoders
xconfigdotenv module
xconfigyaml module
Package flat provides a flat view of an arbitrary nested structs.
Package flat provides a flat view of an arbitrary nested structs.
internal
f
Package f provides simple test fixtures for xconfig.
Package f provides simple test fixtures for xconfig.
utils
original package located here https://github.com/mcuadros/go-lookup
original package located here https://github.com/mcuadros/go-lookup
Package plugins describes the xconfig provider interface.
Package plugins describes the xconfig provider interface.
customdefaults
Package defaults provides default values for xconfig
Package defaults provides default values for xconfig
defaults
Package defaults provides default values for xconfig
Package defaults provides default values for xconfig
env
Package env provides environment variables support for xconfig
Package env provides environment variables support for xconfig
flag
Package flag provides flags support for xconfig
Package flag provides flags support for xconfig
loader
Package file provides config loader support for xconfig
Package file provides config loader support for xconfig
secret
Package secret enable xconfig to integrate with secret plugins.
Package secret enable xconfig to integrate with secret plugins.
sourcers
xconfigvault module

Jump to

Keyboard shortcuts

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