spf13 / viper

Go configuration with fangs
MIT License
26.93k stars 2.02k forks source link

AutomaticEnv does not override Unmarshaled config slice values #1732

Open lwlee2608 opened 9 months ago

lwlee2608 commented 9 months ago

Preflight Checklist

Viper Version

1.18.2

Go Version

1.21.1

Config Source

Files

Format

YAML

Repl.it link

No response

Code reproducing the issue

package main

import (
    "strings"
    "testing"

    "github.com/spf13/viper"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

var yamlSimpleSlice = []byte(`
name: Steve
port: 8080
modes:
  - 1
  - 2
  - 3
clients:
  - name: foo
  - name: bar
`)

func TestSliceIndexAutomaticEnv(t *testing.T) {
    v := viper.New()
    v.SetConfigType("yaml")
    r := strings.NewReader(string(yamlSimpleSlice))

    // Read yaml as default value
    err := v.ReadConfig(r)
    require.NoError(t, err)

    assert.Equal(t, "Steve", v.GetString("name"))
    assert.Equal(t, 8080, v.GetInt("port"))
    assert.Equal(t, "foo", v.GetString("clients.0.name"))
    assert.Equal(t, "bar", v.GetString("clients.1.name"))
    assert.Equal(t, []int{1, 2, 3}, v.GetIntSlice("modes"))

    // Override with env variable
    t.Setenv("NAME", "Steven")
    t.Setenv("MODES_2", "300")
    t.Setenv("CLIENTS_1_NAME", "baz")

    v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
    v.AutomaticEnv()

    // Assert value are overriden
    assert.Equal(t, "Steven", v.GetString("name"))
    assert.Equal(t, 300, v.GetInt("modes.2"))
    assert.Equal(t, "baz", v.GetString("clients.1.name"))

    type ClientConfig struct {
        Name string
    }

    type Configuration struct {
        Port    int
        Name    string
        Modes   []int
        Clients []ClientConfig
    }
    var config Configuration

    // Unmarshal into config struct
    v.Unmarshal(&config)

    assert.Equal(t, "Steven", config.Name) // successfully overridden to 'Steven'
    assert.Equal(t, 8080, config.Port)
    assert.Equal(t, []int{1, 2, 300}, config.Modes) // actual is still default value {1,2,3}
    assert.Equal(t, "foo", config.Clients[0].Name)
    assert.Equal(t, "baz", config.Clients[1].Name) // actual is still default value 'bar'
}

Expected Behavior

Initially read a YAML file with modes set to {1, 2, 3} and clients set to { name: foo, name: bar }.

I expect config.Modes to be []int{1, 2, 300} since the MODES_2 environment variable should override 3 to 300. Similarly, I expect config.Clients[1].Name value to be overridden by the CLIENTS_1_NAME environment variable from bar to baz.

Actual Behavior

The environment variable did not override the unmarshaled config struct. config.Modes is still []int{1, 2, 3}, and config.Clients[1].Name is still bar.

Steps To Reproduce

1.Run above test

Additional Information

The bug only occurs with slice values, i.e., config.Modes and config.Clients[1].Name. Configurations without slices, i.e., config.Name, are still overridden by the env variable.

Note that v.GetInt("modes.2") and v.GetString("clients.1.name") work. Only the unmarshaled struct is bugged.

No response

github-actions[bot] commented 9 months ago

👋 Thanks for reporting!

A maintainer will take a look at your issue 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! ❤️

JckHoe commented 7 months ago

Encountering similar issues.

The PR looks good! Would love this feature added.