-
Notifications
You must be signed in to change notification settings - Fork 6
/
config.go
241 lines (199 loc) · 6.57 KB
/
config.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package config
import (
"context"
"errors"
"fmt"
"reflect"
"sync"
"github.com/DoNewsCode/core/contract"
"github.com/knadh/koanf"
"github.com/mitchellh/mapstructure"
)
// KoanfAdapter is a implementation of contract.Config based on Koanf (https://github.com/knadh/koanf).
type KoanfAdapter struct {
layers []ProviderSet
watcher contract.ConfigWatcher
delimiter string
rwlock sync.RWMutex
K *koanf.Koanf
}
// ProviderSet is a configuration layer formed by a parser and a provider.
type ProviderSet struct {
Parser koanf.Parser
Provider koanf.Provider
}
// Option is the functional option type for KoanfAdapter
type Option func(option *KoanfAdapter)
// WithProviderLayer is an option for *KoanfAdapter that adds a layer to the bottom of the configuration stack.
// This option can be used multiple times, thus forming the whole stack. The layer on top has higher priority.
func WithProviderLayer(provider koanf.Provider, parser koanf.Parser) Option {
return func(option *KoanfAdapter) {
option.layers = append(option.layers, ProviderSet{Provider: provider, Parser: parser})
}
}
// WithWatcher is an option for *KoanfAdapter that adds a config watcher. The watcher should notify the configurations
// whenever a reload event is triggered.
func WithWatcher(watcher contract.ConfigWatcher) Option {
return func(option *KoanfAdapter) {
option.watcher = watcher
}
}
// WithDelimiter changes the default delimiter of Koanf. See Koanf's doc to learn more about delimiters.
func WithDelimiter(delimiter string) Option {
return func(option *KoanfAdapter) {
option.delimiter = delimiter
}
}
// NewConfig creates a new *KoanfAdapter.
func NewConfig(options ...Option) (*KoanfAdapter, error) {
adapter := KoanfAdapter{delimiter: "."}
for _, f := range options {
f(&adapter)
}
adapter.K = koanf.New(adapter.delimiter)
if err := adapter.Reload(); err != nil {
return nil, err
}
return &adapter, nil
}
// Reload reloads the whole configuration stack. It reloads layer by layer, so if
// an error occurred, Reload will return early and abort the rest of the
// reloading.
func (k *KoanfAdapter) Reload() error {
k.rwlock.Lock()
defer k.rwlock.Unlock()
for i := len(k.layers) - 1; i >= 0; i-- {
err := k.K.Load(k.layers[i].Provider, k.layers[i].Parser)
if err != nil {
return fmt.Errorf("unable to load config %w", err)
}
}
return nil
}
// Watch uses the internal watcher to watch the configuration reload signals.
// This function should be registered in the run group. If the watcher is nil,
// this call will block until context expired.
func (k *KoanfAdapter) Watch(ctx context.Context) error {
if k.watcher == nil {
<-ctx.Done()
return ctx.Err()
}
return k.watcher.Watch(ctx, k.Reload)
}
// Unmarshal unmarshals a given key path into the given struct using the mapstructure lib.
// If no path is specified, the whole map is unmarshalled. `koanf` is the struct field tag used to match field names.
func (k *KoanfAdapter) Unmarshal(path string, o interface{}) error {
k.rwlock.RLock()
defer k.rwlock.RUnlock()
return k.K.UnmarshalWithConf(path, o, koanf.UnmarshalConf{
Tag: "json",
DecoderConfig: &mapstructure.DecoderConfig{
Result: o,
ErrorUnused: true,
WeaklyTypedInput: true,
},
})
}
// Route cuts the config map at a given key path into a sub map and returns a new contract.ConfigAccessor instance
// with the cut config map loaded. For instance, if the loaded config has a path that looks like parent.child.sub.a.b,
// `Route("parent.child")` returns a new contract.ConfigAccessor instance with the config map `sub.a.b` where
// everything above `parent.child` are cut out.
func (k *KoanfAdapter) Route(s string) contract.ConfigAccessor {
k.rwlock.RLock()
defer k.rwlock.RUnlock()
return &KoanfAdapter{
K: k.K.Cut(s),
}
}
// String returns the string value of a given key path or "" if the path does not exist or if the value is not a valid string
func (k *KoanfAdapter) String(s string) string {
k.rwlock.RLock()
defer k.rwlock.RUnlock()
return k.K.String(s)
}
// Int returns the int value of a given key path or 0 if the path does not exist or if the value is not a valid int.
func (k *KoanfAdapter) Int(s string) int {
k.rwlock.RLock()
defer k.rwlock.RUnlock()
return k.K.Int(s)
}
// Strings returns the []string slice value of a given key path or an empty []string slice if the path does not exist
// or if the value is not a valid string slice.
func (k *KoanfAdapter) Strings(s string) []string {
k.rwlock.RLock()
defer k.rwlock.RUnlock()
return k.K.Strings(s)
}
// Bool returns the bool value of a given key path or false if the path does not exist or if the value is not a valid bool representation.
// Accepted string representations of bool are the ones supported by strconv.ParseBool.
func (k *KoanfAdapter) Bool(s string) bool {
k.rwlock.RLock()
defer k.rwlock.RUnlock()
return k.K.Bool(s)
}
// Get returns the raw, uncast interface{} value of a given key path in the config map. If the key path does not exist, nil is returned.
func (k *KoanfAdapter) Get(s string) interface{} {
k.rwlock.RLock()
defer k.rwlock.RUnlock()
return k.K.Get(s)
}
// Float64 returns the float64 value of a given key path or 0 if the path does not exist or if the value is not a valid float64.
func (k *KoanfAdapter) Float64(s string) float64 {
k.rwlock.RLock()
defer k.rwlock.RUnlock()
return k.K.Float64(s)
}
// MapAdapter implements ConfigAccessor and ConfigRouter.
// It is primarily used for testing
type MapAdapter map[string]interface{}
func (m MapAdapter) String(s string) string {
return m[s].(string)
}
func (m MapAdapter) Int(s string) int {
return m[s].(int)
}
func (m MapAdapter) Strings(s string) []string {
return m[s].([]string)
}
func (m MapAdapter) Bool(s string) bool {
return m[s].(bool)
}
func (m MapAdapter) Get(s string) interface{} {
return m[s]
}
func (m MapAdapter) Float64(s string) float64 {
return m[s].(float64)
}
func (m MapAdapter) Unmarshal(path string, o interface{}) (err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New(fmt.Sprintf("%v", r))
}
}()
var out interface{}
out = m
if path != "" {
out = m[path]
}
val := reflect.ValueOf(o)
if !val.Elem().CanSet() {
return errors.New("target cannot be set")
}
val.Elem().Set(reflect.ValueOf(out))
return
}
func (m MapAdapter) Route(s string) contract.ConfigAccessor {
var v interface{}
v = m
if s != "" {
v = m[s]
}
switch v.(type) {
case map[string]interface{}:
return MapAdapter(v.(map[string]interface{}))
case MapAdapter:
return v.(MapAdapter)
default:
panic(fmt.Sprintf("value at path %s is not a valid Router", s))
}
}