From 85798776bfab15659066a6746bb2db9b020abe85 Mon Sep 17 00:00:00 2001 From: Leonidas Vrachnis Date: Mon, 8 Apr 2024 19:44:04 +0300 Subject: [PATCH] chore: provide methods to access config init errors (#406) Co-authored-by: Francesco Casula --- config/config.go | 3 +++ config/config_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++ config/load.go | 38 +++++++++++++++---------------- 3 files changed, 75 insertions(+), 19 deletions(-) diff --git a/config/config.go b/config/config.go index 401b70b4..72bc17b2 100644 --- a/config/config.go +++ b/config/config.go @@ -100,6 +100,9 @@ type Config struct { reloadableVars map[string]any reloadableVarsMisuses map[string]string reloadableVarsLock sync.RWMutex // used to protect both the reloadableVars and reloadableVarsMisuses maps + configPath string + configPathErr error + godotEnvErr error } // GetBool gets bool value from config diff --git a/config/config_test.go b/config/config_test.go index 9dbb62eb..94db77d5 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -3,6 +3,7 @@ package config import ( "context" "fmt" + "os" "sync" "testing" "time" @@ -516,6 +517,15 @@ func TestGetEnvThroughViper(t *testing.T) { require.Equal(t, expectedValue, tc.GetString("Key.Var1VarVar", "")) }) + t.Run("with env prefix", func(t *testing.T) { + envPrefix := "ANOTHER_PREFIX" + + tc := New(WithEnvPrefix(envPrefix)) + t.Setenv(envPrefix+"_KEY_VAR1_VAR_VAR", expectedValue) + + require.Equal(t, expectedValue, tc.GetString("Key.Var1VarVar", "")) + }) + t.Run("detects uppercase env variables", func(t *testing.T) { t.Setenv("SOMEENVVARIABLE", expectedValue) tc := New() @@ -671,3 +681,46 @@ func TestConfigLocking(t *testing.T) { require.NoError(t, g.Wait()) } + +func TestConfigLoad(t *testing.T) { + // create a temporary file: + f, err := os.CreateTemp("", "*config.yaml") + require.NoError(t, err) + defer os.Remove(f.Name()) + + t.Setenv("CONFIG_PATH", f.Name()) + c := New() + require.NoError(t, err) + + t.Run("successfully loaded config file", func(t *testing.T) { + configFile, err := c.ConfigFileUsed() + require.NoError(t, err) + require.Equal(t, f.Name(), configFile) + }) + + err = os.Remove(f.Name()) + require.NoError(t, err) + + t.Run("attempt to load non-existent config file", func(t *testing.T) { + c := New() + configFile, err := c.ConfigFileUsed() + require.Error(t, err) + require.Equal(t, f.Name(), configFile) + }) + + t.Run("dot env error", func(t *testing.T) { + c := New() + err := c.DotEnvLoaded() + require.Error(t, err) + }) + + t.Run("dot env found", func(t *testing.T) { + c := New() + f, err := os.Create(".env") + require.NoError(t, err) + defer os.Remove(f.Name()) + + err = c.DotEnvLoaded() + require.Error(t, err) + }) +} diff --git a/config/load.go b/config/load.go index 439a9916..e6698847 100644 --- a/config/load.go +++ b/config/load.go @@ -2,9 +2,7 @@ package config import ( "fmt" - "os" "reflect" - "strings" "time" "github.com/fsnotify/fsnotify" @@ -17,9 +15,7 @@ func (c *Config) load() { c.hotReloadableConfig = make(map[string][]*configValue) c.envs = make(map[string]string) - if err := godotenv.Load(); err != nil && !isTest() { - fmt.Println("INFO: No .env file found.") - } + c.godotEnvErr = godotenv.Load() configPath := getEnv("CONFIG_PATH", "./config/config.yaml") @@ -28,11 +24,12 @@ func (c *Config) load() { bindLegacyEnv(v) v.SetConfigFile(configPath) - err := v.ReadInConfig() // Find and read the config file - // Don't panic if config.yaml is not found or error with parsing. Use the default config values instead - if err != nil && !isTest() { - fmt.Printf("[Config] :: Failed to parse config file from path %q, using default values: %v\n", configPath, err) - } + + // Find and read the config file + // If config.yaml is not found or error with parsing. Use the default config values instead + c.configPathErr = v.ReadInConfig() + c.configPath = v.ConfigFileUsed() + v.OnConfigChange(func(e fsnotify.Event) { c.onConfigChange() }) @@ -41,6 +38,18 @@ func (c *Config) load() { c.v = v } +// ConfigFileUsed returns the file used to load the config. +// If we failed to load the config file, it also returns an error. +func (c *Config) ConfigFileUsed() (string, error) { + return c.configPath, c.configPathErr +} + +// DotEnvLoaded returns an error if there was an error loading the .env file. +// It returns nil otherwise. +func (c *Config) DotEnvLoaded() error { + return c.godotEnvErr +} + func (c *Config) onConfigChange() { defer func() { if r := recover(); r != nil { @@ -230,12 +239,3 @@ func mapDeepEqual[K comparable, V any](a, b map[K]V) bool { } return true } - -func isTest() bool { - for _, arg := range os.Args { - if strings.HasPrefix(arg, "-test.") { - return true - } - } - return false -}