conf

package
v0.0.22 Latest Latest
Warning

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

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

README

Configuration Package

The conf package provides a flexible, layered configuration system for Go applications with support for JSON files, environment variables, and validation. It allows for hierarchical configuration with multiple sources and automatic environment variable mapping.

Features

  • JSON configuration file support with hierarchical override system
  • Automatic environment variable mapping
  • Configuration file discovery based on environment
  • Default value support via struct tags
  • Validation system
  • Secret value masking in logs/output
  • Duration string parsing
  • Pretty printing of configuration values

Basic Usage

type MyConfig struct {
    Hop conf.HopConfig        `json:"hop"`
    Database struct {
        Host     string       `json:"host" default:"localhost"`
        Port     int          `json:"port" default:"5432"`
        Password string       `json:"password" secret:"true"`
    } `json:"database"`
}

func main() {
    config := &MyConfig{}
    manager := conf.NewManager(config,
        conf.WithEnvironment("development"),
        conf.WithEnvPrefix("APP"),
    )
    
    if err := manager.Load(); err != nil {
        log.Fatal(err)
    }
}

Configuration Sources

The configuration system loads values in the following order (later sources override earlier ones):

  1. Default values from struct tags
  2. Discovered configuration files
  3. Explicitly specified configuration files
  4. Environment variables

Configuration File Discovery

The system automatically discovers and loads configuration files in the following order:

config.json              # Base configuration
config.local.json       # Local overrides
config/config.json      # Config directory
config/config.local.json # Config directory local overrides
config.<env>.json       # Environment-specific config
config/<env>.json       # Environment-specific in directory
config/config.<env>.json # Environment-specific in config directory

Manager Options

The configuration manager supports several options for customization:

manager := conf.NewManager(config,
    // Set environment for file discovery
    conf.WithEnvironment("development"),
    
    // Add environment variable prefix
    conf.WithEnvPrefix("APP"),
    
    // Add specific config files
    conf.WithConfigFile("config/custom.json"),
    
    // Add multiple config files
    conf.WithConfigFiles("config/base.json", "config/override.json"),
    
    // Add all JSON files from a directory
    conf.WithDefaultConfigDir("config"),
)

Struct Tags

The package supports several struct tags for configuration:

  • json: Specifies the JSON field name
  • default: Sets the default value
  • secret: Marks sensitive values for masking in output

Example:

type Config struct {
    Port     int    `json:"port" default:"8080"`
    Host     string `json:"host" default:"localhost"`
    APIKey   string `json:"api_key" secret:"true"`
    Timeout  conf.Duration `json:"timeout" default:"5m"`
}

Environment Variables

Environment variables are automatically mapped to configuration fields using the following rules:

  1. Field names are converted to SCREAMING_SNAKE_CASE
  2. Nested structs use underscore separation
  3. Optional prefix is prepended if specified

Example mapping:

Config struct:
    Database.Host -> DATABASE_HOST
    Database.MaxConnections -> DATABASE_MAX_CONNECTIONS

With prefix "APP":
    Database.Host -> APP_DATABASE_HOST
    Database.MaxConnections -> APP_DATABASE_MAX_CONNECTIONS

Duration Support

The package includes a special Duration type that supports parsing duration strings in both JSON and environment variables:

type ServerConfig struct {
    ReadTimeout  conf.Duration `json:"read_timeout" default:"15s"`
    WriteTimeout conf.Duration `json:"write_timeout" default:"15s"`
}

Supported duration formats: "300ms", "1.5h", "2h45m", etc.

Validation

The configuration system supports two types of validation:

  1. Framework validation (ensuring required Hop framework configuration)
  2. Custom validation via the Validator interface

To implement custom validation:

func (c *MyConfig) Validate() error {
    if c.Database.Port < 1024 {
        return fmt.Errorf("database port must be > 1024")
    }
    return nil
}

Pretty Printing

The configuration manager includes pretty printing support with automatic masking of sensitive values:

fmt.Println(manager.String())

