gonfig

package module
v0.202508.0 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2025 License: MIT Imports: 23 Imported by: 0

README

⚙︎ vivaneiona/gonfig

Go Reference

A basic, heavy, ad-hoc configuration loader for Go.


Features

  • Loads struct-tagged settings directly from environment variables
  • Optional .env support
  • Defaults and CSV slices
  • Extended parsing for time.Duration, uuid.UUID, decimal.Decimal, vm.Program (expr), and other specialized types

Quick Start

package main

import (
	"fmt"

	"github.com/vivaneiona/gonfig"
	"github.com/expr-lang/expr"
	"github.com/expr-lang/expr/vm"
)


type Config struct {
	Port int    `env:"PORT"   default:"8080"`
	Host string `env:"HOST"   default:"localhost"`

	Debug  bool     `env:"DEBUG"  default:"false"`
	APIKey string   `secret:"API_KEY"`
	Tags   []string `env:"TAGS"   default:"web,api"`

	DefaultMem  resource.Quantity `env:"DEFAULT_MEM" default:"1Gi"`
	DefaultDisk resource.Quantity `env:"DEFAULT_DISK" default:"10G"`
	IPAddress   net.IP            `env:"IP_ADDRESS"`
	EmailAddr   mail.Address      `env:"EMAIL_ADDR"`

	LogLevel slog.Level `env:"LOG_LEVEL"`

	Database struct {
		Host string `env:"DB_HOST" default:"localhost"`
		Port int    `env:"DB_PORT" default:"5432"`
	}

	DatabaseURL url.URL `env:"DB_URL"`
}

func main() {
	cfg, err := gonfig.Load(Config{})
	if err != nil {
		panic(err)
	}

	fmt.Println(gonfig.PrettyString(cfg)) // secrets masked

	// Use the compiled expression
	if cfg.AccessRule != nil {
		env := map[string]interface{}{
			"user": map[string]interface{}{
				"role":     "user",
				"verified": true,
			},
		}
		result, err := expr.Run(cfg.AccessRule, env)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Access granted: %v\n", result)
	}
}

Custom Types

You can easily add support for your own types by implementing the encoding.TextUnmarshaler interface:

package main

import (
	"errors"
	"fmt"
	"strings"

	"github.com/vivaneiona/gonfig"
)

// Custom type that implements encoding.TextUnmarshaler
type LogLevel string

const (
	LogLevelDebug LogLevel = "debug"
	LogLevelInfo  LogLevel = "info"
	LogLevelWarn  LogLevel = "warn"
	LogLevelError LogLevel = "error"
)

// UnmarshalText implements encoding.TextUnmarshaler
func (l *LogLevel) UnmarshalText(text []byte) error {
	level := LogLevel(strings.ToLower(string(text)))
	switch level {
	case LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError:
		*l = level
		return nil
	default:
		return errors.New("invalid log level: must be debug, info, warn, or error")
	}
}

// Another custom type for demonstration
type DatabaseDriver string

const (
	PostgreSQL DatabaseDriver = "postgres"
	MySQL     DatabaseDriver = "mysql"
	SQLite    DatabaseDriver = "sqlite"
)

func (d *DatabaseDriver) UnmarshalText(text []byte) error {
	driver := DatabaseDriver(strings.ToLower(string(text)))
	switch driver {
	case PostgreSQL, MySQL, SQLite:
		*d = driver
		return nil
	default:
		return fmt.Errorf("unsupported database driver: %s", text)
	}
}

type Config struct {
	// Your custom types work seamlessly
	LogLevel LogLevel        `env:"LOG_LEVEL" default:"info"`
	Driver   DatabaseDriver  `env:"DB_DRIVER" default:"postgres"`
	
	// Custom types work in slices too
	SupportedDrivers []DatabaseDriver `env:"SUPPORTED_DRIVERS" default:"postgres,mysql"`
	
	// And as pointers
	OptionalLevel *LogLevel `env:"OPTIONAL_LEVEL"`
}

func main() {
	cfg, err := gonfig.Load(Config{})
	if err != nil {
		panic(err)
	}
	
	fmt.Printf("Log Level: %s\n", cfg.LogLevel)
	fmt.Printf("Database Driver: %s\n", cfg.Driver)
	fmt.Printf("Supported Drivers: %v\n", cfg.SupportedDrivers)
}

The library automatically detects types that implement encoding.TextUnmarshaler and uses them for parsing. This works for:

  • Value types and pointer types
  • Slices of custom types
  • Nested structs containing custom types

Expression Language Support

gonfig includes built-in support for expr-lang/expr, a powerful expression language for Go. This allows you to configure business rules, validation logic, and filters using expressions that are compiled once and executed efficiently.

package main

