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.
This commit is contained in:
2026-03-01 23:45:17 +00:00
parent b16df4e1a9
commit 91b69246fa

82
jety.go
View File

@@ -22,11 +22,14 @@ const (
type ( type (
configType string configType string
// ConfigMap holds a configuration entry with its original key name and value.
ConfigMap struct { ConfigMap struct {
Key string Key string
Value any Value any
} }
// ConfigManager manages layered configuration from defaults, files,
// environment variables, and programmatic overrides.
ConfigManager struct { ConfigManager struct {
configName string configName string
configPath string configPath string
@@ -46,40 +49,47 @@ var (
ErrConfigFileEmpty = errors.New("config file is empty") ErrConfigFileEmpty = errors.New("config file is empty")
) )
func NewConfigManager() *ConfigManager { // parseEnv reads environment variables, optionally filtering by prefix,
cm := ConfigManager{} // and returns a map keyed by lowercased (and prefix-stripped) variable names.
cm.envConfig = make(map[string]ConfigMap) func parseEnv(prefix string) map[string]ConfigMap {
cm.overrideConfig = make(map[string]ConfigMap) result := make(map[string]ConfigMap)
cm.fileConfig = make(map[string]ConfigMap) for _, env := range os.Environ() {
cm.defaultConfig = make(map[string]ConfigMap)
cm.combinedConfig = make(map[string]ConfigMap)
envSet := os.Environ()
for _, env := range envSet {
key, value, found := strings.Cut(env, "=") key, value, found := strings.Cut(env, "=")
if !found { if !found {
continue continue
} }
lower := strings.ToLower(key) if prefix != "" {
cm.envConfig[lower] = ConfigMap{Key: key, Value: value} stripped, ok := strings.CutPrefix(key, prefix)
if !ok {
continue
} }
return &cm key = stripped
}
result[strings.ToLower(key)] = ConfigMap{Key: key, Value: value}
}
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 { func (c *ConfigManager) WithEnvPrefix(prefix string) *ConfigManager {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
envSet := os.Environ() c.envConfig = parseEnv(prefix)
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}
}
}
return c 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() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
switch configType { switch ct {
case "toml": case "toml":
c.configType = ConfigTypeTOML c.configType = ConfigTypeTOML
case "yaml": case "yaml":
@@ -212,29 +222,17 @@ func (c *ConfigManager) SetConfigType(configType string) error {
case "json": case "json":
c.configType = ConfigTypeJSON c.configType = ConfigTypeJSON
default: default:
return fmt.Errorf("config type %s not supported", configType) return fmt.Errorf("config type %s not supported", ct)
} }
return nil 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) { func (c *ConfigManager) SetEnvPrefix(prefix string) {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
// Re-read environment variables, stripping the prefix from matching keys. c.envConfig = parseEnv(prefix)
// 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}
}
}
} }
func (c *ConfigManager) ReadInConfig() error { func (c *ConfigManager) ReadInConfig() error {