// Output:
// Database.Host                              = "localhost"
// Database.Port                              = 5432
// Database.Password                          = [REDACTED] "p***d"

Reloading Configuration

The configuration can be reloaded at runtime:

if err := manager.Reload(); err != nil {
    log.Printf("Failed to reload configuration: %v", err)
}

Thread Safety

The configuration manager is thread-safe and can be safely accessed from multiple goroutines. All read and write operations are protected by appropriate mutex locks.

Error Handling

The configuration system provides detailed error messages for:

  • File loading failures
  • Environment variable parsing errors
  • Validation failures
  • Type conversion errors
  • Missing required configurations

Best Practices

  1. Always embed the conf.HopConfig struct in your configuration
  2. Use environment-specific files for different deployment environments
  3. Keep sensitive values in environment variables rather than config files
  4. Use the secret tag for sensitive values to prevent logging exposure
  5. Implement the Validator interface for custom validation rules
  6. Use strongly-typed configuration structs rather than maps
  7. Provide sensible defaults using the default tag

Example Complete Configuration

type Config struct {
    Hop conf.HopConfig `json:"hop"`
    
    Database struct {
        Host            string        `json:"host" default:"localhost"`
        Port            int           `json:"port" default:"5432"`
        User            string        `json:"user" default:"postgres"`
        Password        string        `json:"password" secret:"true"`
        MaxConnections  int           `json:"max_connections" default:"100"`
        ConnTimeout     conf.Duration `json:"conn_timeout" default:"10s"`
    } `json:"database"`
    
    Redis struct {
        Host     string        `json:"host" default:"localhost"`
        Port     int           `json:"port" default:"6379"`
        Timeout  conf.Duration `json:"timeout" default:"5s"`
    } `json:"redis"`
    
    API struct {
        Endpoint    string        `json:"endpoint" default:"http://api.example.com"`
        Timeout     conf.Duration `json:"timeout" default:"30s"`
        MaxRetries  int          `json:"max_retries" default:"3"`
        APIKey      string       `json:"api_key" secret:"true"`
    } `json:"api"`
}

func (c *Config) Validate() error {
    if c.Database.MaxConnections < 1 {
        return fmt.Errorf("database max connections must be positive")
    }
    if c.API.MaxRetries < 0 {
        return fmt.Errorf("api max retries cannot be negative")
    }
    return nil
}

Documentation

Overview

Package conf provides a way to load configuration from JSON files and environment variables, along with a structure to hold the configuration settings for an application and the ability to set up command-line flags for configuration options.

Example
package main

import (
	"fmt"
	"os"

	"github.com/patrickward/hop/conf"
	"github.com/patrickward/hop/conf/conftype"
)

