This commit is contained in:
parent
f237a1dc28
commit
4dc6e338dd
|
@ -28,8 +28,14 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
|
|||
return loadEnvInternal(env, prefix, prv.Addr())
|
||||
}
|
||||
|
||||
rt := prv.Type().Elem()
|
||||
|
||||
if i, ok := prv.Interface().(Unmarshaler); ok {
|
||||
if ev, ok := env[prefix]; ok {
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
i = prv.Interface().(Unmarshaler)
|
||||
}
|
||||
err := i.UnmarshalEnv(prefix, ev)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", prefix, err)
|
||||
|
@ -43,8 +49,6 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
|
|||
return nil
|
||||
}
|
||||
|
||||
rt := prv.Type().Elem()
|
||||
|
||||
switch rt {
|
||||
case reflect.TypeOf(""):
|
||||
if ev, ok := env[prefix]; ok {
|
||||
|
@ -173,12 +177,12 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
|
|||
switch {
|
||||
case rt.Elem() == reflect.TypeOf(""):
|
||||
if ev, ok := env[prefix]; ok {
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
if ev == "" {
|
||||
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
|
||||
} else {
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
prv.Elem().Set(reflect.ValueOf(strings.Split(ev, ",")))
|
||||
}
|
||||
}
|
||||
|
@ -186,9 +190,6 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
|
|||
|
||||
case rt.Elem().Kind() == reflect.Struct:
|
||||
if ev, ok := env[prefix]; ok && ev == "" { // special case: empty list
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
|
||||
} else {
|
||||
for i := 0; ; i++ {
|
||||
|
@ -213,13 +214,20 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
|
|||
return fmt.Errorf("unsupported type: %v", rt)
|
||||
}
|
||||
|
||||
// Load loads the configuration from the environment.
|
||||
func Load(prefix string, v interface{}) error {
|
||||
func loadWithEnv(env map[string]string, prefix string, v interface{}) error {
|
||||
return loadEnvInternal(env, prefix, reflect.ValueOf(v).Elem())
|
||||
}
|
||||
|
||||
func envToMap() map[string]string {
|
||||
env := make(map[string]string)
|
||||
for _, kv := range os.Environ() {
|
||||
tmp := strings.SplitN(kv, "=", 2)
|
||||
env[tmp[0]] = tmp[1]
|
||||
}
|
||||
|
||||
return loadEnvInternal(env, prefix, reflect.ValueOf(v).Elem())
|
||||
return env
|
||||
}
|
||||
|
||||
// Load loads the configuration from the environment.
|
||||
func Load(prefix string, v interface{}) error {
|
||||
return loadWithEnv(envToMap(), prefix, v)
|
||||
}
|
||||
|
|
|
@ -9,13 +9,28 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type subStruct struct {
|
||||
MyParam int `json:"myParam"`
|
||||
func stringPtr(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
type mapEntry struct {
|
||||
MyValue string `json:"myValue"`
|
||||
MyStruct subStruct `json:"myStruct"`
|
||||
func intPtr(v int) *int {
|
||||
return &v
|
||||
}
|
||||
|
||||
func uint64Ptr(v uint64) *uint64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
func float64Ptr(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func durationPtr(v time.Duration) *time.Duration {
|
||||
return &v
|
||||
}
|
||||
|
||||
type myDuration time.Duration
|
||||
|
@ -40,106 +55,150 @@ func (d *myDuration) UnmarshalEnv(_ string, v string) error {
|
|||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
type subStruct struct {
|
||||
MyParam int `json:"myParam"`
|
||||
}
|
||||
|
||||
type mapEntry struct {
|
||||
MyValue string `json:"myValue"`
|
||||
MyStruct subStruct `json:"myStruct"`
|
||||
}
|
||||
|
||||
type mySubStruct struct {
|
||||
URL string `json:"url"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
MyInt2 int `json:"myInt2"`
|
||||
}
|
||||
|
||||
type testStruct struct {
|
||||
MyString string `json:"myString"`
|
||||
MyInt int `json:"myInt"`
|
||||
MyFloat float64 `json:"myFloat"`
|
||||
MyBool bool `json:"myBool"`
|
||||
MyDuration myDuration `json:"myDuration"`
|
||||
MyMap map[string]*mapEntry `json:"myMap"`
|
||||
MySlice []string `json:"mySlice"`
|
||||
MySliceEmpty []string `json:"mySliceEmpty"`
|
||||
MySliceSubStruct []mySubStruct `json:"mySliceSubStruct"`
|
||||
MySliceSubStructEmpty []mySubStruct `json:"mySliceSubStructEmpty"`
|
||||
MyString string `json:"myString"`
|
||||
MyStringOpt *string `json:"myStringOpt"`
|
||||
MyInt int `json:"myInt"`
|
||||
MyIntOpt *int `json:"myIntOpt"`
|
||||
MyUint uint64 `json:"myUint"`
|
||||
MyUintOpt *uint64 `json:"myUintOpt"`
|
||||
MyFloat float64 `json:"myFloat"`
|
||||
MyFloatOpt *float64 `json:"myFloatOpt"`
|
||||
MyBool bool `json:"myBool"`
|
||||
MyBoolOpt *bool `json:"myBoolOpt"`
|
||||
MyDuration myDuration `json:"myDuration"`
|
||||
MyDurationOpt *myDuration `json:"myDurationOpt"`
|
||||
MyDurationOptUnset *myDuration `json:"myDurationOptUnset"`
|
||||
MyMap map[string]*mapEntry `json:"myMap"`
|
||||
MySliceString []string `json:"mySliceString"`
|
||||
MySliceStringEmpty []string `json:"mySliceStringEmpty"`
|
||||
MySliceStringOpt *[]string `json:"mySliceStringOpt"`
|
||||
MySliceStringOptUnset *[]string `json:"mySliceStringOptUnset"`
|
||||
MySliceSubStruct []mySubStruct `json:"mySliceSubStruct"`
|
||||
MySliceSubStructEmpty []mySubStruct `json:"mySliceSubStructEmpty"`
|
||||
MySliceSubStructOpt *[]mySubStruct `json:"mySliceSubStructOpt"`
|
||||
MySliceSubStructOptUnset *[]mySubStruct `json:"mySliceSubStructOptUnset"`
|
||||
Unset *bool `json:"unset"`
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
os.Setenv("MYPREFIX_MYSTRING", "testcontent")
|
||||
defer os.Unsetenv("MYPREFIX_MYSTRING")
|
||||
env := map[string]string{
|
||||
"MYPREFIX_MYSTRING": "testcontent",
|
||||
"MYPREFIX_MYSTRINGOPT": "testcontent2",
|
||||
"MYPREFIX_MYINT": "123",
|
||||
"MYPREFIX_MYINTOPT": "456",
|
||||
"MYPREFIX_MYUINT": "8910",
|
||||
"MYPREFIX_MYUINTOPT": "112313",
|
||||
"MYPREFIX_MYFLOAT": "15.2",
|
||||
"MYPREFIX_MYFLOATOPT": "16.2",
|
||||
"MYPREFIX_MYBOOL": "yes",
|
||||
"MYPREFIX_MYBOOLOPT": "false",
|
||||
"MYPREFIX_MYDURATION": "22s",
|
||||
"MYPREFIX_MYDURATIONOPT": "30s",
|
||||
"MYPREFIX_MYMAP_MYKEY": "",
|
||||
"MYPREFIX_MYMAP_MYKEY2_MYVALUE": "asd",
|
||||
"MYPREFIX_MYMAP_MYKEY2_MYSTRUCT_MYPARAM": "456",
|
||||
"MYPREFIX_MYSLICESTRING": "val1,val2",
|
||||
"MYPREFIX_MYSLICESTRINGEMPTY": "",
|
||||
"MYPREFIX_MYSLICESTRINGOPT": "aa",
|
||||
"MYPREFIX_MYSLICESUBSTRUCT_0_URL": "url1",
|
||||
"MYPREFIX_MYSLICESUBSTRUCT_0_USERNAME": "user1",
|
||||
"MYPREFIX_MYSLICESUBSTRUCT_0_PASSWORD": "pass1",
|
||||
"MYPREFIX_MYSLICESUBSTRUCT_1_URL": "url2",
|
||||
"MYPREFIX_MYSLICESUBSTRUCT_1_PASSWORD": "pass2",
|
||||
"MYPREFIX_MYSLICESUBSTRUCTEMPTY": "",
|
||||
"MYPREFIX_MYSLICESUBSTRUCTOPT_1_PASSWORD": "pwd",
|
||||
}
|
||||
|
||||
os.Setenv("MYPREFIX_MYINT", "123")
|
||||
defer os.Unsetenv("MYPREFIX_MYINT")
|
||||
|
||||
os.Setenv("MYPREFIX_MYFLOAT", "15.2")
|
||||
defer os.Unsetenv("MYPREFIX_MYFLOAT")
|
||||
|
||||
os.Setenv("MYPREFIX_MYBOOL", "yes")
|
||||
defer os.Unsetenv("MYPREFIX_MYBOOL")
|
||||
|
||||
os.Setenv("MYPREFIX_MYDURATION", "22s")
|
||||
defer os.Unsetenv("MYPREFIX_MYDURATION")
|
||||
|
||||
os.Setenv("MYPREFIX_MYMAP_MYKEY", "")
|
||||
defer os.Unsetenv("MYPREFIX_MYMAP_MYKEY")
|
||||
|
||||
os.Setenv("MYPREFIX_MYMAP_MYKEY2_MYVALUE", "asd")
|
||||
defer os.Unsetenv("MYPREFIX_MYMAP_MYKEY2_MYVALUE")
|
||||
|
||||
os.Setenv("MYPREFIX_MYMAP_MYKEY2_MYSTRUCT_MYPARAM", "456")
|
||||
defer os.Unsetenv("MYPREFIX_MYMAP_MYKEY2_MYSTRUCT_MYPARAM")
|
||||
|
||||
os.Setenv("MYPREFIX_MYSLICE", "val1,val2")
|
||||
defer os.Unsetenv("MYPREFIX_MYSLICE")
|
||||
|
||||
os.Setenv("MYPREFIX_MYSLICEEMPTY", "")
|
||||
defer os.Unsetenv("MYPREFIX_MYSLICEEMPTY")
|
||||
|
||||
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_0_URL", "url1")
|
||||
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_0_URL")
|
||||
|
||||
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_0_USERNAME", "user1")
|
||||
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_0_USERNAME")
|
||||
|
||||
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_0_PASSWORD", "pass1")
|
||||
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_0_PASSWORD")
|
||||
|
||||
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_1_URL", "url2")
|
||||
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_1_URL")
|
||||
|
||||
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_1_PASSWORD", "pass2")
|
||||
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_1_PASSWORD")
|
||||
|
||||
os.Setenv("MYPREFIX_MYSLICESUBSTRUCTEMPTY", "")
|
||||
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCTEMPTY")
|
||||
for key, val := range env {
|
||||
os.Setenv(key, val)
|
||||
defer os.Unsetenv(key)
|
||||
}
|
||||
|
||||
var s testStruct
|
||||
err := Load("MYPREFIX", &s)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "testcontent", s.MyString)
|
||||
require.Equal(t, 123, s.MyInt)
|
||||
require.Equal(t, 15.2, s.MyFloat)
|
||||
require.Equal(t, true, s.MyBool)
|
||||
require.Equal(t, 22*myDuration(time.Second), s.MyDuration)
|
||||
|
||||
_, ok := s.MyMap["mykey"]
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
v, ok := s.MyMap["mykey2"]
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, "asd", v.MyValue)
|
||||
require.Equal(t, 456, v.MyStruct.MyParam)
|
||||
|
||||
require.Equal(t, []string{"val1", "val2"}, s.MySlice)
|
||||
require.Equal(t, []string{}, s.MySliceEmpty)
|
||||
|
||||
require.Equal(t, []mySubStruct{
|
||||
{
|
||||
URL: "url1",
|
||||
Username: "user1",
|
||||
Password: "pass1",
|
||||
require.Equal(t, testStruct{
|
||||
MyString: "testcontent",
|
||||
MyStringOpt: stringPtr("testcontent2"),
|
||||
MyInt: 123,
|
||||
MyIntOpt: intPtr(456),
|
||||
MyUint: 8910,
|
||||
MyUintOpt: uint64Ptr(112313),
|
||||
MyFloat: 15.2,
|
||||
MyFloatOpt: float64Ptr(16.2),
|
||||
MyBool: true,
|
||||
MyBoolOpt: boolPtr(false),
|
||||
MyDuration: 22000000000,
|
||||
MyDurationOpt: (*myDuration)(durationPtr(30000000000)),
|
||||
MyMap: map[string]*mapEntry{
|
||||
"mykey": {
|
||||
MyValue: "",
|
||||
MyStruct: subStruct{
|
||||
MyParam: 0,
|
||||
},
|
||||
},
|
||||
"mykey2": {
|
||||
MyValue: "asd",
|
||||
MyStruct: subStruct{
|
||||
MyParam: 456,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
URL: "url2",
|
||||
Password: "pass2",
|
||||
MySliceString: []string{
|
||||
"val1",
|
||||
"val2",
|
||||
},
|
||||
}, s.MySliceSubStruct)
|
||||
|
||||
require.Equal(t, []mySubStruct{}, s.MySliceSubStructEmpty)
|
||||
MySliceStringEmpty: []string{},
|
||||
MySliceStringOpt: &[]string{"aa"},
|
||||
MySliceSubStruct: []mySubStruct{
|
||||
{
|
||||
URL: "url1",
|
||||
Username: "user1",
|
||||
Password: "pass1",
|
||||
},
|
||||
{
|
||||
URL: "url2",
|
||||
Username: "",
|
||||
Password: "pass2",
|
||||
},
|
||||
},
|
||||
MySliceSubStructEmpty: []mySubStruct{},
|
||||
}, s)
|
||||
}
|
||||
|
||||
func FuzzLoad(f *testing.F) {
|
||||
f.Add("MYPREFIX_MYINT", "a")
|
||||
f.Add("MYPREFIX_MYUINT", "a")
|
||||
f.Add("MYPREFIX_MYFLOAT", "a")
|
||||
f.Add("MYPREFIX_MYBOOL", "a")
|
||||
f.Add("MYPREFIX_MYSLICESUBSTRUCT_0_MYINT2", "a")
|
||||
f.Add("MYPREFIX_MYDURATION", "a")
|
||||
f.Add("MYPREFIX_MYDURATION_A", "a")
|
||||
|
||||
f.Fuzz(func(t *testing.T, key string, val string) {
|
||||
env := map[string]string{
|
||||
key: val,
|
||||
}
|
||||
|
||||
var s testStruct
|
||||
loadWithEnv(env, "MYPREFIX", &s) //nolint:errcheck
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue