diff --git a/config/config.go b/config/config.go index 72bc17b2..fdec58b3 100644 --- a/config/config.go +++ b/config/config.go @@ -35,7 +35,6 @@ package config import ( "fmt" - "regexp" "strconv" "strings" "sync" @@ -46,12 +45,6 @@ import ( const DefaultEnvPrefix = "RSERVER" -// regular expression matching lowercase letter followed by an uppercase letter -var camelCaseMatch = regexp.MustCompile("([a-z0-9])([A-Z])") - -// regular expression matching uppercase letters contained in environment variable names -var upperCaseMatch = regexp.MustCompile("^[A-Z0-9_]+$") - // Default is the singleton config instance var Default *Config @@ -341,7 +334,7 @@ func getOrCreatePointer[T configTypes]( // the replacer cannot detect camelCase keys. func (c *Config) bindEnv(key string) { envVar := key - if !upperCaseMatch.MatchString(key) { + if !isUpperCaseConfigKey(key) { envVar = ConfigKeyToEnv(c.envPrefix, key) } // bind once diff --git a/config/config_test.go b/config/config_test.go index 94db77d5..5cdf1fbd 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -494,6 +494,9 @@ func TestConfigKeyToEnv(t *testing.T) { require.Equal(t, expected, ConfigKeyToEnv(DefaultEnvPrefix, "KeyVar1Var2")) require.Equal(t, expected, ConfigKeyToEnv(DefaultEnvPrefix, "RSERVER_KEY_VAR1_VAR2")) require.Equal(t, "KEY_VAR1_VAR2", ConfigKeyToEnv(DefaultEnvPrefix, "KEY_VAR1_VAR2")) + require.Equal(t, expected, ConfigKeyToEnv(DefaultEnvPrefix, "Key_Var1.Var2")) + require.Equal(t, expected, ConfigKeyToEnv(DefaultEnvPrefix, "key_Var1_Var2")) + require.Equal(t, "RSERVER_KEY_VAR_1_VAR2", ConfigKeyToEnv(DefaultEnvPrefix, "Key_Var.1.Var2")) } func TestGetEnvThroughViper(t *testing.T) { @@ -724,3 +727,13 @@ func TestConfigLoad(t *testing.T) { require.Error(t, err) }) } + +// Benchmark for the original ConfigKeyToEnv function +func BenchmarkConfigKeyToEnv(b *testing.B) { + envPrefix := "MYAPP" + configKey := "myConfig.KeyName" + for i := 0; i < b.N; i++ { + _ = ConfigKeyToEnv(envPrefix, configKey) + } + b.ReportAllocs() +} diff --git a/config/env.go b/config/env.go index cd67c68d..e726e335 100644 --- a/config/env.go +++ b/config/env.go @@ -4,15 +4,46 @@ package config import ( "os" "strings" + "unicode" ) +func isUpperCaseConfigKey(s string) bool { + for _, ch := range s { + if !(ch == '_' || unicode.IsUpper(ch) || unicode.IsDigit(ch)) { + return false + } + } + return true +} + // ConfigKeyToEnv gets the env variable name from a given config key func ConfigKeyToEnv(envPrefix, s string) string { - if upperCaseMatch.MatchString(s) { + // Check if the string is already in upper case format + if isUpperCaseConfigKey(s) { return s } - snake := camelCaseMatch.ReplaceAllString(s, "${1}_${2}") - return envPrefix + "_" + strings.ToUpper(strings.ReplaceAll(snake, ".", "_")) + + // convert camelCase to snake_case + var builder strings.Builder + + // Add the prefix + builder.WriteString(envPrefix) + builder.WriteByte('_') + + // Transform the input string to the desired format + for i, r := range s { + if r >= 'A' && r <= 'Z' && i > 0 && (s[i-1] >= 'a' && s[i-1] <= 'z' || s[i-1] >= '0' && s[i-1] <= '9') { + builder.WriteByte('_') + } else if r == '.' { + r = '_' + } + if unicode.IsLetter(r) { + r = unicode.ToUpper(r) + } + builder.WriteRune(r) + } + + return builder.String() } // getEnv returns the environment value stored in key variable