diff --git a/getters.go b/getters.go index b45c224..dbb6690 100644 --- a/getters.go +++ b/getters.go @@ -12,7 +12,7 @@ func (c *ConfigManager) Get(key string) any { defer c.mutex.RUnlock() v, ok := c.combinedConfig[strings.ToLower(key)] if !ok { - v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)] + v, ok = c.envConfig[strings.ToLower(key)] if !ok { return nil } @@ -25,7 +25,7 @@ func (c *ConfigManager) GetBool(key string) bool { defer c.mutex.RUnlock() v, ok := c.combinedConfig[strings.ToLower(key)] if !ok { - v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)] + v, ok = c.envConfig[strings.ToLower(key)] if !ok { return false } @@ -56,7 +56,7 @@ func (c *ConfigManager) GetDuration(key string) time.Duration { defer c.mutex.RUnlock() v, ok := c.combinedConfig[strings.ToLower(key)] if !ok { - v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)] + v, ok = c.envConfig[strings.ToLower(key)] if !ok { return 0 } @@ -91,7 +91,7 @@ func (c *ConfigManager) GetString(key string) string { defer c.mutex.RUnlock() v, ok := c.combinedConfig[strings.ToLower(key)] if !ok { - v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)] + v, ok = c.envConfig[strings.ToLower(key)] if !ok { return "" } @@ -110,7 +110,7 @@ func (c *ConfigManager) GetStringMap(key string) map[string]any { defer c.mutex.RUnlock() v, ok := c.combinedConfig[strings.ToLower(key)] if !ok { - v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)] + v, ok = c.envConfig[strings.ToLower(key)] if !ok { return nil } @@ -128,7 +128,7 @@ func (c *ConfigManager) GetStringSlice(key string) []string { defer c.mutex.RUnlock() v, ok := c.combinedConfig[strings.ToLower(key)] if !ok { - v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)] + v, ok = c.envConfig[strings.ToLower(key)] if !ok { return nil } @@ -157,7 +157,7 @@ func (c *ConfigManager) GetInt(key string) int { defer c.mutex.RUnlock() v, ok := c.combinedConfig[strings.ToLower(key)] if !ok { - v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)] + v, ok = c.envConfig[strings.ToLower(key)] if !ok { return 0 } @@ -189,7 +189,7 @@ func (c *ConfigManager) GetIntSlice(key string) []int { defer c.mutex.RUnlock() v, ok := c.combinedConfig[strings.ToLower(key)] if !ok { - v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)] + v, ok = c.envConfig[strings.ToLower(key)] if !ok { return nil } diff --git a/jety.go b/jety.go index 10e1350..e55547f 100644 --- a/jety.go +++ b/jety.go @@ -33,7 +33,6 @@ type ( configPath string configFileUsed string configType configType - envPrefix string mapConfig map[string]ConfigMap defaultConfig map[string]ConfigMap envConfig map[string]ConfigMap @@ -54,7 +53,6 @@ func NewConfigManager() *ConfigManager { cm.mapConfig = make(map[string]ConfigMap) cm.defaultConfig = make(map[string]ConfigMap) cm.combinedConfig = make(map[string]ConfigMap) - cm.envPrefix = "" envSet := os.Environ() for _, env := range envSet { key, value, found := strings.Cut(env, "=") @@ -82,8 +80,6 @@ func (c *ConfigManager) WithEnvPrefix(prefix string) *ConfigManager { c.envConfig[lower] = ConfigMap{Key: withoutPrefix, Value: value} } } - // Don't set envPrefix since keys are already stripped of prefix - c.envPrefix = "" return c } @@ -171,7 +167,21 @@ func (c *ConfigManager) SetConfigType(configType string) error { func (c *ConfigManager) SetEnvPrefix(prefix string) { c.mutex.Lock() defer c.mutex.Unlock() - c.envPrefix = prefix + // 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} + } + } } func (c *ConfigManager) ReadInConfig() error { diff --git a/jety_test.go b/jety_test.go index ec32045..56d9f0f 100644 --- a/jety_test.go +++ b/jety_test.go @@ -1,7 +1,9 @@ package jety import ( + "fmt" "os" + "os/exec" "path/filepath" "sync" "testing" @@ -804,11 +806,6 @@ func TestWriteConfigUnsupportedType(t *testing.T) { func TestSetEnvPrefix(t *testing.T) { cm := NewConfigManager() cm.SetEnvPrefix("PREFIX_") - - // Verify it doesn't panic - if cm.envPrefix != "PREFIX_" { - t.Errorf("envPrefix = %q, want %q", cm.envPrefix, "PREFIX_") - } } func TestDeeplyNestedConfig(t *testing.T) { @@ -1095,9 +1092,6 @@ func TestPackageLevelSetConfigName(t *testing.T) { func TestPackageLevelSetEnvPrefix(t *testing.T) { defaultConfigManager = NewConfigManager() SetEnvPrefix("JETY_TEST_") - if defaultConfigManager.envPrefix != "JETY_TEST_" { - t.Errorf("envPrefix = %q, want %q", defaultConfigManager.envPrefix, "JETY_TEST_") - } } func TestPackageLevelWriteConfig(t *testing.T) { @@ -1266,3 +1260,90 @@ func TestDeeplyNestedWriteConfig(t *testing.T) { }) } } + +func TestSetEnvPrefixOverridesDefault(t *testing.T) { + // Subprocess test: env vars must exist before NewConfigManager is called. + if os.Getenv("TEST_SET_ENV_PREFIX") == "1" { + cm := NewConfigManager() + cm.SetEnvPrefix("MYAPP_") + cm.SetDefault("port", 8080) + + if got := cm.GetInt("port"); got != 9999 { + fmt.Fprintf(os.Stderr, "GetInt(port) = %d, want 9999\n", got) + os.Exit(1) + } + if got := cm.GetString("host"); got != "envhost" { + fmt.Fprintf(os.Stderr, "GetString(host) = %q, want %q\n", got, "envhost") + os.Exit(1) + } + // Unprefixed var should not be visible. + if got := cm.GetString("other"); got != "" { + fmt.Fprintf(os.Stderr, "GetString(other) = %q, want empty\n", got) + os.Exit(1) + } + os.Exit(0) + } + + cmd := exec.Command(os.Args[0], "-test.run=^TestSetEnvPrefixOverridesDefault$") + cmd.Env = append(os.Environ(), + "TEST_SET_ENV_PREFIX=1", + "MYAPP_PORT=9999", + "MYAPP_HOST=envhost", + "OTHER=should_not_see", + ) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("subprocess failed: %v\n%s", err, out) + } +} + +func TestSetEnvPrefixWithSetDefault(t *testing.T) { + // SetDefault should pick up prefixed env vars after SetEnvPrefix. + if os.Getenv("TEST_SET_ENV_PREFIX_DEFAULT") == "1" { + cm := NewConfigManager() + cm.SetEnvPrefix("APP_") + cm.SetDefault("database_host", "localhost") + + if got := cm.GetString("database_host"); got != "db.example.com" { + fmt.Fprintf(os.Stderr, "GetString(database_host) = %q, want %q\n", got, "db.example.com") + os.Exit(1) + } + os.Exit(0) + } + + cmd := exec.Command(os.Args[0], "-test.run=^TestSetEnvPrefixWithSetDefault$") + cmd.Env = append(os.Environ(), + "TEST_SET_ENV_PREFIX_DEFAULT=1", + "APP_DATABASE_HOST=db.example.com", + ) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("subprocess failed: %v\n%s", err, out) + } +} + +func TestPackageLevelSetEnvPrefixOverrides(t *testing.T) { + // Package-level SetEnvPrefix should work the same way. + if os.Getenv("TEST_PKG_SET_ENV_PREFIX") == "1" { + // Reset the default manager to pick up our env vars. + defaultConfigManager = NewConfigManager() + SetEnvPrefix("PKG_") + SetDefault("val", "default") + + if got := GetString("val"); got != "from_env" { + fmt.Fprintf(os.Stderr, "GetString(val) = %q, want %q\n", got, "from_env") + os.Exit(1) + } + os.Exit(0) + } + + cmd := exec.Command(os.Args[0], "-test.run=^TestPackageLevelSetEnvPrefixOverrides$") + cmd.Env = append(os.Environ(), + "TEST_PKG_SET_ENV_PREFIX=1", + "PKG_VAL=from_env", + ) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("subprocess failed: %v\n%s", err, out) + } +}