func main() {
	// STEP1: Define a custom configuration struct that embeds HopConfig
	type AppConfig struct {
		Hop   conf.HopConfig // Inherit base configuration
		Redis struct {
			Host    string            `json:"host" env:"REDIS_HOST" default:"localhost"`
			Port    int               `json:"port" env:"REDIS_PORT" default:"6379"`
			Timeout conftype.Duration `json:"timeout" env:"REDIS_TIMEOUT" default:"5s"`
		} `json:"redis"`
		API struct {
			Endpoint string            `json:"endpoint" env:"API_ENDPOINT" default:"http://api.local"`
			Timeout  conftype.Duration `json:"timeout" env:"API_TIMEOUT" default:"30s"`
		} `json:"api"`
	}

	// Create a temporary config file (for example purposes only)
	configJSON := `{
		"hop": {
			"app": { 
				"environment": "production",
				"debug": false
			}
		},
        "redis": {
            "host": "redis.prod.example.com",
            "timeout": "10s"
        },
        "api": {
            "endpoint": "https://api.prod.example.com"
        }
    }`
	tmpFile, err := os.CreateTemp("", "config.*.json")
	if err != nil {
		fmt.Printf("Error creating temp file: %v\n", err)
		return
	}
	defer func(name string) {
		_ = os.Remove(name)
	}(tmpFile.Name())

	if _, err := tmpFile.Write([]byte(configJSON)); err != nil {
		fmt.Printf("Error writing temp file: %v\n", err)
		return
	}
	_ = tmpFile.Close()

	// Set some environment variables (for example purposes only)
	_ = os.Setenv("REDIS_PORT", "6380")
	_ = os.Setenv("API_TIMEOUT", "45s")

	// STEP2: Create and load configuration
	cfg := &AppConfig{}
	cmr := conf.NewManager(cfg, conf.WithConfigFile(tmpFile.Name()))
	if err := cmr.Load(); err != nil {
		fmt.Printf("Error loading config: %v\n", err)
		return
	}
	//if err := conf.Load(cfg, tmpFile.Name()); err != nil {
	//	fmt.Printf("Error loading config: %v\n", err)
	//	return
	//}

	// Print the resulting configuration
	fmt.Printf("Environment: %s\n", cfg.Hop.App.Environment)
	fmt.Printf("Redis Host: %s\n", cfg.Redis.Host)
	fmt.Printf("Redis Port: %d\n", cfg.Redis.Port)
	fmt.Printf("Redis Timeout: %s\n", cfg.Redis.Timeout)
	fmt.Printf("API Endpoint: %s\n", cfg.API.Endpoint)
	fmt.Printf("API Timeout: %s\n", cfg.API.Timeout)

}
Output:

Environment: production
Redis Host: redis.prod.example.com
Redis Port: 6380
Redis Timeout: 10s
API Endpoint: https://api.prod.example.com
API Timeout: 45s

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func PrettyString added in v0.0.18

func PrettyString(cfg interface{}) string

PrettyString returns a formatted string representation of the configuration

func ToScreamingSnake added in v0.0.18

func ToScreamingSnake(s string) string

ToScreamingSnake converts a string from camelCase/PascalCase to SCREAMING_SNAKE_CASE

Types

type AppConfig added in v0.0.8

type AppConfig struct {
	Environment string `json:"environment" default:"development"`
	Debug       bool   `json:"debug" default:"false"`
}

type CsrfConfig added in v0.0.18

type CsrfConfig struct {
	HTTPOnly bool   `json:"http_only" default:"true"`
	Path     string `json:"path" default:"/"`
	MaxAge   int    `json:"max_age" default:"86400"`
	SameSite string `json:"same_site" default:"Lax"`
	Secure   bool   `json:"secure" default:"true"`
}

type EnvParser added in v0.0.18

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

EnvParser handles environment variable parsing for configuration structs

func NewEnvParser added in v0.0.18

func NewEnvParser(namespace string) *EnvParser

NewEnvParser creates a new environment variable parser

func (*EnvParser) Parse added in v0.0.18

func (p *EnvParser) Parse(v interface{}) error

Parse walks through the given struct and populates it from environment variables

func (*EnvParser) ParseStruct added in v0.0.18

func (p *EnvParser) ParseStruct(val reflect.Value, prefix string) error

ParseStruct handles parsing for struct values

func (*EnvParser) ParseStructOLD added in v0.0.18

func (p *EnvParser) ParseStructOLD(val reflect.Value, prefix string) error

ParseStructOLD handles parsing for struct values

type EventsConfig added in v0.0.18

type EventsConfig struct {
	MaxHistory int  `json:"max_history" default:"100"`
	DebugMode  bool `json:"debug_mode" default:"false"`
}

type HopConfig added in v0.0.18

type HopConfig struct {
	App         AppConfig         `json:"app"`
	Csrf        CsrfConfig        `json:"csrf"`
	Events      EventsConfig      `json:"events"`
	Log         LogConfig         `json:"log"`
	Maintenance MaintenanceConfig `json:"maintenance"`
	Server      ServerConfig      `json:"server"`
	Session     SessionConfig     `json:"session"`
}

HopConfig provides core configuration options

func (*HopConfig) IsDevelopment added in v0.0.18

