goconfig

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Dec 24, 2025 License: MIT Imports: 12 Imported by: 0

README

goconfig

A simple, type-safe Go library for loading configuration from environment variables using struct tags.

Features

  • 🏷️ Struct-based configuration - Define config with Go structs and tags
  • Built-in validation - min, max, and pattern tags plus custom type validators (docs | example)
  • 🎯 Type-safe - Automatic conversion for primitives, durations, and JSON with generic type handlers
  • 🧱 Building block architecture - Compose custom types from simple, reusable components (docs)
  • 🔄 Flexible defaults - Struct tags or pre-initialized values (docs)
  • 🌳 Nested structs - Organize configuration hierarchically
  • 🔧 Extensible - Custom types and key stores (docs)
  • 💬 Clear errors - Descriptive validation and missing field errors

Installation

go get github.com/m0rjc/goconfig

Quick Start

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/m0rjc/goconfig"
)

type Config struct {
    // Basic fields
    APIKey string `key:"API_KEY" required:"true"`
    Host   string `key:"HOST" default:"localhost"`

    // With validation
    Port    int           `key:"PORT" default:"8080" min:"1024" max:"65535"`
    Timeout time.Duration `key:"TIMEOUT" default:"30s" min:"1s" max:"5m"`

    // Nested configuration
    Database struct {
        Host     string `key:"DB_HOST" default:"localhost"`
        Port     int    `key:"DB_PORT" default:"5432"`
        Username string `key:"DB_USER" required:"true"`
        Password string `key:"DB_PASSWORD" required:"true"`
    }
}