import (
	"fmt"
	"log"

	"github.com/expr-lang/expr"
	"github.com/expr-lang/expr/vm"
	"github.com/vivaneiona/gonfig"
)

type Config struct {
	// Business rules
	AccessRule    *vm.Program `env:"ACCESS_RULE" default:"user.role in ['admin', 'moderator']"`
	RateLimit     *vm.Program `env:"RATE_LIMIT" default:"user.requests_per_hour < 1000"`
	FeatureFlags  *vm.Program `env:"FEATURE_FLAGS"`
	
	// Multiple validation rules
	ValidationRules []*vm.Program `env:"VALIDATION_RULES"`
}

func main() {
	cfg, err := gonfig.Load(Config{})
	if err != nil {
		log.Fatal(err)
	}

	// Use compiled expressions for fast evaluation
	user := map[string]interface{}{
		"role":               "admin",
		"requests_per_hour":  500,
		"verified":           true,
	}

	// Check access
	if cfg.AccessRule != nil {
		hasAccess, err := expr.Run(cfg.AccessRule, map[string]interface{}{"user": user})
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("Access granted: %v\n", hasAccess)
	}

	// Check rate limit
	if cfg.RateLimit != nil {
		withinLimit, err := expr.Run(cfg.RateLimit, map[string]interface{}{"user": user})
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("Within rate limit: %v\n", withinLimit)
	}
}
Expression Examples

Set expressions via environment variables:

export ACCESS_RULE="user.role == 'admin' && user.verified == true"
export RATE_LIMIT="user.requests_per_hour < 1000 && user.tier != 'free'"
export FEATURE_FLAGS="user.beta_features == true || user.role in ['admin', 'developer']"
export VALIDATION_RULES="age >= 18,email matches '^[\\w\\.]+@[\\w\\.]+$',country in ['US', 'CA', 'UK']"

Benefits:

  • Performance: Expressions are compiled once at startup, not evaluated repeatedly
  • Safety: Expression syntax is validated at configuration load time
  • Flexibility: Support for complex logic including arrays, objects, and built-in functions
  • Type Safety: Full integration with gonfig's type system and error handling

API

func Load[T any](cfg *T) *T
func LoadWithDotenv[T any](cfg *T, paths ...string) *T
func PrettyString(v any) string

License

This project is licensed under the MIT License.

Documentation

Overview

Package gonfig provides type-safe configuration loading from environment variables with support for defaults, secret masking, nested structs, and .env files.

The library uses struct tags to control configuration loading:

  • `env` tag: Maps struct fields to environment variables
  • `secret` tag: Maps struct fields to environment variables but masks them in output
  • `default` tag: Provides fallback values when environment variables are not set
  • `required` tag: Makes fields required (fails if not set and no default)

Supported types: string, bool, int (all sizes), float32, float64, slices, nested structs, time.Duration, time.Time, slog.Level, big.Int, decimal.Decimal, url.URL, net.IP, mail.Address, uuid.UUID, resource.Quantity, rsa.PrivateKey, ecdsa.PrivateKey (from PEM), vm.Program (expr-lang/expr), and any type implementing encoding.TextUnmarshaler

New: Nested structs (value or pointer) are fully supported with recursive processing.

Example usage:

type Config struct {
    Port   int    `env:"PORT" default:"8080"`
    APIKey string `secret:"API_KEY" required:"true"`
    DB     struct {
        Host string `env:"DB_HOST" default:"localhost"`
        Port int    `env:"DB_PORT" default:"5432"`
    }
    // PostgreSQL URLs - supports both TCP and Unix socket formats
    DatabaseURL url.URL `env:"DATABASE_URL" default:"postgres://user:pass@localhost:5432/mydb"`
    SocketURL   url.URL `env:"SOCKET_URL" default:"postgresql://user:pass@/mydb?host=/var/run/postgresql"`
}

cfg, err := gonfig.Load(Config{})
if err != nil {
    log.Fatal(err)
}
log.Info("config loaded", "cfg", gonfig.PrettyString(cfg))

Package gonfig provides a small, type-safe configuration loader for Go application// // Crypto keys (PEM format)

PrivateKey rsa.PrivateKey `secret:"PRIVATE_KEY"`

// Expression language for business rules
AccessRule *vm.Program `env:"ACCESS_RULE" default:"user.role == 'admin'"`
Rules      []*vm.Program `env:"BUSINESS_RULES"`

// Database URLs with support for both TCP and Unix sockets/

Features

  • Loads struct-tagged settings directly from environment variables
  • Optional .env file support
  • Default values and CSV slices parsing
  • Extended parsing for time.Duration, uuid.UUID, decimal.Decimal, vm.Program (expr), and other specialized types
  • Secret masking for sensitive configuration values
  • Support for nested structs (both value and pointer types)
  • Comprehensive type support including crypto keys, network addresses, and Kubernetes resource quantities

