From b081facea58e9798ba5117f2d2a969f356c5f0e3 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Sun, 23 Oct 2022 09:44:08 +0200 Subject: [PATCH] feat inform enventhandler about an error that has occurred --- api.go | 20 +++++++- change-handler.go | 0 change.go | 7 +++ copyable.go | 7 +++ env.go | 7 +++ error-handler.go | 105 ++++++++++++++++++++++++++++++++++++++++++ error-handler_test.go | 0 export.go | 21 +++++++++ file.go | 53 +++++++++++++++++++++ flags.go | 14 ++++++ import.go | 14 ++++++ settings.go | 7 +++ watch.go | 21 +++++++++ 13 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 change-handler.go create mode 100644 error-handler.go create mode 100644 error-handler_test.go diff --git a/api.go b/api.go index 809b9e7..9dcbb03 100644 --- a/api.go +++ b/api.go @@ -3,13 +3,23 @@ package configuration -import "github.com/imdario/mergo" +import ( + "github.com/imdario/mergo" +) // NewSetting creates a new configuration setting // with the given defaults. func New[C any](defaults C) *Settings[C] { s := &Settings[C]{} + + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + s.initDefaults() if err := mergo.Merge(&defaults, s.config); err != nil { @@ -32,6 +42,14 @@ func New[C any](defaults C) *Settings[C] { // Set the mnemonic // The mnemonic is used to identify the configuration in the configuration file func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] { + + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if mnemonic == "" { s.errors = append(s.errors, MnemonicEmptyError) } else { diff --git a/change-handler.go b/change-handler.go new file mode 100644 index 0000000..e69de29 diff --git a/change.go b/change.go index f513670..cb083d7 100644 --- a/change.go +++ b/change.go @@ -71,6 +71,13 @@ func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] { }() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if err := validateConfig[C](config); err != nil { s.errors = append(s.errors, err) return s diff --git a/copyable.go b/copyable.go index 1fde765..df98baf 100644 --- a/copyable.go +++ b/copyable.go @@ -12,6 +12,13 @@ func (s *Settings[C]) Copy(m map[string]any) { c := s.Config() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + for k, v := range m { err := pathfinder.SetValue(&c, k, v) if err != nil { diff --git a/env.go b/env.go index 50ac0d1..7271930 100644 --- a/env.go +++ b/env.go @@ -14,6 +14,13 @@ func (s *Settings[C]) InitFromEnv(prefix string) *Settings[C] { s.Lock() defer s.Unlock() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + err := runOnTags(&s.config, []string{envTagKey}, func(k string, field reflect.Value) { if !field.CanSet() { diff --git a/error-handler.go b/error-handler.go new file mode 100644 index 0000000..91f3ae4 --- /dev/null +++ b/error-handler.go @@ -0,0 +1,105 @@ +// Copyright 2022 schukai GmbH +// SPDX-License-Identifier: AGPL-3.0 + +package configuration + +import ( + "fmt" + "github.com/r3labs/diff/v3" +) + +type ChangeEvent struct { + Changlog diff.Changelog +} + +type EventHook interface { + Handle(event ChangeEvent) +} + +// OnChange registers a hook that is called when the configuration changes. +func (s *Settings[C]) OnChange(hook EventHook) *Settings[C] { + s.hooks.change = append(s.hooks.change, hook) + return s +} + +// HasOnChangeHook returns true if there are registered hooks. +func (s *Settings[C]) HasOnChangeHook(hook EventHook) *Settings[C] { + for _, h := range s.hooks.change { + if h == hook { + break + } + } + return s +} + +// RemoveOnChangeHook removes a change hook from the list of hooks. +func (s *Settings[C]) RemoveOnChangeHook(hook EventHook) *Settings[C] { + for i, h := range s.hooks.change { + if h == hook { + s.hooks.change = append(s.hooks.change[:i], s.hooks.change[i+1:]...) + break + } + } + return s +} + +func (s *Settings[C]) notifyChangeHooks(changelog diff.Changelog) *Settings[C] { + for _, h := range s.hooks.change { + h.Handle(ChangeEvent{Changlog: changelog}) + } + return s +} + +func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] { + + var ( + changelog diff.Changelog + err error + ) + + if lock { + s.Lock() + } + + defer func() { + if lock { + s.Unlock() + } + + if len(changelog) > 0 { + go s.notifyChangeHooks(changelog) + } + + fmt.Println(">>>>>>>>> setConfigInternal", len(changelog)) + fmt.Println(changelog) + fmt.Println(s.errors) + + }() + + if err := validateConfig[C](config); err != nil { + s.errors = append(s.errors, err) + return s + } + + d, err := diff.NewDiffer() + if err != nil { + s.errors = append(s.errors, err) + return s + } + + d.ConvertCompatibleTypes = true + d.AllowTypeMismatch = true + + changelog, err = d.Diff(s.config, config) + if err != nil { + s.errors = append(s.errors, err) + return s + } + + s.config = config + return s +} + +func (s *Settings[C]) SetConfig(config C) *Settings[C] { + return s.setConfigInternal(config, true) +} diff --git a/error-handler_test.go b/error-handler_test.go new file mode 100644 index 0000000..e69de29 diff --git a/export.go b/export.go index bbeba7f..0c57483 100644 --- a/export.go +++ b/export.go @@ -34,6 +34,13 @@ func (s *Settings[C]) writeProperties(writer io.Writer) error { m, errors := getMapForProperties[C](s.config) + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if len(errors) > 0 { for _, err := range errors { s.errors = append(s.errors, err) @@ -53,6 +60,13 @@ func (s *Settings[C]) WriteFile(fn string, format Format) *Settings[C] { var file *os.File + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if fn == "" { file = os.Stdout } else { @@ -73,6 +87,13 @@ func (s *Settings[C]) Write(writer io.Writer, format Format) *Settings[C] { var err error + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + switch format { case Json: err = s.writeJson(writer) diff --git a/file.go b/file.go index f3161de..252529e 100644 --- a/file.go +++ b/file.go @@ -38,6 +38,13 @@ func (s *Settings[C]) AddFile(file string, format ...Format) *Settings[C] { var f Format + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if format == nil || len(format) == 0 { f = RecogniseFormat } else if format != nil && len(format) == 1 { @@ -104,6 +111,13 @@ func (s *Settings[C]) AddDirectory(d string) *Settings[C] { func (s *Settings[C]) sanitizeDirectories() { + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + wd, err := os.Getwd() if err != nil { s.errors = append(s.errors, err) @@ -146,9 +160,17 @@ func (s *Settings[C]) AddWorkingDirectory() *Settings[C] { s.Lock() defer s.Unlock() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + current, err := os.Getwd() if err != nil { s.errors = append(s.errors, err) + return s } s.files.directories = append(s.files.directories, current) @@ -161,6 +183,13 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] { s.Lock() defer s.Unlock() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if s.mnemonic == "" { s.errors = append(s.errors, MnemonicEmptyError) return s @@ -183,6 +212,16 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] { // The mnemonic must be set for this function func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] { + s.Lock() + defer s.Unlock() + + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if s.mnemonic == "" { s.errors = append(s.errors, MnemonicEmptyError) return s @@ -214,6 +253,13 @@ func (s *Settings[C]) SetDefaultDirectories() *Settings[C] { func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] { + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if slices.Contains(availableFormats, format) { s.files.format = format } else { @@ -226,6 +272,13 @@ func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] { // Set the file name without extension func (s *Settings[C]) SetFileName(name string) *Settings[C] { + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if name == "" { s.errors = append(s.errors, FileNameEmptyError) } else { diff --git a/flags.go b/flags.go index 904947b..0fb13a8 100644 --- a/flags.go +++ b/flags.go @@ -13,6 +13,13 @@ import ( // The file is read from the flag specified by the name func (s *Settings[C]) AddFileFromFlagSet(flagset *flag.FlagSet, name string, format Format) *Settings[C] { + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + flag := flagset.Lookup(name) if flag == nil { s.errors = append(s.errors, newFlagDoesNotExistError(name)) @@ -33,6 +40,13 @@ func (s *Settings[C]) InitFromFlagSet(flagset *flag.FlagSet) *Settings[C] { s.Lock() defer s.Unlock() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + err := runOnTags(&s.config, []string{flagTagKey}, func(k string, field reflect.Value) { flag := flagset.Lookup(k) diff --git a/import.go b/import.go index 6ec9e29..074a413 100644 --- a/import.go +++ b/import.go @@ -49,6 +49,13 @@ func (s *Settings[C]) importStream(r reader) { var c C var err error + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + err = nil // reset error x := r.reader @@ -128,6 +135,13 @@ func (s *Settings[C]) Import() *Settings[C] { s.Lock() defer s.Unlock() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + defaults := s.config var n C diff --git a/settings.go b/settings.go index ff35067..12d57bd 100644 --- a/settings.go +++ b/settings.go @@ -36,6 +36,13 @@ type Settings[C any] struct { func (s *Settings[C]) initDefaults() *Settings[C] { + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + err := runOnTags(&s.config, []string{"default"}, func(v string, field reflect.Value) { if field.CanSet() { diff --git a/watch.go b/watch.go index a1d3d7a..9503048 100644 --- a/watch.go +++ b/watch.go @@ -11,6 +11,13 @@ func (s *Settings[C]) initWatch() *Settings[C] { var err error + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if s.fileWatch.watcher != nil { s.errors = append(s.errors, WatchAlreadyInitializedError) return s @@ -31,6 +38,13 @@ func (s *Settings[C]) StopWatching() *Settings[C] { s.fileWatch.Lock() defer s.fileWatch.Unlock() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if s.fileWatch.watcher == nil { s.errors = append(s.errors, WatchNotInitializedError) return s @@ -52,6 +66,13 @@ func (s *Settings[C]) Watch() *Settings[C] { s.fileWatch.Lock() defer s.fileWatch.Unlock() + errorCount := len(s.errors) + defer func() { + if len(s.errors) > errorCount { + s.notifyErrorHooks() + } + }() + if s.fileWatch.watcher == nil { s.initWatch() } -- GitLab