cfggo

package module
v1.0.22 Latest Latest
Warning

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

Go to latest
Published: Jun 20, 2025 License: MIT Imports: 21 Imported by: 0

README

cfggo

cfggo is a Go package designed to simplify configuration management in Go applications. It provides a flexible and powerful way to handle configuration through various sources such as files, environment variables, and HTTP endpoints. The package supports default values, dynamic configuration updates, and command-line flags.

Key Features

  • Type-safe configuration: All configuration values are accessed through strongly-typed functions
  • Hot reloadable: Configuration can be reloaded at runtime without restarting your application
  • Function-based approach: Configuration values are accessed through functions, not direct field access
  • Multiple configuration sources: Load from JSON files, environment variables, and HTTP endpoints
  • Command-line flag integration: Seamless integration with Go's flag package
  • Thread-safe: All operations are protected by mutexes for concurrent access
  • Validation support: Register validators for configuration values
  • Default values: Specify default values for configuration items
  • Automatic type conversion: Values are automatically converted to the correct type

Installation

To install the package, run:

go get github.com/iqhive/cfggo

How It Works

cfggo uses a unique approach to configuration management:

  1. Function-based access: Instead of accessing configuration values directly, you define functions that return the values
  2. Embedded Structure: Your configuration struct embeds the cfggo.Structure type
  3. Type safety: The return type of each function defines the type of the configuration value
  4. Hot reloading: Configuration can be reloaded at runtime, and all functions will return the updated values

Basic Example

Here's a simple example of how to use cfggo:

package main

import (
    "fmt"
    "github.com/iqhive/cfggo"
)

// Define your configuration struct
type MyConfig struct {
    cfggo.Structure // Embed the Structure type
    
    // Define configuration items as functions that return the desired type
    ServerPort    func() int           `cfggo:"server_port" default:"8080" help:"Port for the server to listen on"`
    DatabaseURL   func() string        `cfggo:"db_url" default:"postgres://localhost:5432/mydb" help:"Database connection URL"`
    FeatureFlags  func() map[string]bool `cfggo:"features" default:"{\"new_ui\": false, \"analytics\": true}" help:"Feature flags"`
    LogLevel      func() string        `cfggo:"log_level" default:"info" help:"Logging level"`
}

func main() {
    // Create a new configuration instance
    config := &MyConfig{
        // Set default values using the DefaultValue helper
        ServerPort:   cfggo.DefaultValue(8080),
        DatabaseURL:  cfggo.DefaultValue("postgres://localhost:5432/mydb"),
        FeatureFlags: cfggo.DefaultValue(map[string]bool{"new_ui": false, "analytics": true}),
        LogLevel:     cfggo.DefaultValue("info"),
    }
    
    // Initialize the configuration
    // This will load values from a JSON file, environment variables, and command-line flags
    cfggo.Init(config, cfggo.WithDefaultFileConfig("config.json"))
    
    // Access configuration values through the functions
    fmt.Printf("Server will listen on port %d\n", config.ServerPort())
    fmt.Printf("Database URL: %s\n", config.DatabaseURL())
    fmt.Printf("Log level: %s\n", config.LogLevel())
    
    // Feature flags are accessed as a map
    if config.FeatureFlags()["new_ui"] {
        fmt.Println("New UI is enabled")
    }
}

Configuration Sources and Precedence

cfggo loads configuration from multiple sources in the following order (later sources override earlier ones):

  1. Default values: Set in code using cfggo.DefaultValue() or via struct tags
  2. Configuration files: JSON files loaded via options
  3. Environment variables: Automatically mapped from config keys (e.g., server_portSERVER_PORT)
  4. Command-line flags: Automatically registered based on your struct fields

Environment Variables

Environment variables are automatically mapped from your configuration keys. The mapping follows these rules:

  • Keys are converted to uppercase
  • Dots (.) are replaced with underscores (_)

For example:

  • server_portSERVER_PORT
  • db.urlDB_URL

Command-Line Flags