func main() {
    var config Config

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

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

Set environment variables and run:

export API_KEY="sk-your-api-key"
export DB_USER="appuser"
export DB_PASSWORD="secret"
export PORT="8080"
go run main.go

Struct Tags

Tag Purpose Example
key Environment variable name (required) key:"PORT"
default Default value if not set default:"8080"
min Minimum value (numbers, durations) min:"1024"
max Maximum value (numbers, durations) max:"65535"
pattern Regex pattern for strings pattern:"^[a-z]+$"
scheme Command separated list of schemes for *url.URL scheme:"http,https"
required Must be present and non-empty required:"true"
keyRequired Must be present (can be empty) keyRequired:"true"

Supported Types

  • Primitives: string, bool
  • Integers: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
  • Floats: float32, float64
  • Duration: time.Duration - uses Go format: "30s", "5m", "1h"
  • JSON: map[string]interface{} or structs with json tags
  • Pointers: All above types as pointers
  • Nested structs: Organize configuration hierarchically

Validation

Built-in Validators
type ServerConfig struct {
    Port       int           `key:"PORT" default:"8080" min:"1024" max:"65535"`
    MaxConns   int           `key:"MAX_CONNS" default:"100" min:"1" max:"10000"`
    LoadFactor float64       `key:"LOAD_FACTOR" default:"0.75" min:"0.0" max:"1.0"`
    Timeout    time.Duration `key:"TIMEOUT" default:"30s" min:"1s" max:"5m"`
    Username   string        `key:"USERNAME" pattern:"^[a-zA-Z0-9_]+$"`
}

Validation errors provide clear messages:

invalid value for PORT: below minimum 1024
Custom Types

Define custom types with validation using the building block architecture - compose simple, reusable components:

type APIKey string

type Config struct {
    APIKey APIKey `key:"API_KEY" required:"true"`
}

// Building block approach: parser + validators
apiKeyHandler := goconfig.NewCustomType(
    func(rawValue string) (APIKey, error) {
        return APIKey(rawValue), nil
    },
    func(value APIKey) error {
        if !strings.HasPrefix(string(value), "sk-") {
            return fmt.Errorf("API key must start with 'sk-'")
        }
        return nil
    },
)

err := goconfig.Load(context.Background(), &cfg,
    goconfig.WithCustomType[APIKey](apiKeyHandler),
)

The building block system lets you compose handlers:

  • NewCustomType - Start with parser and validators
  • AddValidators - Add validators to existing handlers
  • CastCustomType - Transform handlers for type aliases
  • NewStringEnumType - Specialized enum builder

📚 Custom Types Guide | Validation Guide | Example

JSON Configuration

Load complex JSON structures from environment variables:

type ModelParams struct {
    Temperature float64 `json:"temperature"`
    MaxTokens   int     `json:"max_tokens"`
}

type Config struct {
    Params ModelParams `key:"MODEL_PARAMS"`
}
export MODEL_PARAMS='{"temperature":0.7,"max_tokens":1000}'

📚 JSON Guide

Troubleshooting

If you see an error about parsing JSON when you are not expecting a JSON value, check that the type is recognized. The JSON handling for struct types catches various types (such as url.URL before I added support for it)

Documentation

Examples

Advanced Usage

Custom Key Stores

Read from sources other than environment variables:

// Composite store: try environment, then fall back to file
store := goconfig.CompositeStore(
    goconfig.EnvironmentKeyStore,
    fileKeyStore("/etc/myapp/config"),
)

err := goconfig.Load(context.Background(), &cfg,
    goconfig.WithKeyStore(store),
)

Supports AWS Secrets Manager, HashiCorp Vault, config files, and more.

📚 Advanced Guide

Error Handling

err := goconfig.Load(context.Background(), &config)
if err != nil {
    // Check for specific errors
    if errors.Is(err, goconfig.ErrMissingConfigKey) {
        log.Fatal("Missing required environment variable")
    }

    log.Fatalf("Configuration error: %v", err)
}

Multiple errors are collected and reported together for easier debugging.

Testing

go test -v

Contributing

Contributions welcome! Please open an issue or pull request on GitHub.

License

MIT License - see LICENSE file for details.

Documentation

Overview

Package goconfig provides a simple way to load configuration from environment variables using struct tags.

Features

  • Load configuration from environment variables using struct tags
  • Support for nested structs
  • Optional default values
  • Type conversion for common types: string, bool, int, uint, float, time.Duration
  • Ability to convert JSON strings into maps or JSON annotated structs
  • Built-in min, max, pattern validators plus support for custom validation
  • Support for custom field parsing
  • Support for a custom key-value store as an alternative to environment variables
  • Clear error messages for missing required fields or invalid values

Basic Usage

Define your configuration struct with 'key' and optional 'default' tags:

type Config struct {
    APIKey  string        `key:"API_KEY"`                                      // Required
    Model   string        `key:"MODEL" default:"gpt-4"`                        // Optional with default
    Port    int           `key:"PORT" default:"8080" min:"1024" max:"65535"`  // With min/max validation
    Timeout time.Duration `key:"TIMEOUT" default:"30s" min:"1s" max:"5m"`     // Duration with validation
}

func main() {
    var config Config
    if err := goconfig.Load(context.Background(), &config); err != nil {
        log.Fatalf("Failed to load configuration: %v", err)
    }
    // Port is guaranteed to be between 1024 and 65535
    // Timeout is guaranteed to be between 1s and 5m
}

Struct Tags

  • key: The environment variable name to read from (required)
  • default: The default value to use if the environment variable is not set (optional)
  • min: Minimum value for numeric types (optional)
  • max: Maximum value for numeric types (optional)
  • pattern: Regular expression for string types (optional)
  • required: Set to "true" to require the field to not be empty (optional)
  • keyRequired: Set to "true" to require the field to be present, though it can be explicitly blank

Supported Types

  • string
  • bool
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • time.Duration (uses Go's duration format: "30s", "1m", "1h", etc.)
  • map[string]interface{} using JSON deserialisation
  • struct using JSON deserialisation
  • pointers to the above

Custom Validation

Use the WithValidator option to add custom validation logic:

err := goconfig.Load(ctx, &cfg,
    goconfig.WithValidator("APIKey", func(value any) error {
        key := value.(string)
        if !strings.HasPrefix(key, "sk-") {
            return fmt.Errorf("API key must start with 'sk-'")
        }
        return nil
    }),
)

Custom Parsers

Use the WithParser option to provide custom parsing logic for specific fields:

err := goconfig.Load(ctx, &cfg,
    goconfig.WithParser("SpecialField", func(value string) (any, error) {
        // Custom parsing logic
        return customParse(value)
    }),
)

Custom Key Stores

By default, the package reads from environment variables, but you can provide a custom key-value store:

myStore := func(ctx context.Context, key string) (string, bool, error) {
    // Custom logic to retrieve configuration values
    return value, found, nil
}

err := goconfig.Load(ctx, &config, goconfig.WithKeyStore(myStore))

You can also chain multiple key stores using CompositeStore, which tries each store in order until one returns a value:

store := goconfig.CompositeStore(
    customStore,
    goconfig.EnvironmentKeyStore,
)
err := goconfig.Load(ctx, &config, goconfig.WithKeyStore(store))

Error Handling

The package provides two sentinel errors for common cases:

  • ErrMissingConfigKey: returned when a required key is not found in the key store
  • ErrMissingValue: returned when a key is found but has a blank value when required="true"

When multiple configuration errors occur, they are collected into a ConfigErrors type, which implements error and provides an Unwrap method for Go 1.20+ error inspection:

err := goconfig.Load(ctx, &config)
if err != nil {
    var configErrs *goconfig.ConfigErrors
    if errors.As(err, &configErrs) {
        for _, e := range configErrs.Unwrap() {
            if errors.Is(e, goconfig.ErrMissingConfigKey) {
                // Handle missing key
            }
        }
    }
}

Error logging to structured logs is supported.

Documentation

For detailed guides and examples, see:

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrMissingConfigKey = errors.New("no configuration found for this key")
	ErrMissingValue     = errors.New("missing or blank value for this key")
)

Functions

func EnvironmentKeyStore

func EnvironmentKeyStore(_ context.Context, key string) (string, bool, error)

EnvironmentKeyStore is a key store that reads values from environment variables

func Load

func Load(ctx context.Context, config interface{}, options ...Option) error

Load populates the given configuration struct from environment variables using the `key`, `default`, `required`, `min`, `max`, and `pattern` struct tags.

Value resolution follows this precedence (highest to lowest):

  1. Environment variable (if set)
  2. Tag default (if specified with default:"value")
  3. Pre-initialized struct value (allows coded defaults)

Fields without any value source are left unchanged, allowing you to set defaults by initializing the struct before calling Load.

Use required:"true" to enforce that a field must be set via environment variable or default tag.

Builtin Validation Tags:

  • min:"value" and max:"value": Numeric range validation (int, uint, float types)
  • pattern:"regex": Regular expression validation (string types only)

Custom Validation:

  • WithValidator(path, validator): Add a validator for a specific field path
  • WithValidatorFactory(factory): Register a factory to auto-add validators based on field metadata
  • Validators run after type conversion but before field assignment

Options:

  • WithValidator(path, validator): Register custom validator for a specific field
  • WithValidatorFactory(factory): Register a custom validator factory

Example:

type Config struct {
    Port     int    `key:"PORT" default:"8080" min:"1024" max:"65535"`
    Username string `key:"USERNAME" pattern:"^[a-zA-Z0-9_]+$"`
    Email    string `key:"EMAIL"`
}

cfg := Config{}
err := Load(&cfg, WithValidator("Email", emailValidator))

func LogError

func LogError(logger *slog.Logger, err error, opts ...ErrorLogOption)

LogError is a convenience function to log either a single error from the configuration load or the collection of validation errors. If a collection of validation errors is returned then they will be logged individually using ConfigErrors.LogAll().

func RegisterCustomType

func RegisterCustomType[T any](handler TypedHandler[T])

Types

type ConfigError

type ConfigError struct {
	Key string // Environment variable name (e.g., "DB_PORT", "API_KEY")
	Err error  // The underlying error
}

ConfigError represents a single configuration error for a specific environment variable.

type ConfigErrors

type ConfigErrors struct {
	// Errors contains the collected errors
	Errors []ConfigError
}

ConfigErrors collects multiple runtime configuration errors. It maintains the order errors were encountered and provides formatted output.

func (*ConfigErrors) Add

func (ce *ConfigErrors) Add(key string, err error)

Add adds a new error for the given environment variable.

func (*ConfigErrors) Error

func (ce *ConfigErrors) Error() string

Error implements the error interface. It formats all collected errors as: "KEY1: error1; KEY2: error2"

func (*ConfigErrors) HasErrors

func (ce *ConfigErrors) HasErrors() bool

HasErrors returns true if any errors were collected.

func (*ConfigErrors) Len

func (ce *ConfigErrors) Len() int

Len returns the number of errors collected.

func (*ConfigErrors) LogAll

func (ce *ConfigErrors) LogAll(logger *slog.Logger, opts ...ErrorLogOption)

LogAll logs each configuration error using structured logging.

Example usage:

logger := slog.New(slog.NewJSONHandler(os.Stderr, nil))
if err := LoadConfig(&cfg); err != nil {
    if configErrs, ok := err.(*ConfigErrors); ok {
        configErrs.LogAll(logger, WithLogMessage("configuration_error"))
    }
}

func (*ConfigErrors) Unwrap

func (ce *ConfigErrors) Unwrap() []error

Unwrap returns all underlying errors for Go 1.20+ error inspection.

type ErrorLogOption

type ErrorLogOption func(*logSettings)

ErrorLogOption provides options for the ConfigErrors.LogAll method

func WithLogMessage

func WithLogMessage(message string) ErrorLogOption

WithLogMessage sets the log message to be passed to the Logger in the ConfigErrors.LogAll method

type FieldProcessor

type FieldProcessor[T any] = readpipeline.FieldProcessor[T]

func AddValidatorToPipeline added in v0.4.0

func AddValidatorToPipeline[T any](pipeline FieldProcessor[T], validator Validator[T]) FieldProcessor[T]

AddValidatorToPipeline adds a validator to a pipeline. This is used as part of pipeline building in the TypedHandler.

type KeyStore

type KeyStore func(ctx context.Context, key string) (string, bool, error)

KeyStore reads string values given keys. Return the value if present (it may be empty), an indication of whether it is present or an error if there was an error accessing the store.

func CompositeStore

func CompositeStore(stores ...KeyStore) KeyStore

CompositeStore tries each store in turn until one returns a value or an error.

func NewEnvFileKeyStore added in v0.4.0

func NewEnvFileKeyStore(filenames ...string) KeyStore

NewEnvFileKeyStore returns a KeyStore that reads values from a list of environment files. If no filenames are provided, it defaults to ".env". Files are processed in the order they are provided. If multiple files contain the same key, the first one encountered wins.

type Option

type Option func(*loadOptions)

Option is a functional option for configuring the Load function.

func WithCustomType

func WithCustomType[T any](handler TypedHandler[T]) Option

WithCustomType registers a custom type handler for a given type.

func WithKeyStore

func WithKeyStore(keyStore KeyStore) Option

WithKeyStore replaces the environment variable keystore with an alternative. Use this to read from other sources such as a database or properties file.

type Transform added in v0.4.0

type Transform[T, U any] = customtypes.Transform[T, U]

type TypedHandler

type TypedHandler[T any] = readpipeline.TypedHandler[T]

func AddDynamicValidation

func AddDynamicValidation[T any](baseHandler TypedHandler[T], wrapper Wrapper[T]) TypedHandler[T]

AddDynamicValidation allows a TypedHandler to add validation (or other logic) to the process pipeline dependent on struct tags present on the target field. See the AddValidatorToPipeline function and the example/custom_tags example for more details.

func AddValidators

func AddValidators[T any](baseHandler TypedHandler[T], customValidators ...Validator[T]) TypedHandler[T]

func CastCustomType

func CastCustomType[T, U any](baseHandler TypedHandler[T]) TypedHandler[U]

func DefaultDurationType

func DefaultDurationType() TypedHandler[time.Duration]

func DefaultFloatIntegerType

func DefaultFloatIntegerType[T ~float32 | ~float64]() TypedHandler[T]

func DefaultIntegerType

func DefaultIntegerType[T ~int | ~int8 | ~int16 | ~int32 | ~int64]() TypedHandler[T]

func DefaultStringType

func DefaultStringType[T ~string]() TypedHandler[T]

func DefaultUnsignedIntegerType

func DefaultUnsignedIntegerType[T ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64]() TypedHandler[T]

func NewCustomType

func NewCustomType[T any](customParser FieldProcessor[T], customValidators ...Validator[T]) TypedHandler[T]

func NewStringEnumType

func NewStringEnumType[T ~string](validValues ...T) TypedHandler[T]

func TransformCustomType added in v0.4.0

func TransformCustomType[T, U any](baseHandler TypedHandler[T], transform Transform[T, U]) TypedHandler[U]

TransformCustomType creates a TypedHandler that applies a Transform function to process data from a base handler.

type Validator

type Validator[T any] = readpipeline.Validator[T]

type Wrapper

type Wrapper[T any] = readpipeline.Wrapper[T]

Directories

Path Synopsis
example
custom_tags command
custom_types command
simple command
validation command
internal
customtypes
Package customtypes provides the building blocks to make custom types for GoConfig.
Package customtypes provides the building blocks to make custom types for GoConfig.

Jump to

Keyboard shortcuts

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