Skip to content

Commit 9d2107b

Browse files
rewensetondrej-fabry
authored andcommitted
feat: Support string duration format in config files (#408)
* Create new type to use when you need duration in config * Support new duration format without custom type * Compare types, not names
1 parent b74d434 commit 9d2107b

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed

Gopkg.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/parser.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ package config
1717
import (
1818
"io/ioutil"
1919
"os"
20+
"reflect"
21+
"time"
2022

2123
"github.com/ghodss/yaml"
24+
"github.com/mitchellh/mapstructure"
2225
)
2326

2427
// ParseConfigFromYamlFile parses a configuration from a file in YAML
@@ -33,8 +36,37 @@ func ParseConfigFromYamlFile(path string, cfg interface{}) error {
3336
if err != nil {
3437
return err
3538
}
39+
return parseConfigFromYamlBytes(b, cfg)
40+
}
41+
42+
func parseConfigFromYamlBytes(b []byte, cfg interface{}) error {
43+
var data map[string]interface{}
44+
err := yaml.Unmarshal(b, &data)
45+
if err != nil {
46+
return err
47+
}
3648

37-
err = yaml.Unmarshal(b, cfg)
49+
dc := &mapstructure.DecoderConfig{
50+
DecodeHook: func(in, out reflect.Type, data interface{}) (interface{}, error) {
51+
// Only intended to help with cases when string must be set to `time.Duration`
52+
if in.Kind() != reflect.String || out != reflect.TypeOf(time.Duration(0)) {
53+
return data, nil
54+
}
55+
56+
pd, err := time.ParseDuration(data.(string))
57+
if err != nil {
58+
return nil, err
59+
}
60+
return pd, nil
61+
},
62+
Result: cfg,
63+
TagName: "json",
64+
}
65+
dec, err := mapstructure.NewDecoder(dc)
66+
if err != nil {
67+
return err
68+
}
69+
err = dec.Decode(data)
3870
if err != nil {
3971
return err
4072
}

config/parser_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package config
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestParseConfigFromYamlBytes(t *testing.T) {
11+
RegisterTestingT(t)
12+
13+
type Duration string
14+
15+
type BigConfig struct {
16+
Simple string
17+
Str string `json:"name"`
18+
Integer16 int16
19+
Timeout time.Duration
20+
NotTimeout Duration
21+
}
22+
23+
var testData = map[string]struct {
24+
input string
25+
want BigConfig
26+
fail bool
27+
}{
28+
"simple": {"simple: test", BigConfig{Simple: "test"}, false},
29+
"bad simple": {"simple test", BigConfig{}, true},
30+
"json tag": {"name: BigName", BigConfig{Str: "BigName"}, false},
31+
"int16": {"integer16: 25", BigConfig{Integer16: 25}, false},
32+
"duration number": {"timeout: 5000000000", BigConfig{Timeout: 5 * time.Second}, false},
33+
"duration string": {"timeout: 5s", BigConfig{Timeout: 5 * time.Second}, false},
34+
"bad duration": {"timeout: s5s", BigConfig{}, true},
35+
"not duration": {"nottimeout: 30SecToAgent", BigConfig{NotTimeout: "30SecToAgent"}, false},
36+
}
37+
38+
for name, tt := range testData {
39+
t.Run(name, func(t *testing.T) {
40+
RegisterTestingT(t)
41+
42+
out := BigConfig{}
43+
err := parseConfigFromYamlBytes([]byte(tt.input), &out)
44+
45+
if tt.fail {
46+
Expect(err).To(HaveOccurred())
47+
return
48+
}
49+
50+
Expect(err).ToNot(HaveOccurred())
51+
Expect(out).To(Equal(tt.want))
52+
})
53+
}
54+
}

0 commit comments

Comments
 (0)