Command-line flags are automatically registered based on your struct fields. The flag name is the same as the configuration key.

For example:

--server_port=8080
--db_url="postgres://localhost:5432/mydb"
--log_level=debug

Boolean flags can be used with or without a value:

--feature_enabled        # Sets to true
--feature_enabled=true   # Sets to true
--feature_enabled=false  # Sets to false

Hot Reloading

One of the key features of cfggo is the ability to reload configuration at runtime. This is useful for applications that need to change configuration without restarting.

// Reload configuration from all sources
if err := config.ReloadConfig(); err != nil {
    log.Fatalf("Failed to reload configuration: %v", err)
}

// After reloading, all function calls will return the updated values
fmt.Printf("Updated server port: %d\n", config.ServerPort())

Validation

You can register validators for configuration values to ensure they meet your requirements:

// Register a validator for the "server_port" configuration key
config.RegisterValidator("server_port", func(value interface{}) error {
    port, ok := value.(int)
    if !ok {
        return errors.New("server_port must be an integer")
    }
    if port < 1024 || port > 65535 {
        return errors.New("server_port must be between 1024 and 65535")
    }
    return nil
})

// Validate the entire configuration
if err := config.Validate(); err != nil {
    log.Fatalf("Configuration validation failed: %v", err)
}

// Validate a specific configuration key
if err := config.ValidateKey("server_port"); err != nil {
    log.Fatalf("Validation of server_port failed: %v", err)
}

Advanced Features

Custom Configuration Sources

You can implement custom configuration sources by implementing the configHandler interface:

type configHandler interface {
    LoadConfig() ([]byte, error)
    SaveConfig([]byte) error
}
Nested Configuration

You can use nested structures for more complex configuration:

type DatabaseConfig struct {
    Host     func() string `cfggo:"host" default:"localhost" help:"Database host"`
    Port     func() int    `cfggo:"port" default:"5432" help:"Database port"`
    Username func() string `cfggo:"username" default:"user" help:"Database username"`
    Password func() string `cfggo:"password" default:"pass" help:"Database password"`
}

type MyConfig struct {
    cfggo.Structure
    ServerPort func() int           `cfggo:"server_port" help:"Server port"`
    Database   DatabaseConfig       `cfggo:"database" help:"Database configuration"`
}
Enhanced Validation

The package provides a rich set of built-in validators for common validation scenarios:

// Add validators to your configuration
config.AddValidator("server_port", cfggo.Range(1024, 65535))
config.AddValidator("email", cfggo.Email())
config.AddValidator("username", cfggo.All(
    cfggo.Required(),
    cfggo.MinLength(3),
    cfggo.MaxLength(20),
))
config.AddValidator("api_key", cfggo.Regex(`^[A-Za-z0-9]{32}$`))
config.AddValidator("log_level", cfggo.OneOf("debug", "info", "warn", "error"))
config.AddValidator("website", cfggo.URL())

// Validate the entire configuration
if err := config.Validate(); err != nil {
    // ValidationErrors provides detailed information about all validation failures
    log.Fatalf("Configuration validation failed: %v", err)
}

Available validators include:

  • Required() - Ensures a value is not nil or empty
  • MinLength(min) - Checks if a string, slice, or map has at least min elements
  • MaxLength(max) - Checks if a string, slice, or map has at most max elements
  • Range(min, max) - Ensures a numeric value is within the specified range
  • OneOf(options...) - Checks if a value is one of the provided options
  • Regex(pattern) - Validates a string against a regular expression
  • Email() - Validates that a string is a valid email address
  • URL() - Validates that a string is a valid URL
  • Custom(func) - Creates a validator from a custom function
  • All(validators...) - Ensures all validators pass
  • Any(validators...) - Ensures at least one validator passes
Logging

The package includes a configurable logging system that can be adjusted based on your application's needs:

// Set the log level
cfggo.SetLogLevel(cfggo.LogLevelDebug)

// Parse log level from string
level, _ := cfggo.ParseLogLevel("info")
cfggo.SetLogLevel(level)

