Files
golib/ezconf/ezconf.go
2026-02-25 21:52:57 +11:00

153 lines
4.5 KiB
Go

package ezconf
import (
"os"
"github.com/pkg/errors"
)
// EnvVar represents a single environment variable with its metadata
type EnvVar struct {
Name string // The environment variable name (e.g., "LOG_LEVEL")
Description string // Description of what this variable does
Required bool // Whether this variable is required
Default string // Default value if not set
CurrentValue string // Current value from environment (empty if not set)
Group string // Group name for organizing variables (e.g., "Database", "Logging")
}
// configStruct holds a config struct pointer and its group name for parsing
type configStruct struct {
configPtr any
groupName string
}
// ConfigLoader manages configuration loading from multiple sources
type ConfigLoader struct {
configFuncs map[string]ConfigFunc // Map of config names to ConfigFromEnv functions
configStructs []configStruct // Config struct pointers for tag parsing
extraEnvVars []EnvVar // Additional environment variables to track
envVars []EnvVar // All extracted environment variables
configs map[string]any // Loaded configurations
}
// ConfigFunc is a function that loads configuration from environment variables
type ConfigFunc func() (any, error)
// New creates a new ConfigLoader
func New() *ConfigLoader {
return &ConfigLoader{
configFuncs: make(map[string]ConfigFunc),
configStructs: make([]configStruct, 0),
extraEnvVars: make([]EnvVar, 0),
envVars: make([]EnvVar, 0),
configs: make(map[string]any),
}
}
// AddConfigFunc adds a ConfigFromEnv function to be called during loading.
// The name parameter is used as a key to retrieve the loaded config later.
func (cl *ConfigLoader) AddConfigFunc(name string, fn ConfigFunc) error {
if fn == nil {
return errors.New("config function cannot be nil")
}
if name == "" {
return errors.New("config name cannot be empty")
}
cl.configFuncs[name] = fn
return nil
}
// AddConfigStruct adds a config struct pointer for parsing ezconf tags.
// The configPtr must be a pointer to a struct with ezconf struct tags.
// The groupName is used for organizing environment variables in output.
func (cl *ConfigLoader) AddConfigStruct(configPtr any, groupName string) error {
if configPtr == nil {
return errors.New("config pointer cannot be nil")
}
if groupName == "" {
groupName = "Other"
}
cl.configStructs = append(cl.configStructs, configStruct{
configPtr: configPtr,
groupName: groupName,
})
return nil
}
// AddEnvVar adds an additional environment variable to track
func (cl *ConfigLoader) AddEnvVar(envVar EnvVar) {
cl.extraEnvVars = append(cl.extraEnvVars, envVar)
}
// ParseEnvVars extracts environment variables from config struct tags and extra vars.
// This can be called without having actual environment variables set.
func (cl *ConfigLoader) ParseEnvVars() error {
// Clear existing env vars to prevent duplicates
cl.envVars = make([]EnvVar, 0)
// Parse config structs for ezconf tags
for _, cs := range cl.configStructs {
envVars, err := ParseConfigStruct(cs.configPtr)
if err != nil {
return errors.Wrap(err, "failed to parse config struct")
}
// Set group name for these variables
for i := range envVars {
envVars[i].Group = cs.groupName
}
cl.envVars = append(cl.envVars, envVars...)
}
// Add extra env vars
cl.envVars = append(cl.envVars, cl.extraEnvVars...)
// Populate current values from environment
for i := range cl.envVars {
cl.envVars[i].CurrentValue = os.Getenv(cl.envVars[i].Name)
}
return nil
}
// LoadConfigs executes the config functions to load actual configurations.
// This should be called after environment variables are properly set.
func (cl *ConfigLoader) LoadConfigs() error {
// Load configurations
for name, fn := range cl.configFuncs {
cfg, err := fn()
if err != nil {
return errors.Wrapf(err, "failed to load config: %s", name)
}
cl.configs[name] = cfg
}
return nil
}
// Load loads all configurations and extracts environment variables
func (cl *ConfigLoader) Load() error {
if err := cl.ParseEnvVars(); err != nil {
return err
}
return cl.LoadConfigs()
}
// GetConfig returns a loaded configuration by name
func (cl *ConfigLoader) GetConfig(name string) (any, bool) {
cfg, ok := cl.configs[name]
return cfg, ok
}
// GetAllConfigs returns all loaded configurations
func (cl *ConfigLoader) GetAllConfigs() map[string]any {
return cl.configs
}
// GetEnvVars returns all extracted environment variables
func (cl *ConfigLoader) GetEnvVars() []EnvVar {
return cl.envVars
}