/
loader.go
113 lines (101 loc) Β· 3.02 KB
/
loader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package cconfig
import (
"github.com/gocopper/copper/cerrors"
"github.com/pelletier/go-toml"
)
type (
// Path defines the path to the config file.
Path string
// Overrides defines a ';' separated string of config overrides in TOML format
Overrides string
)
// Loader provides methods to load config files into structs.
type Loader interface {
// Load reads the config values defined under the given key (TOML table) and sets them into the dest struct.
// For example:
// # prod.toml
// key1 = "val1"
//
// # config.go
// type MyConfig struct {
// Key1 string `toml:"key1"`
// }
//
// func LoadMyConfig(loader cconfig.Loader) (MyConfig, error) {
// var config MyConfig
//
// err := loader.Load("my_config", &config)
// if err != nil {
// return MyConfig{}, cerrors.New(err, "failed to load configs for my_config", nil)
// }
//
// return config, nil
// }
Load(key string, dest interface{}) error
}
// New provides an implementation of Loader that reads a config file at the given file path. It supports extending the
// config file at the given path by using an 'extends' key. For example, the config file may be extended like so:
//
// # base.toml
// key1 = "val1"
//
// # prod.toml
// extends = "base.toml
// key2 = "val2"
//
// If New is called with the path to prod.toml, it loads both key2 (from prod.toml) and key1 (from base.toml) since
// prod.toml extends base.toml.
//
// The extends key can support multiple files like so:
// extends = ["base.toml", "secrets.toml"]
//
// If a config key is present in multiple files, New returns an error. For example, if prod.toml sets a value for 'key1'
// that has already been set in base.toml, an error will be returned. To enable key overrides see NewWithKeyOverrides.
func New(fp Path, ov Overrides) (Loader, error) {
return newLoader(string(fp), string(ov), true)
}
// NewWithKeyOverrides works exactly the same way as New except it supports key overrides. For example, this is a valid
// config:
// # base.toml
// key1 = "val1"
//
// # prod.toml
// extends = "base.toml
// key1 = "val2"
//
// If prod.toml is loaded, key1 will be set to "val2" since it has been overridden in prod.toml.
func NewWithKeyOverrides(fp Path, overrides Overrides) (Loader, error) {
return newLoader(string(fp), string(overrides), false)
}
func newLoader(fp, overrides string, disableKeyOverrides bool) (*loader, error) {
tree, err := loadTree(fp, overrides, disableKeyOverrides)
if err != nil {
return nil, cerrors.New(err, "failed to load config tree", map[string]interface{}{
"path": fp,
})
}
return &loader{
tree: tree,
}, nil
}
type loader struct {
tree *toml.Tree
}
func (l *loader) Load(key string, dest interface{}) error {
if !l.tree.Has(key) {
return nil
}
keyTree, ok := l.tree.Get(key).(*toml.Tree)
if !ok {
return cerrors.New(nil, "invalid key type", map[string]interface{}{
"key": key,
})
}
err := keyTree.Unmarshal(dest)
if err != nil {
return cerrors.New(err, "failed to unmarshal config into dest", map[string]interface{}{
"key": key,
})
}
return nil
}