Supported Types

The library supports a wide range of Go types:

  • Basic types: string, bool, int (all sizes), float32, float64
  • Collections: slices of supported types
  • Time types: time.Duration, time.Time
  • Network types: net.IP, mail.Address, url.URL
  • Crypto types: rsa.PrivateKey, ecdsa.PrivateKey (from PEM format)
  • Specialized types: uuid.UUID, decimal.Decimal, big.Int, slog.Level
  • Kubernetes types: resource.Quantity
  • Expression language: vm.Program (expr-lang/expr for business rules and validation)
  • Any type implementing encoding.TextUnmarshaler
  • Nested structs (recursive processing)

Struct Tags

Configuration is controlled through struct tags:

  • `env:"VAR_NAME"` - Maps field to environment variable
  • `secret:"VAR_NAME"` - Maps field to environment variable but masks it in output
  • `default:"value"` - Provides fallback value when environment variable is not set
  • `required:"true"` - Makes field required (fails if not set and no default)

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/vivaneiona/gonfig"
)

type Config struct {
	Port   int      `env:"PORT"   default:"8080"`
	Host   string   `env:"HOST"   default:"localhost"`
	Debug  bool     `env:"DEBUG"  default:"false"`
	APIKey string   `secret:"API_KEY"`
	Tags   []string `env:"TAGS"   default:"web,api"`

	Database struct {
		Host string `env:"DB_HOST" default:"localhost"`
		Port int    `env:"DB_PORT" default:"5432"`
	}

	DatabaseURL url.URL `env:"DATABASE_URL"`
}

func main() {
	cfg, err := gonfig.Load(Config{})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(gonfig.PrettyString(cfg)) // secrets are masked
}

Advanced Usage

The library supports complex configuration scenarios:

type AdvancedConfig struct {
	// Kubernetes resource quantities
	DefaultMem  resource.Quantity `env:"DEFAULT_MEM" default:"1Gi"`
	DefaultDisk resource.Quantity `env:"DEFAULT_DISK" default:"10G"`

	// Network configuration
	IPAddress net.IP        `env:"IP_ADDRESS"`
	EmailAddr mail.Address  `env:"EMAIL_ADDR"`

	// Logging
	LogLevel slog.Level `env:"LOG_LEVEL"`

	// Crypto keys (PEM format)
	PrivateKey rsa.PrivateKey `secret:"PRIVATE_KEY"`

	// Database URLs with support for both TCP and Unix sockets
	DatabaseURL url.URL `env:"DATABASE_URL" default:"postgres://user:pass@localhost:5432/mydb"`
	SocketURL   url.URL `env:"SOCKET_URL" default:"postgresql://user:pass@/mydb?host=/var/run/postgresql"`
}

Environment File Support

Load configuration with optional .env file support:

cfg, err := gonfig.LoadWithDotenv(Config{}, ".env", ".env.local")
if err != nil {
	log.Fatal(err)
}

API Reference

The package provides three main functions:

func Load[T any](cfg T) (T, error)                    // Load from environment variables only
func LoadWithDotenv[T any](cfg T, paths ...string) (T, error) // Load with .env file support
func PrettyString(v any) string                       // Format config with masked secrets

Error Handling

The library provides detailed error messages for configuration issues:

  • Missing required fields
  • Type conversion failures
  • Invalid default values
  • Malformed PEM keys or other specialized formats

All errors include context about the field name and expected format to aid in debugging.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Load

func Load[T any](config T) (T, error)

Load populates a configuration struct from environment variables using reflection. Now supports nested structs (value or pointer) with recursive processing. It supports the following struct tags:

  • `env:"ENV_VAR"`: Maps the field to the specified environment variable
  • `secret:"SECRET_VAR"`: Maps the field to the specified environment variable (for secrets)
  • `default:"value"`: Sets a default value if the environment variable is not set
  • `required:"true"`: Makes the field required (fails if not set and no default)

Supported field types:

  • string
  • bool (parsed using strconv.ParseBool)
  • int, int8, int16, int32, int64 (parsed using strconv.ParseInt)
  • float32, float64 (parsed using strconv.ParseFloat)
  • slices of the above types (comma-separated values)
  • nested structs (value or pointer)
  • time.Duration for int64 fields (when value ends with 's', 'ms', etc.)
  • time.Duration (parsed using time.ParseDuration)
  • time.Time (RFC3339 format or Unix seconds)
  • log/slog.Level (debug|info|warn|error or integer)
  • math/big.Int (base-10 integer strings)
  • github.com/shopspring/decimal.Decimal (exact decimal arithmetic)
  • url.URL (parsed using url.Parse, supports TCP and Unix socket PostgreSQL URLs) Examples: postgres://user:pass@host:port/db, postgresql://user:pass@/db?host=/socket/path
  • net.IP (IPv4 and IPv6 addresses)
  • net/mail.Address (email addresses with optional display names)
  • github.com/google/uuid.UUID (UUID strings)
  • k8s.io/apimachinery/pkg/api/resource.Quantity (Kubernetes resource units like 250m, 1.5Gi)
  • crypto/rsa.PrivateKey (RSA private keys from PEM format)
  • crypto/ecdsa.PrivateKey (ECDSA private keys from PEM format)
  • github.com/expr-lang/expr/vm.Program (compiled expressions for business rules and validation)
  • Any type implementing encoding.TextUnmarshaler

