Skip to content

Commit edfb821

Browse files
Merge pull request #54 from valentin-kaiser/service
fixed config package type handling and improved jrpc
2 parents d59e2bf + 4043a19 commit edfb821

File tree

8 files changed

+421
-233
lines changed

8 files changed

+421
-233
lines changed

apperror/apperror.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,32 @@ func (e Error) GetContext() map[string]interface{} {
194194
return e.Context
195195
}
196196

197+
// Is implements the error unwrapping interface for errors.Is()
198+
// It checks if the target error is equal to this error by comparing their messages
199+
func (e Error) Is(target error) bool {
200+
if target == nil {
201+
return false
202+
}
203+
204+
// Check if target is also an Error type
205+
if t, ok := target.(Error); ok {
206+
return e.Message == t.Message
207+
}
208+
209+
// Check if target's error message matches this error's message
210+
return e.Message == target.Error()
211+
}
212+
213+
// Unwrap implements the error unwrapping interface for errors.Is() and errors.As()
214+
// It returns the first additional error if any exist, allowing the standard library
215+
// to traverse the error chain when looking for specific error types
216+
func (e Error) Unwrap() error {
217+
if len(e.Errors) > 0 {
218+
return e.Errors[0]
219+
}
220+
return nil
221+
}
222+
197223
// Error implements the error interface and returns the error message
198224
// If debug mode is enabled, it includes the stack trace and additional errors
199225
func (e Error) Error() string {

config/core.go

Lines changed: 98 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
// import (
2929
// "fmt"
3030
// "github.com/valentin-kaiser/go-core/config"
31+
// "github.com/valentin-kaiser/go-core/flag"
3132
// "github.com/fsnotify/fsnotify"
3233
// )
3334
//
@@ -60,11 +61,16 @@
6061
// Port: 8080,
6162
// }
6263
//
63-
// if err := config.Register("server", cfg); err != nil {
64+
// // Register config - path parameter is ignored, flag.Path will be used
65+
// if err := config.Register("", "server", cfg); err != nil {
6466
// fmt.Println("Error registering config:", err)
6567
// return
6668
// }
6769
//
70+
// // Parse flags (including --path and config-specific flags)
71+
// flag.Init()
72+
//
73+
// // Read config using the parsed --path flag
6874
// if err := config.Read(); err != nil {
6975
// fmt.Println("Error reading config:", err)
7076
// return
@@ -98,35 +104,68 @@ import (
98104
)
99105

100106
var (
101-
logger = logging.GetPackageLogger("config")
102-
mutex = &sync.RWMutex{}
107+
logger = logging.GetPackageLogger("config")
108+
mutex = &sync.RWMutex{}
109+
cm = &manager{
110+
name: "config",
111+
defaults: make(map[string]interface{}),
112+
values: make(map[string]interface{}),
113+
flags: make(map[string]*pflag.Flag),
114+
}
115+
)
116+
117+
// Config is the interface that all configuration structs must implement
118+
// It should contain a Validate method that checks the configuration for errors
119+
type Config interface {
120+
Validate() error
121+
}
122+
123+
type manager struct {
124+
path string
125+
name string
103126
config Config
104-
configName string
105-
configPath string
106127
lastChange atomic.Int64
107128
prefix string
108129
defaults map[string]interface{}
109130
values map[string]interface{}
110131
flags map[string]*pflag.Flag
111132
onChange []func(o Config, n Config) error
112133
watcher *fsnotify.Watcher
113-
)
134+
}
114135