// Redirect logs to a file
file, _ := os.OpenFile("config.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
cfggo.SetLogOutput(file)

Available log levels:

  • LogLevelDebug - Detailed debugging information
  • LogLevelInfo - General information about normal operation
  • LogLevelWarn - Warnings that don't affect normal operation
  • LogLevelError - Errors that affect operation but don't cause termination
  • LogLevelFatal - Errors that cause termination
  • LogLevelNone - Disables all logging
Configuration Options

cfggo provides several options for initializing configuration:

// Load configuration from a file
cfggo.Init(config, cfggo.WithFileConfig("config.json"))

// Load configuration from a file specified by a command-line flag
cfggo.Init(config, cfggo.WithFileConfigParamName("config"))

// Load configuration from HTTP endpoints
cfggo.Init(config, cfggo.WithHTTPConfig(httpLoader, httpSaver))

// Skip loading from environment variables
cfggo.Init(config, cfggo.WithSkipEnvironment())

// Enable automatic saving of configuration on program exit
cfggo.Init(config, cfggo.WithAutoSave())

// Use a custom FlagSet
cfggo.Init(config, cfggo.WithFlagSet(myFlagSet))

// Add a validator during initialization
cfggo.Init(config, cfggo.WithValidation("server_port", portValidator))

Thread Safety

All operations in cfggo are protected by mutexes, making it safe to use in concurrent applications. You can access and update configuration values from multiple goroutines without worrying about race conditions.

Contributing

Contributions are welcome! If you find any issues or have suggestions for new features, please open an issue or submit a pull request on the GitHub repository.

License

cfggo is licensed under the MIT License.

Acknowledgments

cfggo is inspired by the viper configuration package. It aims to provide a more lightweight and flexible alternative with additional features like type safety and custom configuration handlers.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrorWrapper errorWrapper = defaultErrorWrapper
	Logger       logger       = NewDefaultLogger()
)

Functions

func CleanupSignalHandler added in v1.0.22

func CleanupSignalHandler()

CleanupSignalHandler allows for graceful cleanup of the signal handler This should be called in tests or when the application wants to clean up

func ConvertValue added in v1.0.15

func ConvertValue(value interface{}, targetType reflect.Type) (interface{}, error)

ConvertValue converts a value to the target type This function extracts common type conversion logic from set() and dynamicVar.Set()

func DefaultValue

func DefaultValue[T any](x T) func() T

DefaultValue returns a function that returns the type of the input parameter X

func IgnoreFlags

func IgnoreFlags(flags ...string)

func Validate added in v1.0.19

func Validate(configName string, config map[string]interface{}) error

Validate validates the configuration

Types

type ConfigHandler added in v1.0.22

type ConfigHandler interface {
	// IsDefault should return true if the application may continue to start up if an error
	// is returned from [ConfigHandler.LoadConfig].
	IsDefault() bool
	// LoadConfig should load the JSON representation of the config.
	LoadConfig() (json.RawMessage, error)
	// SaveConfig should save the JSON representation of the config.
	SaveConfig(json.RawMessage) error
}

type DefaultLogger added in v1.0.19

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

DefaultLogger is the default implementation of ExtendedLogger

func NewDefaultLogger added in v1.0.19

func NewDefaultLogger() *DefaultLogger

NewDefaultLogger creates a new DefaultLogger with the specified level

func (*DefaultLogger) Debug added in v1.0.19

func (dl *DefaultLogger) Debug(msg string, args ...interface{})

Debug logs a debug message

func (*DefaultLogger) Debugf added in v1.0.19

func (dl *DefaultLogger) Debugf(format string, args ...interface{})

func (*DefaultLogger) Error added in v1.0.19

func (dl *DefaultLogger) Error(msg string, args ...interface{})

Error logs an error message

func (*DefaultLogger) Errorf added in v1.0.19

func (dl *DefaultLogger) Errorf(format string, args ...interface{})

func (*DefaultLogger) Fatal added in v1.0.19

