This repository has been archived by the owner on Oct 9, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
/
section.go
234 lines (181 loc) · 7 KB
/
section.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
package config
import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"sync"
"github.com/lyft/flytestdlib/atomic"
"github.com/spf13/pflag"
)
type Section interface {
// Gets a cloned copy of the Config registered to this section. This config instance does not account for any child
// section registered.
GetConfig() Config
// Gets a function pointer to call when the config has been updated.
GetConfigUpdatedHandler() SectionUpdated
// Sets the config and sets a bit indicating whether the new config is different when compared to the existing value.
SetConfig(config Config) error
// Gets a value indicating whether the config has changed since the last call to GetConfigChangedAndClear and clears
// the changed bit. This operation is atomic.
GetConfigChangedAndClear() bool
// Retrieves the loaded values for section key if one exists, or nil otherwise.
GetSection(key SectionKey) Section
// Gets all child config sections.
GetSections() SectionMap
// Registers a section with the config manager. Section keys are case insensitive and must be unique.
// The section object must be passed by reference since it'll be used to unmarshal into. It must also support json
// marshaling. If the section registered gets updated at runtime, the updatesFn will be invoked to handle the propagation
// of changes.
RegisterSectionWithUpdates(key SectionKey, configSection Config, updatesFn SectionUpdated) (Section, error)
// Registers a section with the config manager. Section keys are case insensitive and must be unique.
// The section object must be passed by reference since it'll be used to unmarshal into. It must also support json
// marshaling. If the section registered gets updated at runtime, the updatesFn will be invoked to handle the propagation
// of changes.
MustRegisterSectionWithUpdates(key SectionKey, configSection Config, updatesFn SectionUpdated) Section
// Registers a section with the config manager. Section keys are case insensitive and must be unique.
// The section object must be passed by reference since it'll be used to unmarshal into. It must also support json
// marshaling.
RegisterSection(key SectionKey, configSection Config) (Section, error)
// Registers a section with the config manager. Section keys are case insensitive and must be unique.
// The section object must be passed by reference since it'll be used to unmarshal into. It must also support json
// marshaling.
MustRegisterSection(key SectionKey, configSection Config) Section
}
type Config = interface{}
type SectionKey = string
type SectionMap map[SectionKey]Section
// A section can optionally implements this interface to add its fields as cmdline arguments.
type PFlagProvider interface {
GetPFlagSet(prefix string) *pflag.FlagSet
}
type SectionUpdated func(ctx context.Context, newValue Config)
// Global section to use with any root-level config sections registered.
var rootSection = NewRootSection()
type section struct {
config Config
handler SectionUpdated
isDirty atomic.Bool
sections SectionMap
lockObj sync.RWMutex
}
// Gets the global root section.
func GetRootSection() Section {
return rootSection
}
func MustRegisterSection(key SectionKey, configSection Config) Section {
s, err := RegisterSection(key, configSection)
if err != nil {
panic(err)
}
return s
}
func (r *section) MustRegisterSection(key SectionKey, configSection Config) Section {
s, err := r.RegisterSection(key, configSection)
if err != nil {
panic(err)
}
return s
}
// Registers a section with the config manager. Section keys are case insensitive and must be unique.
// The section object must be passed by reference since it'll be used to unmarshal into. It must also support json
// marshaling.
func RegisterSection(key SectionKey, configSection Config) (Section, error) {
return rootSection.RegisterSection(key, configSection)
}
func (r *section) RegisterSection(key SectionKey, configSection Config) (Section, error) {
return r.RegisterSectionWithUpdates(key, configSection, nil)
}
func MustRegisterSectionWithUpdates(key SectionKey, configSection Config, updatesFn SectionUpdated) Section {
s, err := RegisterSectionWithUpdates(key, configSection, updatesFn)
if err != nil {
panic(err)
}
return s
}
func (r *section) MustRegisterSectionWithUpdates(key SectionKey, configSection Config, updatesFn SectionUpdated) Section {
s, err := r.RegisterSectionWithUpdates(key, configSection, updatesFn)
if err != nil {
panic(err)
}
return s
}
// Registers a section with the config manager. Section keys are case insensitive and must be unique.
// The section object must be passed by reference since it'll be used to unmarshal into. It must also support json
// marshaling. If the section registered gets updated at runtime, the updatesFn will be invoked to handle the propagation
// of changes.
func RegisterSectionWithUpdates(key SectionKey, configSection Config, updatesFn SectionUpdated) (Section, error) {
return rootSection.RegisterSectionWithUpdates(key, configSection, updatesFn)
}
func (r *section) RegisterSectionWithUpdates(key SectionKey, configSection Config, updatesFn SectionUpdated) (Section, error) {
r.lockObj.Lock()
defer r.lockObj.Unlock()
key = strings.ToLower(key)
if len(key) == 0 {
return nil, errors.New("key must be a non-zero string")
}
if configSection == nil {
return nil, fmt.Errorf("configSection must be a non-nil pointer. SectionKey: %v", key)
}
if reflect.TypeOf(configSection).Kind() != reflect.Ptr {
return nil, fmt.Errorf("section must be a Pointer. SectionKey: %v", key)
}
if _, alreadyExists := r.sections[key]; alreadyExists {
return nil, fmt.Errorf("key already exists [%v]", key)
}
section := NewSection(configSection, updatesFn)
r.sections[key] = section
return section, nil
}
// Retrieves the loaded values for section key if one exists, or nil otherwise.
func GetSection(key SectionKey) Section {
return rootSection.GetSection(key)
}
func (r *section) GetSection(key SectionKey) Section {
r.lockObj.RLock()
defer r.lockObj.RUnlock()
key = strings.ToLower(key)
if section, alreadyExists := r.sections[key]; alreadyExists {
return section
}
return nil
}
func (r *section) GetSections() SectionMap {
return r.sections
}
func (r *section) GetConfig() Config {
r.lockObj.RLock()
defer r.lockObj.RUnlock()
return r.config
}
func (r *section) SetConfig(c Config) error {
r.lockObj.Lock()
defer r.lockObj.Unlock()
if reflect.TypeOf(c).Kind() != reflect.Ptr {
return fmt.Errorf("config must be a Pointer")
}
if !DeepEqual(r.config, c) {
r.config = c
r.isDirty.Store(true)
}
return nil
}
func (r *section) GetConfigUpdatedHandler() SectionUpdated {
return r.handler
}
func (r *section) GetConfigChangedAndClear() bool {
return r.isDirty.CompareAndSwap(true, false)
}
func NewSection(configSection Config, updatesFn SectionUpdated) Section {
return §ion{
config: configSection,
handler: updatesFn,
isDirty: atomic.NewBool(false),
sections: map[SectionKey]Section{},
lockObj: sync.RWMutex{},
}
}
func NewRootSection() Section {
return NewSection(nil, nil)
}