Repository: spf13/viper
Pull request #1056 (2020-12-24 06:11:57)

Sub parent inheritance
This PR fixes two issues with Sub():
-    Options were not copied from parent (e.g, automaticEnvApplied).
 -   Environment variable names were wrong: Sub("x").Sub("y").Get("z") should look up env var x.y.z but was instead looking up z

Fixes https://github.com/spf13/viper/issues/745
Fixes https://github.com/spf13/viper/issues/507


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/spf13/viper?pullRequest=1056) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/spf13/viper?pullRequest=1056) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/spf13/viper?pullRequest=1056) it.</sub>
github-actions[bot]: 👋 Thanks for contributing to Viper! You are awesome! 🎉

A maintainer will take a look at your pull request shortly. 👀

In the meantime: We are working on **Viper v2** and we would love to hear your thoughts about what you like or don't like about Viper, so we can improve or fix those issues.

⏰ If you have a couple minutes, please take some time and share your thoughts: https://forms.gle/R6faU74qPRPAzchZ9

📣 If you've already given us your feedback, you can still help by spreading the news,
either by sharing the above link or telling people about this on Twitter:

https://twitter.com/sagikazarmark/status/1306904078967074816

**Thank you!** ❤️

afirth: Probably fixes:
#745
#507
#1012 (partial)
#801 (partial)
lightglitch: @sagikazarmark any news on the possibility of merging this? Just found this inconsistency and this would be very helpful to solve it.
sagikazarmark: The PR needs rebasing, otherwise it looks good to me.
TaylorOno: This feels like it could be a breaking change, but using the `Sub` function to look up a top level value is something I hope nobody is doing
sagikazarmark: @TaylorOno can you explain? (Or better yet, write a test?)
TaylorOno: Im just saying it is possible some people maybe relying on the old behavior e.g. https://xkcd.com/1172/
sagikazarmark: Fixes https://github.com/spf13/viper/issues/745
Fixes https://github.com/spf13/viper/issues/507

Patch:
diff --git a/viper.go b/viper.go
index 45a2d5c..fa6f3e3 100644
--- a/viper.go
+++ b/viper.go
@@ -206,6 +206,7 @@ type Viper struct {
 	envKeyReplacer      StringReplacer
 	allowEmptyEnv       bool
 
+	parents        []string
 	config         map[string]interface{}
 	override       map[string]interface{}
 	defaults       map[string]interface{}
@@ -232,6 +233,7 @@ func New() *Viper {
 	v.configPermissions = os.FileMode(0o644)
 	v.fs = afero.NewOsFs()
 	v.config = make(map[string]interface{})
+	v.parents = []string{}
 	v.override = make(map[string]interface{})
 	v.defaults = make(map[string]interface{})
 	v.kvstore = make(map[string]interface{})
@@ -948,6 +950,10 @@ func (v *Viper) Sub(key string) *Viper {
 	}
 
 	if reflect.TypeOf(data).Kind() == reflect.Map {
+		subv.parents = append(v.parents, strings.ToLower(key))
+		subv.automaticEnvApplied = v.automaticEnvApplied
+		subv.envPrefix = v.envPrefix
+		subv.envKeyReplacer = v.envKeyReplacer
 		subv.config = cast.ToStringMap(data)
 		return subv
 	}
@@ -1294,9 +1300,10 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
 
 	// Env override next
 	if v.automaticEnvApplied {
+		envKey := strings.Join(append(v.parents, lcaseKey), ".")
 		// even if it hasn't been registered, if automaticEnv is used,
 		// check any Get request
-		if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok {
+		if val, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok {
 			return val
 		}
 		if nested && v.isPathShadowedInAutoEnv(path) != "" {


Test patch:
diff --git a/viper_test.go b/viper_test.go
index 9363da8..b867337 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -733,6 +733,24 @@ func TestEnvKeyReplacer(t *testing.T) {
 	assert.Equal(t, "30s", v.Get("refresh-interval"))
 }
 
+func TestEnvSubConfig(t *testing.T) {
+	initYAML()
+
+	v.AutomaticEnv()
+
+	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+
+	testutil.Setenv(t, "CLOTHING_PANTS_SIZE", "small")
+	subv := v.Sub("clothing").Sub("pants")
+	assert.Equal(t, "small", subv.Get("size"))
+
+	// again with EnvPrefix
+	v.SetEnvPrefix("foo") // will be uppercased automatically
+	subWithPrefix := v.Sub("clothing").Sub("pants")
+	testutil.Setenv(t, "FOO_CLOTHING_PANTS_SIZE", "large")
+	assert.Equal(t, "large", subWithPrefix.Get("size"))
+}
+
 func TestAllKeys(t *testing.T) {
 	initConfigs()
 
@@ -1522,6 +1540,14 @@ func TestSub(t *testing.T) {
 
 	subv = v.Sub("missing.key")
 	assert.Equal(t, (*Viper)(nil), subv)
+
+	subv = v.Sub("clothing")
+	assert.Equal(t, subv.parents[0], "clothing")
+
+	subv = v.Sub("clothing").Sub("pants")
+	assert.Equal(t, len(subv.parents), 2)
+	assert.Equal(t, subv.parents[0], "clothing")
+	assert.Equal(t, subv.parents[1], "pants")
 }
 
 var hclWriteExpected = []byte(`"foos" = {