func (c *HopConfig) IsDevelopment() bool

func (*HopConfig) IsProduction added in v0.0.18

func (c *HopConfig) IsProduction() bool

type HopConfigValidator added in v0.0.18

type HopConfigValidator struct{}

HopConfigValidator implements core framework validation

func (*HopConfigValidator) Validate added in v0.0.18

func (v *HopConfigValidator) Validate(cfg interface{}) error

type LogConfig

type LogConfig struct {
	Format      string `json:"format" default:"pretty"`
	IncludeTime bool   `json:"include_time" default:"false"`
	Level       string `json:"level" default:"debug"`
	Verbose     bool   `json:"verbose" default:"false"`
}

type MaintenanceConfig

type MaintenanceConfig struct {
	Enabled bool   `json:"enabled" default:"false"`
	Message string `json:"message" default:""`
}

type Manager added in v0.0.18

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

Manager handles configuration loading and access

func NewManager added in v0.0.18

func NewManager(config interface{}, opts ...Option) *Manager

NewManager creates a Manager instance

func (*Manager) Get added in v0.0.18

func (m *Manager) Get() interface{}

Get returns the current configuration

func (*Manager) Load added in v0.0.18

func (m *Manager) Load() error

Load performs initial load with lock

func (*Manager) Reload added in v0.0.18

func (m *Manager) Reload() error

Reload safely reloads config with new values

func (*Manager) String added in v0.0.18

func (m *Manager) String() string

String returns a pretty string representation of the configuration

type Option added in v0.0.18

type Option func(*Manager)

Option is a functional option for Manager

func WithConfigFile added in v0.0.18

func WithConfigFile(file string) Option

WithConfigFile adds a JSON file to the list of configuration files to load Files are processed in the order they are added

func WithConfigFiles added in v0.0.18

func WithConfigFiles(files ...string) Option

WithConfigFiles adds multiple JSON files to the list of configuration files to load Files are processed in the order they are added

func WithDefaultConfigDir added in v0.0.18

func WithDefaultConfigDir(dir string) Option

WithDefaultConfigDir adds all .json files from a directory to the list of configuration files to load

func WithEnvPrefix added in v0.0.18

func WithEnvPrefix(prefix string) Option

WithEnvPrefix sets the environment variable prefix, which makes the env parser look for variables with the prefix. For example, if the prefix is "APP", the parser will only look for environment variables like "APP_PORT" and "APP_DEBUG".

func WithEnvironment added in v0.0.18

func WithEnvironment(env string) Option

WithEnvironment sets the environment for configuration file discovery

type ServerConfig

type ServerConfig struct {
	BaseURL         string            `json:"base_url" default:"http://localhost:4444"`
	Host            string            `json:"host" default:"localhost"`
	Port            int               `json:"port" default:"4444"`
	IdleTimeout     conftype.Duration `json:"idle_timeout" default:"120s"`
	ReadTimeout     conftype.Duration `json:"read_timeout" default:"15s"`
	WriteTimeout    conftype.Duration `json:"write_timeout" default:"15s"`
	ShutdownTimeout conftype.Duration `json:"shutdown_timeout" default:"10s"`
}

type SessionConfig added in v0.0.18

type SessionConfig struct {
	Lifetime      conftype.Duration `json:"lifetime" default:"168h"`
	CookiePersist bool              `json:"cookie_persist" default:"true"`
	// Other same-site values: "none", "strict"
	CookieSameSite string `json:"cookie_same_site" default:"lax"`
	CookieSecure   bool   `json:"cookie_secure" default:"true"`
	CookieHTTPOnly bool   `json:"cookie_http_only" default:"true"`
	CookiePath     string `json:"cookie_path" default:"/"`
}

type StringParser added in v0.0.18

type StringParser interface {
	ParseString(s string) error
}

StringParser is implemented by types that can parse themselves from strings

type Validator added in v0.0.18

type Validator interface {
	Validate() error
}

Validator interface allows configs to implement their own validation

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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