The function returns an error if:

  • An unsupported field type is encountered
  • A required field is missing
  • Type conversion fails

Example:

type Config struct {
    Port   int    `env:"PORT" default:"8080"`
    Debug  bool   `env:"DEBUG" default:"false"`
    APIKey string `secret:"API_KEY" required:"true"`
    DB     struct {
        Host string `env:"DB_HOST" default:"localhost"`
    }
}

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

func LoadWithDotenv

func LoadWithDotenv[T any](config T, dotenvPath ...string) (T, error)

LoadWithDotenv loads configuration from environment variables with support for .env files. It first attempts to load a .env file using godotenv, then calls Load to populate the configuration struct from environment variables.

The function loads environment variables in this precedence order:

  1. Existing environment variables (highest priority)
  2. Variables from .env file
  3. Default values from struct tags (lowest priority)

If the .env file doesn't exist or can't be loaded, the error is silently ignored and the function continues with existing environment variables.

Parameters:

  • config: Pointer to a configuration struct with tagged fields
  • dotenvPath: Optional path to .env file (defaults to ".env" in current directory)

Example:

type Config struct {
    Port   int    `env:"PORT" default:"8080"`
    APIKey string `secret:"API_KEY"`
}

cfg := &Config{}
loaded := LoadWithDotenv(cfg, "config/.env")

LoadWithDotenv loads a .env file first, then calls Load to populate the configuration. It accepts an optional path to the .env file; if not provided, it defaults to ".env". Returns the populated configuration struct and any error encountered.

Example:

cfg, err := LoadWithDotenv(Config{}, ".env")
if err != nil {
    log.Fatal(err)
}

func PrettyString

func PrettyString(c any) string

PrettyString returns a JSON-formatted string representation of the configuration with secret fields automatically masked for safe logging and debugging. Now supports nested structs (value or pointer) with recursive processing.

Fields tagged with `secret` will have their values masked using the mask function. The function uses struct tags to determine field names in the output:

  • `env` tag value is used as the key name
  • `secret` tag value is used as the key name (and value is masked)
  • If no tag is present, the struct field name is used

Example:

type Config struct {
    Port   int    `env:"PORT"`
    APIKey string `secret:"API_KEY"`
    DB     struct {
        Host string `env:"DB_HOST"`
    }
//	cfg := &Config{Port: 8080, APIKey: "secret123"}
fmt.Println(PrettyString(cfg))
// Output: {"PORT": 8080, "API_KEY": "sec*****", "DB": {"DB_HOST": ""}}

func RegisterParser

func RegisterParser(typ reflect.Type, fn parserFunc)

RegisterParser lets users plug in custom type parsers. Call this in your init() or main() before Load.

func RegisterParserFactory

func RegisterParserFactory(factory parserFactory)

RegisterParserFactory lets users plug in factory functions that can generate parsers for entire categories of types (e.g., anything implementing TextUnmarshaler). Factories are checked in registration order before falling back to explicit parsers.

Types

type FieldSetting

type FieldSetting struct {
	Path      string            // Dot-separated field path (e.g., "DB.Host")
	FieldName string            // Struct field name
	EnvVar    string            // Environment variable name
	Type      string            // Go type name
	Default   string            // Default value from tag
	Required  bool              // Whether field is required
	Secret    bool              // Whether field is marked as secret
	Tags      map[string]string // All struct tags
}

FieldSetting represents metadata about a configuration field

func FilterSettings

func FilterSettings(settings []FieldSetting, predicate func(FieldSetting) bool) []FieldSetting

FilterSettings returns settings matching the given predicate function

func RequiredFields

func RequiredFields(config any) []FieldSetting

RequiredFields returns all required fields

func SecretFields

func SecretFields(config any) []FieldSetting

SecretFields returns all fields marked as secrets

func Settings

func Settings(config any) []FieldSetting

Settings returns metadata about all configuration fields in the struct. It recursively traverses nested structs and collects tag information.

Directories

Path Synopsis
examples
jwt command

Jump to

Keyboard shortcuts

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