From a3c035d58892a7b76c0c5f1dcb4960daaae3d041 Mon Sep 17 00:00:00 2001 From: Carolyn Van Slyck Date: Wed, 12 May 2021 15:30:39 -0500 Subject: [PATCH] Add ReadConfigFrom to load config from a struct 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 https://github.com/spf13/viper/issues/761. If you need to support this scenario, call ReadConfigFrom on the destination struct before calling Unmarshal. Signed-off-by: Carolyn Van Slyck --- viper.go | 27 +++++++++++++++++++++++++++ viper_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/viper.go b/viper.go index 50b4780298..1fc8af0aad 100644 --- a/viper.go +++ b/viper.go @@ -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 { diff --git a/viper_test.go b/viper_test.go index 45bf8e9ba4..931aff3651 100644 --- a/viper_test.go +++ b/viper_test.go @@ -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\"}")