115-
func init() {
116-
defaults = make(map[string]interface{})
117-
values = make(map[string]interface{})
118-
flags = make(map[string]*pflag.Flag)
136+
func new() *manager {
137+
return &manager{
138+
name: "config",
139+
defaults: make(map[string]interface{}),
140+
values: make(map[string]interface{}),
141+
flags: make(map[string]*pflag.Flag),
142+
}
119143
}
120144

121-
// Config is the interface that all configuration structs must implement
122-
// It should contain a Validate method that checks the configuration for errors
123-
type Config interface {
124-
Validate() error
145+
func Manager() *manager {
146+
mutex.Lock()
147+
defer mutex.Unlock()
148+
return cm
149+
}
150+
151+
func (m *manager) WithPath(path string) *manager {
152+
mutex.Lock()
153+
defer mutex.Unlock()
154+
m.path = path
155+
return m
156+
}
157+
158+
func (m *manager) WithName(name string) *manager {
159+
mutex.Lock()
160+
defer mutex.Unlock()
161+
m.name = name
162+
m.prefix = strings.ToUpper(strings.ReplaceAll(name, "-", "_"))
163+
return m
125164
}
126165

127166
// Register registers a configuration struct and parses its tags
128167
// The name is used as the name of the configuration file and the prefix for the environment variables
129-
func Register(path, name string, c Config) error {
168+
func (m *manager) Register(c Config) error {
130169
if c == nil {
131170
return apperror.NewError("the configuration provided is nil")
132171
}
@@ -135,65 +174,66 @@ func Register(path, name string, c Config) error {
135174
return apperror.NewErrorf("the configuration provided is not a pointer to a struct, got %T", c)
136175
}
137176

138-
mutex.Lock()
139-
configName = name
140-
configPath = path
141-
prefix = strings.ToUpper(strings.ReplaceAll(configName, "-", "_"))
142-
mutex.Unlock()
143-
144-
err := parseStructTags(reflect.ValueOf(c), "")
177+
err := m.parseStructTags(reflect.ValueOf(c), "")
145178
if err != nil {
146179
return apperror.Wrap(err)
147180
}
148181

149-
set(c)
182+
m.set(c)
150183
return nil
151184
}
152185

153186
// OnChange registers a function that is called when the configuration changes
154187
func OnChange(f func(o Config, n Config) error) {
155-
onChange = append(onChange, f)
188+
mutex.Lock()
189+
defer mutex.Unlock()
190+
cm.onChange = append(cm.onChange, f)
156191
}
157192

158193
// Get returns the current configuration
159194
func Get() Config {
160195
mutex.RLock()
161196
defer mutex.RUnlock()
162-
return config
197+
return cm.config
163198
}
164199

165200
// Read reads the configuration from the file, validates it and applies it
166201
// If the file does not exist, it creates a new one with the default values
202+
// The config path is resolved from flag.Path when this function is called
167203
func Read() error {
168-
setConfigName(configName)
169-
addConfigPath(flag.Path)
204+
// Resolve the config path from flag.Path now that flags should be parsed
205+
if cm.path == "" {
206+
mutex.Lock()
207+
cm.path = flag.Path
208+
mutex.Unlock()
209+
}
170210

171-
err := read()
211+
err := cm.read()
172212
if err != nil {
173-
err := os.MkdirAll(flag.Path, 0750)
213+
err := os.MkdirAll(cm.path, 0750)
174214
if err != nil {
175215
return apperror.NewError("creating configuration directory failed").AddError(err)
176216
}
177217

178-
err = save()
218+
err = cm.save()
179219
if err != nil {
180220
return apperror.NewError("writing default configuration file failed").AddError(err)
181221
}
182222

183-
err = read()
223+
err = cm.read()
184224
if err != nil {
185225
return apperror.NewError("reading configuration file after creation failed").AddError(err)
186226
}
187227
}
188228

189-
change, ok := reflect.New(reflect.TypeOf(config).Elem()).Interface().(Config)
229+
change, ok := reflect.New(reflect.TypeOf(cm.config).Elem()).Interface().(Config)
190230
if !ok {
191-
return apperror.NewErrorf("creating new instance of %T failed", config)
231+
return apperror.NewErrorf("creating new instance of %T failed", cm.config)
192232
}
193233

194-
err = unmarshal(change)
234+
err = cm.unmarshal(change)
195235
if err != nil {
196-
return apperror.NewErrorf("unmarshalling configuration data in %T failed", config).AddError(err)
236+
return apperror.NewErrorf("unmarshalling configuration data in %T failed", cm.config).AddError(err)
197237
}
198238

199239
err = change.Validate()
@@ -202,8 +242,8 @@ func Read() error {
202242
}
203243

204244
o := Get()
205-
set(change)
206-
for _, f := range onChange {
245+
cm.set(change)
246+
for _, f := range cm.onChange {
207247
err = f(o, change)
208248
if err != nil {
209249
return apperror.Wrap(err)
@@ -215,42 +255,43 @@ func Read() error {
215255

216256
// Write writes the configuration to the file, validates it and applies it
217257
// If the file does not exist, it creates a new one with the default values
258+
// The config path is resolved from flag.Path when this function is called
259+
// Write will not trigger any OnChange handlers unless the configuration is Read again
218260
func Write(change Config) error {
219261
if change == nil {
220262
return apperror.NewError("the configuration provided is nil")
221263
}
222264

265+
// Resolve the config path from flag.Path if not already set
266+
if cm.path == "" {
267+
mutex.Lock()
268+
cm.path = flag.Path
269+
mutex.Unlock()
270+
}
271+
223272
err := change.Validate()
224273
if err != nil {
225274
return apperror.Wrap(err)
226275
}
227276

228-
o := Get()
229-
set(change)
230-
err = save()
277+
cm.set(change)
278+
err = cm.save()
231279
if err != nil {
232280
return apperror.Wrap(err)
233281
}
234282

235-
for _, f := range onChange {
236-
err = f(o, change)
237-
if err != nil {
238-
return apperror.Wrap(err)
239-
}
240-
}
241-
242283
return nil
243284
}
244285

245-
// Watch watches the configuration file for changes and calls the provided function when it changes
286+
// Watch watches the configuration file for changes and calls Read when it changes
246287
// It ignores changes that happen within 1 second of each other
247288
// This is to prevent multiple calls when the file is saved
248289
func Watch() {
249-
err := watch(func(_ fsnotify.Event) {
250-
if time.Now().UnixMilli()-lastChange.Load() < 1000 {
290+
err := cm.watch(func(_ fsnotify.Event) {
291+
if time.Now().UnixMilli()-cm.lastChange.Load() < 1000 {
251292
return
252293
}
253-
lastChange.Store(time.Now().UnixMilli())
294+
cm.lastChange.Store(time.Now().UnixMilli())
254295
err := Read()
255296
if err != nil {
256297
logger.Error().Err(err).Msg("failed to read configuration")
@@ -268,20 +309,12 @@ func Reset() {
268309
mutex.Lock()
269310
defer mutex.Unlock()
270311

271-
if watcher != nil {
272-
watcher.Close()
273-
watcher = nil
312+
if cm.watcher != nil {
313+
cm.watcher.Close()
314+
cm.watcher = nil
274315
}
275316

276-
config = nil
277-
configName = ""
278-
configPath = ""
279-
prefix = ""
280-
defaults = make(map[string]interface{})
281-
values = make(map[string]interface{})
282-
flags = make(map[string]*pflag.Flag)
283-
onChange = nil
284-
lastChange.Store(0)
317+
cm = new()
285318
}
286319

287320
// Changed checks if two configuration values are different by comparing their reflection values.
@@ -314,8 +347,8 @@ func Changed(o, n any) bool {
314347
}
315348

316349
// set applies the configuration to the global variable
317-
func set(appConfig Config) {
350+
func (m *manager) set(appConfig Config) {
318351
mutex.Lock()
319352
defer mutex.Unlock()
320-
config = appConfig
353+
m.config = appConfig
321354
}

0 commit comments

Comments
 (0)