func (dl *DefaultLogger) Fatal(msg string, args ...interface{})

Fatal logs a fatal message and exits

func (*DefaultLogger) Fatalf added in v1.0.19

func (dl *DefaultLogger) Fatalf(format string, args ...interface{})

func (*DefaultLogger) Info added in v1.0.19

func (dl *DefaultLogger) Info(msg string, args ...interface{})

Info logs an info message

func (*DefaultLogger) Infof added in v1.0.19

func (dl *DefaultLogger) Infof(format string, args ...interface{})

func (*DefaultLogger) Warn added in v1.0.19

func (dl *DefaultLogger) Warn(msg string, args ...interface{})

Warn logs a warning message

func (*DefaultLogger) Warnf added in v1.0.19

func (dl *DefaultLogger) Warnf(format string, args ...interface{})

type NoopLogger added in v1.0.19

type NoopLogger struct{}

NoopLogger is a logger that does nothing

func (*NoopLogger) Debug added in v1.0.19

func (nl *NoopLogger) Debug(msg string, args ...interface{})

Debug does nothing

func (*NoopLogger) Error added in v1.0.19

func (nl *NoopLogger) Error(msg string, args ...interface{})

Error does nothing

func (*NoopLogger) Fatal added in v1.0.19

func (nl *NoopLogger) Fatal(msg string, args ...interface{})

Fatal does nothing

func (*NoopLogger) Info added in v1.0.19

func (nl *NoopLogger) Info(msg string, args ...interface{})

Info does nothing

func (*NoopLogger) Warn added in v1.0.19

func (nl *NoopLogger) Warn(msg string, args ...interface{})

Warn does nothing

type Option added in v1.0.3

type Option func(*Structure) error

Option is a function that configures a Structure

func WithAutoSave added in v1.0.14

func WithAutoSave() Option

WithAutoSave enables automatic saving of configuration on program exit

func WithConfigHandler added in v1.0.22

func WithConfigHandler(handler ConfigHandler) Option

WithConfigHandler uses the given ConfigHandler to save and load configuration.

func WithDefaultFileConfig added in v1.0.9

func WithDefaultFileConfig(filename string) Option

withFileConfig sets the config source/dest to a filename, and ignores if unable to find file

func WithFileConfig added in v1.0.3

func WithFileConfig(filename string) Option

withFileConfig sets the config source/dest to a filename, and logs and error if unable to find file

func WithFileConfigParamName added in v1.0.7

func WithFileConfigParamName(argName string) Option

WithFileConfigParamName sets the config source/dest to a filename defined in the command line arguments

func WithFlagSet added in v1.0.15

func WithFlagSet(fs *flag.FlagSet) Option

WithFlagSet sets a custom FlagSet for the configuration

func WithHTTPConfig added in v1.0.3

func WithHTTPConfig(httpLoader *http.Request, httpSaver *http.Request) Option

WithHTTPConfig sets the config source/dest to a filename

func WithName added in v1.0.3

func WithName(name string) Option

WithName sets the name of the configuration

func WithSkipEnvironment added in v1.0.3

func WithSkipEnvironment() Option

WithSkipEnvironment skips loading from environment variables

func WithValidation added in v1.0.15

func WithValidation(key string, validator Validator) Option

WithValidation adds a validator for a configuration key

type Structure added in v1.0.2

type Structure struct {

	// Field to store a dedicated FlagSet instead of using flag.CommandLine
	FlagSet *flag.FlagSet
	// contains filtered or unexported fields
}

func (*Structure) AddValidator added in v1.0.19

func (c *Structure) AddValidator(key string, validator Validator)

AddValidator adds a validator for a configuration key

func (*Structure) CheckUnrecognizedItems added in v1.0.2

func (c *Structure) CheckUnrecognizedItems(s interface{})

func (*Structure) Get added in v1.0.2

func (c *Structure) Get(key string) (interface{}, bool)

Get gets a configuration value and whether it exists from the configData

func (*Structure) GetFlagSet added in v1.0.15

