diff options
author | Pacien TRAN-GIRARD | 2015-02-14 11:24:17 +0100 |
---|---|---|
committer | Pacien TRAN-GIRARD | 2015-02-14 11:27:36 +0100 |
commit | 8e99baedf2c3e433750c2f51681f3c9d15d412be (patch) | |
tree | 3fa0ff3ea4823a408dadceb14fcd51ddc16c2aa8 | |
parent | 8b1f88ce72ef8496879395e7f04b676e6a16e07a (diff) | |
download | go-envcfg-8e99baedf2c3e433750c2f51681f3c9d15d412be.tar.gz |
First version
-rw-r--r-- | envcfg.go | 40 | ||||
-rw-r--r-- | mapper.go | 86 | ||||
-rw-r--r-- | nesting_test.go | 52 | ||||
-rw-r--r-- | overwriting_test.go | 67 | ||||
-rw-r--r-- | tagging_test.go | 70 | ||||
-rw-r--r-- | typeconv_test.go | 71 |
6 files changed, 386 insertions, 0 deletions
diff --git a/envcfg.go b/envcfg.go new file mode 100644 index 0000000..b91ea13 --- /dev/null +++ b/envcfg.go | |||
@@ -0,0 +1,40 @@ | |||
1 | // Package envcfg provides environment variable mapping to structs. | ||
2 | // | ||
3 | // Can be used to read configuration parameters from the environment. | ||
4 | // | ||
5 | // Fields for which environment variables can be found are overwritten, otherwise they are left to their previous | ||
6 | // value. | ||
7 | // | ||
8 | // Can be used, for example, after gcfg to override settings provided in a configuration file. | ||
9 | package envcfg | ||
10 | |||
11 | import ( | ||
12 | "errors" | ||
13 | "reflect" | ||
14 | ) | ||
15 | |||
16 | const ( | ||
17 | TAG = "env" | ||
18 | ABS_TAG = "absenv" | ||
19 | SEP = "_" | ||
20 | ) | ||
21 | |||
22 | type node struct { | ||
23 | parent *node | ||
24 | value *reflect.Value | ||
25 | properties *reflect.StructField | ||
26 | } | ||
27 | |||
28 | var ErrInvalidConfigStruct = errors.New("invalid parameter: must map to a struct") | ||
29 | |||
30 | func ReadInto(cfgStruct interface{}) (interface{}, []error) { | ||
31 | s := reflect.ValueOf(cfgStruct).Elem() | ||
32 | |||
33 | if s.Kind() != reflect.Struct { | ||
34 | return nil, []error{ErrInvalidConfigStruct} | ||
35 | } | ||
36 | |||
37 | _, errs := setStructFields(node{nil, &s, nil}) | ||
38 | |||
39 | return cfgStruct, errs | ||
40 | } | ||
diff --git a/mapper.go b/mapper.go new file mode 100644 index 0000000..ca6885b --- /dev/null +++ b/mapper.go | |||
@@ -0,0 +1,86 @@ | |||
1 | package envcfg | ||
2 | |||
3 | import ( | ||
4 | "os" | ||
5 | "reflect" | ||
6 | "strconv" | ||
7 | "strings" | ||
8 | ) | ||
9 | |||
10 | func getEnvName(n node) string { | ||
11 | name := n.properties.Tag.Get(TAG) | ||
12 | if name == "" { | ||
13 | name = n.properties.Name | ||
14 | } | ||
15 | |||
16 | abs, _ := strconv.ParseBool(n.properties.Tag.Get(ABS_TAG)) | ||
17 | if !abs && n.parent != nil { | ||
18 | if n.parent.properties != nil { | ||
19 | parentName := getEnvName(*n.parent) | ||
20 | name = parentName + SEP + name | ||
21 | } | ||
22 | } | ||
23 | |||
24 | return strings.ToUpper(name) | ||
25 | } | ||
26 | |||
27 | func setValue(n node, v string) (node, error) { | ||
28 | switch n.value.Kind() { | ||
29 | case reflect.String: | ||
30 | n.value.SetString(v) | ||
31 | |||
32 | case reflect.Bool: | ||
33 | boolVal, err := strconv.ParseBool(v) | ||
34 | if err != nil { | ||
35 | return n, err | ||
36 | } | ||
37 | n.value.SetBool(boolVal) | ||
38 | |||
39 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
40 | intVal, err := strconv.ParseInt(v, 0, n.value.Type().Bits()) | ||
41 | if err != nil { | ||
42 | return n, err | ||
43 | } | ||
44 | n.value.SetInt(intVal) | ||
45 | |||
46 | case reflect.Float32, reflect.Float64: | ||
47 | floatVal, err := strconv.ParseFloat(v, n.value.Type().Bits()) | ||
48 | if err != nil { | ||
49 | return n, err | ||
50 | } | ||
51 | n.value.SetFloat(floatVal) | ||
52 | } | ||
53 | |||
54 | return n, nil | ||
55 | } | ||
56 | |||
57 | func setFieldValue(n node) (node, error) { | ||
58 | if n.value.Kind() == reflect.Struct { | ||
59 | setStructFields(n) | ||
60 | return n, nil | ||
61 | } | ||
62 | |||
63 | v := os.Getenv(getEnvName(n)) | ||
64 | if v != "" { | ||
65 | return setValue(n, v) | ||
66 | } | ||
67 | |||
68 | return n, nil | ||
69 | } | ||
70 | |||
71 | func setStructFields(n node) (node, []error) { | ||
72 | t := n.value.Type() | ||
73 | errs := []error{} | ||
74 | |||
75 | for i := 0; i < n.value.NumField(); i++ { | ||
76 | v := n.value.Field(i) | ||
77 | p := t.Field(i) | ||
78 | |||
79 | _, err := setFieldValue(node{&n, &v, &p}) | ||
80 | if err != nil { | ||
81 | errs = append(errs, err) | ||
82 | } | ||
83 | } | ||
84 | |||
85 | return n, errs | ||
86 | } | ||
diff --git a/nesting_test.go b/nesting_test.go new file mode 100644 index 0000000..3fcc059 --- /dev/null +++ b/nesting_test.go | |||
@@ -0,0 +1,52 @@ | |||
1 | package envcfg | ||
2 | |||
3 | import ( | ||
4 | "os" | ||
5 | "testing" | ||
6 | ) | ||
7 | |||
8 | type nestedStruct struct { | ||
9 | SubStruct struct { | ||
10 | Field string | ||
11 | } | ||
12 | } | ||
13 | |||
14 | func TestNestedMapping(t *testing.T) { | ||
15 | const ENV_KEY = "SUBSTRUCT_FIELD" | ||
16 | const ENV_VAL = "Remember: testing is the future!" | ||
17 | |||
18 | os.Clearenv() | ||
19 | os.Setenv(ENV_KEY, ENV_VAL) | ||
20 | |||
21 | s := nestedStruct{} | ||
22 | |||
23 | ReadInto(&s) | ||
24 | |||
25 | if s.SubStruct.Field != ENV_VAL { | ||
26 | t.Errorf("expected '%s', got '%s'", ENV_VAL, s.SubStruct.Field) | ||
27 | } | ||
28 | } | ||
29 | |||
30 | type deeplyNestedStruct struct { | ||
31 | SubStruct struct { | ||
32 | SubSubStruct struct { | ||
33 | Field string | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | |||
38 | func TestDeeplyNestedMapping(t *testing.T) { | ||
39 | const ENV_KEY = "SUBSTRUCT_SUBSUBSTRUCT_FIELD" | ||
40 | const ENV_VAL = "Remember: testing is the future!" | ||
41 | |||
42 | os.Clearenv() | ||
43 | os.Setenv(ENV_KEY, ENV_VAL) | ||
44 | |||
45 | s := deeplyNestedStruct{} | ||
46 | |||
47 | ReadInto(&s) | ||
48 | |||
49 | if s.SubStruct.SubSubStruct.Field != ENV_VAL { | ||
50 | t.Errorf("expected '%s', got '%s'", ENV_VAL, s.SubStruct.SubSubStruct.Field) | ||
51 | } | ||
52 | } | ||
diff --git a/overwriting_test.go b/overwriting_test.go new file mode 100644 index 0000000..1ed5daa --- /dev/null +++ b/overwriting_test.go | |||
@@ -0,0 +1,67 @@ | |||
1 | package envcfg | ||
2 | |||
3 | import ( | ||
4 | "os" | ||
5 | "testing" | ||
6 | ) | ||
7 | |||
8 | func TestKeeping(t *testing.T) { | ||
9 | const ORIG_VAL = "Remember: testing is the future!" | ||
10 | |||
11 | os.Clearenv() | ||
12 | |||
13 | s := struct{ Field string }{ORIG_VAL} | ||
14 | |||
15 | ReadInto(&s) | ||
16 | |||
17 | if s.Field != ORIG_VAL { | ||
18 | t.Errorf("expected '%s', got '%s'", ORIG_VAL, s.Field) | ||
19 | } | ||
20 | } | ||
21 | |||
22 | func TestOverwriting(t *testing.T) { | ||
23 | const ENV_KEY = "FIELD" | ||
24 | const ENV_VAL = "Remember: testing is the future!" | ||
25 | const ORIG_VAL = "Testing is pointless!" | ||
26 | |||
27 | os.Clearenv() | ||
28 | os.Setenv(ENV_KEY, ENV_VAL) | ||
29 | |||
30 | s := struct{ Field string }{ORIG_VAL} | ||
31 | |||
32 | ReadInto(&s) | ||
33 | |||
34 | if s.Field != ENV_VAL { | ||
35 | t.Errorf("expected '%s', got '%s'", ENV_VAL, s.Field) | ||
36 | } | ||
37 | } | ||
38 | |||
39 | type superStruct struct { | ||
40 | SubStruct nestedFields | ||
41 | } | ||
42 | |||
43 | type nestedFields struct { | ||
44 | KeepMe string | ||
45 | OverwriteMe string | ||
46 | } | ||
47 | |||
48 | func TestMultiOverwriting(t *testing.T) { | ||
49 | const ENV_KEY = "SUBSTRUCT_OVERWRITEME" | ||
50 | const ENV_VAL = "Remember: testing is the future!" | ||
51 | const ORIG_VAL = "Testing is pointless!" | ||
52 | |||
53 | os.Clearenv() | ||
54 | os.Setenv(ENV_KEY, ENV_VAL) | ||
55 | |||
56 | s := superStruct{nestedFields{ORIG_VAL, ORIG_VAL}} | ||
57 | |||
58 | ReadInto(&s) | ||
59 | |||
60 | if s.SubStruct.KeepMe != ORIG_VAL { | ||
61 | t.Errorf("expected '%s', got '%s'", ORIG_VAL, s.SubStruct.KeepMe) | ||
62 | } | ||
63 | |||
64 | if s.SubStruct.OverwriteMe != ENV_VAL { | ||
65 | t.Errorf("expected '%s', got '%s'", ENV_VAL, s.SubStruct.OverwriteMe) | ||
66 | } | ||
67 | } | ||
diff --git a/tagging_test.go b/tagging_test.go new file mode 100644 index 0000000..2fe2e33 --- /dev/null +++ b/tagging_test.go | |||
@@ -0,0 +1,70 @@ | |||
1 | package envcfg | ||
2 | |||
3 | import ( | ||
4 | "os" | ||
5 | "testing" | ||