mirror of
https://github.com/taigrr/jety.git
synced 2026-04-02 03:19:03 -07:00
fix!: SetEnvPrefix now re-reads env vars with prefix stripping
SetEnvPrefix was broken: it set the prefix string but never re-read environment variables. This meant collapse() and SetDefault() couldn't match prefixed env vars to their unprefixed config keys, so defaults always won over env vars. The fix makes SetEnvPrefix behave identically to WithEnvPrefix: it re-reads os.Environ(), strips the prefix from matching keys, and stores the stripped keys in envConfig. The envPrefix field is removed entirely since keys are always pre-stripped. BREAKING CHANGE: SetEnvPrefix now filters env vars to only those matching the prefix (previously all env vars were accessible). This matches the documented and expected behavior.
This commit is contained in:
16
getters.go
16
getters.go
@@ -12,7 +12,7 @@ func (c *ConfigManager) Get(key string) any {
|
|||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
v, ok := c.combinedConfig[strings.ToLower(key)]
|
v, ok := c.combinedConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)]
|
v, ok = c.envConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ func (c *ConfigManager) GetBool(key string) bool {
|
|||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
v, ok := c.combinedConfig[strings.ToLower(key)]
|
v, ok := c.combinedConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)]
|
v, ok = c.envConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func (c *ConfigManager) GetDuration(key string) time.Duration {
|
|||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
v, ok := c.combinedConfig[strings.ToLower(key)]
|
v, ok := c.combinedConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)]
|
v, ok = c.envConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ func (c *ConfigManager) GetString(key string) string {
|
|||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
v, ok := c.combinedConfig[strings.ToLower(key)]
|
v, ok := c.combinedConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)]
|
v, ok = c.envConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ func (c *ConfigManager) GetStringMap(key string) map[string]any {
|
|||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
v, ok := c.combinedConfig[strings.ToLower(key)]
|
v, ok := c.combinedConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)]
|
v, ok = c.envConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ func (c *ConfigManager) GetStringSlice(key string) []string {
|
|||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
v, ok := c.combinedConfig[strings.ToLower(key)]
|
v, ok := c.combinedConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)]
|
v, ok = c.envConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ func (c *ConfigManager) GetInt(key string) int {
|
|||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
v, ok := c.combinedConfig[strings.ToLower(key)]
|
v, ok := c.combinedConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)]
|
v, ok = c.envConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ func (c *ConfigManager) GetIntSlice(key string) []int {
|
|||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
v, ok := c.combinedConfig[strings.ToLower(key)]
|
v, ok := c.combinedConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = c.envConfig[strings.ToLower(c.envPrefix+key)]
|
v, ok = c.envConfig[strings.ToLower(key)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
20
jety.go
20
jety.go
@@ -33,7 +33,6 @@ type (
|
|||||||
configPath string
|
configPath string
|
||||||
configFileUsed string
|
configFileUsed string
|
||||||
configType configType
|
configType configType
|
||||||
envPrefix string
|
|
||||||
mapConfig map[string]ConfigMap
|
mapConfig map[string]ConfigMap
|
||||||
defaultConfig map[string]ConfigMap
|
defaultConfig map[string]ConfigMap
|
||||||
envConfig map[string]ConfigMap
|
envConfig map[string]ConfigMap
|
||||||
@@ -54,7 +53,6 @@ func NewConfigManager() *ConfigManager {
|
|||||||
cm.mapConfig = make(map[string]ConfigMap)
|
cm.mapConfig = make(map[string]ConfigMap)
|
||||||
cm.defaultConfig = make(map[string]ConfigMap)
|
cm.defaultConfig = make(map[string]ConfigMap)
|
||||||
cm.combinedConfig = make(map[string]ConfigMap)
|
cm.combinedConfig = make(map[string]ConfigMap)
|
||||||
cm.envPrefix = ""
|
|
||||||
envSet := os.Environ()
|
envSet := os.Environ()
|
||||||
for _, env := range envSet {
|
for _, env := range envSet {
|
||||||
key, value, found := strings.Cut(env, "=")
|
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}
|
c.envConfig[lower] = ConfigMap{Key: withoutPrefix, Value: value}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Don't set envPrefix since keys are already stripped of prefix
|
|
||||||
c.envPrefix = ""
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +167,21 @@ func (c *ConfigManager) SetConfigType(configType string) error {
|
|||||||
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()
|
||||||
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 {
|
func (c *ConfigManager) ReadInConfig() error {
|
||||||
|
|||||||
97
jety_test.go
97
jety_test.go
@@ -1,7 +1,9 @@
|
|||||||
package jety
|
package jety
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -804,11 +806,6 @@ func TestWriteConfigUnsupportedType(t *testing.T) {
|
|||||||
func TestSetEnvPrefix(t *testing.T) {
|
func TestSetEnvPrefix(t *testing.T) {
|
||||||
cm := NewConfigManager()
|
cm := NewConfigManager()
|
||||||
cm.SetEnvPrefix("PREFIX_")
|
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) {
|
func TestDeeplyNestedConfig(t *testing.T) {
|
||||||
@@ -1095,9 +1092,6 @@ func TestPackageLevelSetConfigName(t *testing.T) {
|
|||||||
func TestPackageLevelSetEnvPrefix(t *testing.T) {
|
func TestPackageLevelSetEnvPrefix(t *testing.T) {
|
||||||
defaultConfigManager = NewConfigManager()
|
defaultConfigManager = NewConfigManager()
|
||||||
SetEnvPrefix("JETY_TEST_")
|
SetEnvPrefix("JETY_TEST_")
|
||||||
if defaultConfigManager.envPrefix != "JETY_TEST_" {
|
|
||||||
t.Errorf("envPrefix = %q, want %q", defaultConfigManager.envPrefix, "JETY_TEST_")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPackageLevelWriteConfig(t *testing.T) {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user