func (c *Structure) GetFlagSet() *flag.FlagSet

GetFlagSet returns the FlagSet used by this configuration This allows applications to register the FlagSet with their own flag parsing system

func (*Structure) GetHelpTag added in v1.0.5

func (c *Structure) GetHelpTag(key string) string

func (*Structure) GetJSONBytes added in v1.0.3

func (c *Structure) GetJSONBytes() []byte

func (*Structure) Init added in v1.0.2

func (c *Structure) Init(parent interface{}, options ...Option)

func (*Structure) InitMyParent added in v1.0.16

func (c *Structure) InitMyParent(options ...Option)

InitMyParent automatically determines the parent struct that contains this Structure and calls Init() with that parent.

func (*Structure) InitSelf added in v1.0.16

func (c *Structure) InitSelf(options ...Option)

func (*Structure) NewFlag added in v1.0.3

func (c *Structure) NewFlag(configVarName string, defaultValue interface{}, configDescription string)

NewFlag creates a new configuration item, using the type of the defaultValue

func (*Structure) RegisterValidator added in v1.0.15

func (c *Structure) RegisterValidator(key string, validator Validator)

RegisterValidator registers a validation function for a configuration key

func (*Structure) Reload added in v1.0.15

func (c *Structure) Reload() error

Reload reloads the configuration from all sources

func (*Structure) ReloadConfig added in v1.0.15

func (c *Structure) ReloadConfig() error

ReloadConfig reloads the configuration from sources

func (*Structure) Set added in v1.0.2

func (c *Structure) Set(key string, value interface{}) error

Set sets a configuration value and then updates the config struct as well

func (*Structure) String added in v1.0.3

func (c *Structure) String() string

func (*Structure) Validate added in v1.0.15

func (c *Structure) Validate() error

Validate validates all configuration values against registered validators

func (*Structure) ValidateKey added in v1.0.15

func (c *Structure) ValidateKey(key string) error

ValidateKey validates a specific configuration key against its registered validator

type ValidationError added in v1.0.19

type ValidationError struct {
	Key string
	Err error
}

ValidationError represents an error that occurred during validation

func (ValidationError) Error added in v1.0.19

func (v ValidationError) Error() string

Error implements the error interface

func (ValidationError) Unwrap added in v1.0.19

func (v ValidationError) Unwrap() error

Unwrap returns the underlying error

type ValidationErrors added in v1.0.19

type ValidationErrors []ValidationError

ValidationErrors represents multiple validation errors

func (ValidationErrors) Error added in v1.0.19

func (v ValidationErrors) Error() string

Error implements the error interface

type Validator added in v1.0.15

type Validator func(interface{}) error

Validator is a function that validates a configuration value

func All added in v1.0.19

func All(validators ...Validator) Validator

All returns a validator that checks if all validators pass

func Any added in v1.0.19

func Any(validators ...Validator) Validator

Any returns a validator that checks if any validator passes

func Custom added in v1.0.19

func Custom(fn func(interface{}) error) Validator

Custom returns a validator that uses a custom function

func Email added in v1.0.19

func Email() Validator

Email returns a validator that checks if a string is a valid email address

func MaxLength added in v1.0.19

func MaxLength(max int) Validator

MaxLength returns a validator that checks if a string or slice has at most max elements

func MinLength added in v1.0.19

func MinLength(min int) Validator

MinLength returns a validator that checks if a string or slice has at least min elements

func OneOf added in v1.0.19

func OneOf(options ...interface{}) Validator

OneOf returns a validator that checks if a value is one of the provided options

func Range added in v1.0.19

func Range(min, max float64) Validator

Range returns a validator that checks if a number is within a range

func Regex added in v1.0.19

func Regex(pattern string) Validator

Regex returns a validator that checks if a string matches a regular expression

func Required added in v1.0.19

func Required() Validator

Required returns a validator that checks if a value is not nil

func URL added in v1.0.19

func URL() Validator

URL returns a validator that checks if a string is a valid URL

Jump to

Keyboard shortcuts

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