From 91b69246fa3c20182d91fd84719b42d03cbb39c6 Mon Sep 17 00:00:00 2001 From: Tai Groot Date: Sun, 1 Mar 2026 23:45:17 +0000 Subject: [PATCH] refactor: extract parseEnv helper, add doc comments, fix param shadow - Extract parseEnv() to deduplicate env parsing in NewConfigManager, WithEnvPrefix, and SetEnvPrefix (was 3 copies of the same logic). - Add doc comments to ConfigMap, ConfigManager, NewConfigManager, WithEnvPrefix, SetEnvPrefix, IsSet, AllKeys, AllSettings. - Rename configType parameter in SetConfigType to avoid shadowing the configType type. --- jety.go | 82 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/jety.go b/jety.go index 9eee510..124c587 100644 --- a/jety.go +++ b/jety.go @@ -22,11 +22,14 @@ const ( type ( configType string + // ConfigMap holds a configuration entry with its original key name and value. ConfigMap struct { Key string Value any } + // ConfigManager manages layered configuration from defaults, files, + // environment variables, and programmatic overrides. ConfigManager struct { configName string configPath string @@ -46,40 +49,47 @@ var ( ErrConfigFileEmpty = errors.New("config file is empty") ) -func NewConfigManager() *ConfigManager { - cm := ConfigManager{} - cm.envConfig = make(map[string]ConfigMap) - cm.overrideConfig = make(map[string]ConfigMap) - cm.fileConfig = make(map[string]ConfigMap) - cm.defaultConfig = make(map[string]ConfigMap) - cm.combinedConfig = make(map[string]ConfigMap) - envSet := os.Environ() - for _, env := range envSet { +// parseEnv reads environment variables, optionally filtering by prefix, +// and returns a map keyed by lowercased (and prefix-stripped) variable names. +func parseEnv(prefix string) map[string]ConfigMap { + result := make(map[string]ConfigMap) + for _, env := range os.Environ() { key, value, found := strings.Cut(env, "=") if !found { continue } - lower := strings.ToLower(key) - cm.envConfig[lower] = ConfigMap{Key: key, Value: value} + if prefix != "" { + stripped, ok := strings.CutPrefix(key, prefix) + if !ok { + continue + } + key = stripped + } + result[strings.ToLower(key)] = ConfigMap{Key: key, Value: value} } - return &cm + return result } +// NewConfigManager creates a new ConfigManager with all environment +// variables loaded. Use [ConfigManager.WithEnvPrefix] or +// [ConfigManager.SetEnvPrefix] to filter by prefix. +func NewConfigManager() *ConfigManager { + return &ConfigManager{ + envConfig: parseEnv(""), + overrideConfig: make(map[string]ConfigMap), + fileConfig: make(map[string]ConfigMap), + defaultConfig: make(map[string]ConfigMap), + combinedConfig: make(map[string]ConfigMap), + } +} + +// WithEnvPrefix filters environment variables to only those starting with +// the given prefix, stripping the prefix from key names. Returns the +// ConfigManager for chaining. func (c *ConfigManager) WithEnvPrefix(prefix string) *ConfigManager { c.mutex.Lock() defer c.mutex.Unlock() - envSet := os.Environ() - c.envConfig = make(map[string]ConfigMap) - for _, env := range envSet { - key, value, found := strings.Cut(env, "=") - if !found { - continue - } - if withoutPrefix, ok := strings.CutPrefix(key, prefix); ok { - lower := strings.ToLower(withoutPrefix) - c.envConfig[lower] = ConfigMap{Key: withoutPrefix, Value: value} - } - } + c.envConfig = parseEnv(prefix) return c } @@ -201,10 +211,10 @@ func (c *ConfigManager) WriteConfig() error { } } -func (c *ConfigManager) SetConfigType(configType string) error { +func (c *ConfigManager) SetConfigType(ct string) error { c.mutex.Lock() defer c.mutex.Unlock() - switch configType { + switch ct { case "toml": c.configType = ConfigTypeTOML case "yaml": @@ -212,29 +222,17 @@ func (c *ConfigManager) SetConfigType(configType string) error { case "json": c.configType = ConfigTypeJSON default: - return fmt.Errorf("config type %s not supported", configType) + return fmt.Errorf("config type %s not supported", ct) } return nil } +// SetEnvPrefix filters environment variables to only those starting with +// the given prefix, stripping the prefix from key names. func (c *ConfigManager) SetEnvPrefix(prefix string) { c.mutex.Lock() defer c.mutex.Unlock() - // Re-read environment variables, stripping the prefix from matching keys. - // This mirrors WithEnvPrefix behavior so that prefixed env vars are - // accessible by their unprefixed key name. - envSet := os.Environ() - c.envConfig = make(map[string]ConfigMap) - for _, env := range envSet { - key, value, found := strings.Cut(env, "=") - if !found { - continue - } - if withoutPrefix, ok := strings.CutPrefix(key, prefix); ok { - lower := strings.ToLower(withoutPrefix) - c.envConfig[lower] = ConfigMap{Key: withoutPrefix, Value: value} - } - } + c.envConfig = parseEnv(prefix) } func (c *ConfigManager) ReadInConfig() error {