Skip to content

Commit

Permalink
Add ReadConfigFrom to load config from a struct
Browse files Browse the repository at this point in the history
In cases where there isn't a configuration file, none of the fields on
the struct are bound to environment variables unless BindEnv was
explicitly called. AutomaticEnv essentially doesn't work with the config
file per spf13#761.

If you need to support this scenario, call ReadConfigFrom on the
destination struct before calling Unmarshal.

Signed-off-by: Carolyn Van Slyck <me@carolynvanslyck.com>
  • Loading branch information
carolynvs committed May 12, 2021
1 parent 36be6bf commit 8fcac77
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 0 deletions.
27 changes: 27 additions & 0 deletions viper.go
Expand Up @@ -995,6 +995,33 @@ func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error
return decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
}

// ReadConfigFrom applies the specified data to Viper's configuration.
// Useful for setting defaults from the specified structs in cases
// where a configuration file may not be present.
func ReadConfigFrom(val interface{}) error {
return v.ReadConfigFrom(val)
}

// ReadConfigFrom applies the specified data to Viper's configuration.
// Useful for setting defaults from the specified structs.
func (v *Viper) ReadConfigFrom(val interface{}) error {
var tmp map[string]interface{}
err := mapstructure.Decode(val, &tmp)
if err != nil {
return fmt.Errorf("error decoding configuration from struct: %v", err)
}

cfgType := v.configType
defer func() { v.configType = cfgType }()
v.SetConfigType("json")
tmpJson, err := json.Marshal(tmp)
if err != nil {
return fmt.Errorf("error marshaling configuration from struct: %v", err)
}

return v.ReadConfig(bytes.NewReader(tmpJson))
}

// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
// of time.Duration values & string slices
func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
Expand Down
31 changes: 31 additions & 0 deletions viper_test.go
Expand Up @@ -798,6 +798,37 @@ func TestUnmarshal(t *testing.T) {
)
}

// Check that we can unmarshal environment variables without
// explicit bindings or a configuration file.
func TestUnmarshalWithoutConfigFile(t *testing.T) {
v := New()
v.AutomaticEnv()

type Person struct {
Name string
}
type config struct {
Port int
Person Person
}

testutil.Setenv(t, "PORT", "1313")

v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
testutil.Setenv(t, "PERSON_NAME", "Sally")

C := config{}

v.ReadConfigFrom(C)

err := v.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}

assert.Equal(t, config{Port: 1313, Person: Person{Name: "Sally"}}, C)
}

func TestUnmarshalWithDecoderOptions(t *testing.T) {
Set("credentials", "{\"foo\":\"bar\"}")

Expand Down

0 comments on commit 8fcac77

Please sign in to comment.