Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • v1.0.0
  • v1.0.1
  • v1.1.0
  • v1.10.0
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.15.0
  • v1.16.0
  • v1.17.1
  • v1.17.2
  • v1.17.3
  • v1.17.4
  • v1.17.5
  • v1.17.6
  • v1.17.7
  • v1.18.0
  • v1.18.1
  • v1.18.2
  • v1.18.3
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.3
  • v1.22.4
  • v1.22.5
  • v1.22.6
  • v1.22.7
  • v1.22.8
  • v1.22.9
  • v1.3.0
  • v1.4.0
  • v1.4.1
  • v1.4.2
  • v1.4.3
  • v1.5.0
  • v1.6.0
  • v1.7.0
  • v1.7.1
  • v1.8.0
  • v1.9.0
55 results

Target

Select target project
  • oss/libraries/go/application/configuration
1 result
Select Git revision
  • master
  • v1.0.0
  • v1.0.1
  • v1.1.0
  • v1.10.0
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.15.0
  • v1.16.0
  • v1.17.1
  • v1.17.2
  • v1.17.3
  • v1.17.4
  • v1.17.5
  • v1.17.6
  • v1.17.7
  • v1.18.0
  • v1.18.1
  • v1.18.2
  • v1.18.3
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.3
  • v1.22.4
  • v1.22.5
  • v1.22.6
  • v1.22.7
  • v1.22.8
  • v1.22.9
  • v1.3.0
  • v1.4.0
  • v1.4.1
  • v1.4.2
  • v1.4.3
  • v1.5.0
  • v1.6.0
  • v1.7.0
  • v1.7.1
  • v1.8.0
  • v1.9.0
55 results
Show changes
Commits on Source (8)
<a name="v1.11.0"></a>
## [v1.11.0] - 2022-10-23
### Add Features
- feat error handler and changed update handler
- feat inform enventhandler about an error that has occurred
- feat new ChangeEventHandler struct
### Bug Fixes
- fix protect access to the config
### Changes
- chore licenses
### Code Refactoring
- refactor assign file list
<a name="v1.10.0"></a> <a name="v1.10.0"></a>
## [v1.10.0] - 2022-10-17 ## [v1.10.0] - 2022-10-17
### Bug Fixes ### Bug Fixes
...@@ -96,6 +113,7 @@ ...@@ -96,6 +113,7 @@
<a name="v1.0.0"></a> <a name="v1.0.0"></a>
## v1.0.0 - 2022-09-18 ## v1.0.0 - 2022-09-18
[v1.11.0]: https://gitlab.schukai.com/oss/libraries/go/application/configuration/compare/v1.10.0...v1.11.0
[v1.10.0]: https://gitlab.schukai.com/oss/libraries/go/application/configuration/compare/v1.9.0...v1.10.0 [v1.10.0]: https://gitlab.schukai.com/oss/libraries/go/application/configuration/compare/v1.9.0...v1.10.0
[v1.9.0]: https://gitlab.schukai.com/oss/libraries/go/application/configuration/compare/v1.8.0...v1.9.0 [v1.9.0]: https://gitlab.schukai.com/oss/libraries/go/application/configuration/compare/v1.8.0...v1.9.0
[v1.8.0]: https://gitlab.schukai.com/oss/libraries/go/application/configuration/compare/v1.7.1...v1.8.0 [v1.8.0]: https://gitlab.schukai.com/oss/libraries/go/application/configuration/compare/v1.7.1...v1.8.0
......
...@@ -3,13 +3,23 @@ ...@@ -3,13 +3,23 @@
package configuration package configuration
import "github.com/imdario/mergo" import (
"github.com/imdario/mergo"
)
// NewSetting creates a new configuration setting // NewSetting creates a new configuration setting
// with the given defaults. // with the given defaults.
func New[C any](defaults C) *Settings[C] { func New[C any](defaults C) *Settings[C] {
s := &Settings[C]{} s := &Settings[C]{}
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
s.initDefaults() s.initDefaults()
if err := mergo.Merge(&defaults, s.config); err != nil { if err := mergo.Merge(&defaults, s.config); err != nil {
...@@ -32,6 +42,14 @@ func New[C any](defaults C) *Settings[C] { ...@@ -32,6 +42,14 @@ func New[C any](defaults C) *Settings[C] {
// Set the mnemonic // Set the mnemonic
// The mnemonic is used to identify the configuration in the configuration file // The mnemonic is used to identify the configuration in the configuration file
func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] { func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] {
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if mnemonic == "" { if mnemonic == "" {
s.errors = append(s.errors, MnemonicEmptyError) s.errors = append(s.errors, MnemonicEmptyError)
} else { } else {
...@@ -44,5 +62,9 @@ func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] { ...@@ -44,5 +62,9 @@ func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] {
// Remember that the configuration is a copy of the original configuration. // Remember that the configuration is a copy of the original configuration.
// Changes to the configuration will not be reflected in the original configuration. // Changes to the configuration will not be reflected in the original configuration.
func (s *Settings[C]) Config() C { func (s *Settings[C]) Config() C {
s.Lock()
defer s.Unlock()
return s.config return s.config
} }
// Copyright 2022 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0
package configuration
import (
"github.com/r3labs/diff/v3"
)
type ChangeEvent struct {
Changlog diff.Changelog
}
type ChangeHook interface {
Handle(event ChangeEvent)
}
// OnChange registers a hook that is called when the configuration changes.
func (s *Settings[C]) OnChange(hook ChangeHook) *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 ChangeHook) *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 ChangeHook) *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 {
go h.Handle(ChangeEvent{Changlog: changelog})
}
return s
}
...@@ -7,48 +7,6 @@ import ( ...@@ -7,48 +7,6 @@ import (
"github.com/r3labs/diff/v3" "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] { func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] {
var ( var (
...@@ -64,13 +22,23 @@ func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] { ...@@ -64,13 +22,23 @@ func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] {
if lock { if lock {
s.Unlock() s.Unlock()
} }
}()
defer func() {
if len(changelog) > 0 { if len(changelog) > 0 {
go s.notifyChangeHooks(changelog) s.notifyChangeHooks(changelog)
} }
}() }()
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if err := validateConfig[C](config); err != nil { if err := validateConfig[C](config); err != nil {
s.errors = append(s.errors, err) s.errors = append(s.errors, err)
return s return s
......
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
) )
type mockTestEventHandler struct { type mockTestEventHandler struct {
EventHook ChangeHook
} }
func (m *mockTestEventHandler) Handle(event ChangeEvent) { func (m *mockTestEventHandler) Handle(event ChangeEvent) {
...@@ -30,7 +30,7 @@ func TestAddRemoveHook(t *testing.T) { ...@@ -30,7 +30,7 @@ func TestAddRemoveHook(t *testing.T) {
s := New(config) s := New(config)
var h EventHook var h ChangeHook
h = &mockTestEventHandler{} h = &mockTestEventHandler{}
s.OnChange(h) s.OnChange(h)
...@@ -46,14 +46,6 @@ func TestAddRemoveHook(t *testing.T) { ...@@ -46,14 +46,6 @@ func TestAddRemoveHook(t *testing.T) {
} }
type ChangeEventTester struct {
callback func(event ChangeEvent)
}
func (c *ChangeEventTester) Handle(event ChangeEvent) {
c.callback(event)
}
func TestReadmeExample(t *testing.T) { func TestReadmeExample(t *testing.T) {
config := struct { config := struct {
...@@ -68,9 +60,9 @@ func TestReadmeExample(t *testing.T) { ...@@ -68,9 +60,9 @@ func TestReadmeExample(t *testing.T) {
msg := "" msg := ""
var h EventHook var h ChangeHook
h = &ChangeEventTester{ h = &ChangeEventHandler{
callback: func(event ChangeEvent) { Callback: func(event ChangeEvent) {
log := event.Changlog log := event.Changlog
msg = fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To) msg = fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To)
// for Readme // for Readme
...@@ -134,9 +126,9 @@ func TestCangeOnChange(t *testing.T) { ...@@ -134,9 +126,9 @@ func TestCangeOnChange(t *testing.T) {
counter := 0 counter := 0
var h EventHook var h ChangeHook
h = &ChangeEventTester{ h = &ChangeEventHandler{
callback: func(event ChangeEvent) { Callback: func(event ChangeEvent) {
counter++ counter++
closeChan <- true closeChan <- true
}, },
......
...@@ -12,6 +12,13 @@ func (s *Settings[C]) Copy(m map[string]any) { ...@@ -12,6 +12,13 @@ func (s *Settings[C]) Copy(m map[string]any) {
c := s.Config() c := s.Config()
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
for k, v := range m { for k, v := range m {
err := pathfinder.SetValue(&c, k, v) err := pathfinder.SetValue(&c, k, v)
if err != nil { if err != nil {
......
...@@ -14,6 +14,13 @@ func (s *Settings[C]) InitFromEnv(prefix string) *Settings[C] { ...@@ -14,6 +14,13 @@ func (s *Settings[C]) InitFromEnv(prefix string) *Settings[C] {
s.Lock() s.Lock()
defer s.Unlock() 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) { err := runOnTags(&s.config, []string{envTagKey}, func(k string, field reflect.Value) {
if !field.CanSet() { if !field.CanSet() {
......
// Copyright 2022 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0
package configuration
type ErrorEvent struct {
}
type ErrorHook interface {
Handle(event ErrorEvent)
}
// OnChange registers a hook that is called when the configuration changes.
func (s *Settings[C]) OnError(hook ErrorHook) *Settings[C] {
s.hooks.error = append(s.hooks.error, hook)
return s
}
// HasOnChangeHook returns true if there are registered hooks.
func (s *Settings[C]) HasOnErrorHook(hook ErrorHook) *Settings[C] {
for _, h := range s.hooks.error {
if h == hook {
break
}
}
return s
}
// RemoveOnChangeHook removes a change hook from the list of hooks.
func (s *Settings[C]) RemoveOnErrorHook(hook ErrorHook) *Settings[C] {
for i, h := range s.hooks.error {
if h == hook {
s.hooks.error = append(s.hooks.error[:i], s.hooks.error[i+1:]...)
break
}
}
return s
}
func (s *Settings[C]) notifyErrorHooks() *Settings[C] {
for _, h := range s.hooks.error {
go h.Handle(ErrorEvent{})
}
return s
}
// Copyright 2022 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0
package configuration
import (
"github.com/stretchr/testify/assert"
"testing"
"time"
)
type mockTestEventErrorHandler struct {
ErrorHook
ch chan bool
}
func (m *mockTestEventErrorHandler) Handle(event ErrorEvent) {
// do nothing
m.ch <- true
}
func TestErrorHandling(t *testing.T) {
defaults := ConfigStruct2{
A: "Hello!",
}
c := New(defaults)
var h ErrorHook
h = &mockTestEventErrorHandler{
ch: make(chan bool),
}
c.OnError(h)
c.SetDefaultDirectories()
select {
case <-h.(*mockTestEventErrorHandler).ch:
assert.True(t, true)
case <-time.After(11 * time.Second):
t.Error("Timeout, expected error event")
}
}
func TestAddRemoveErrorHook(t *testing.T) {
config := struct {
Host string
}{
Host: "localhost",
}
s := New(config)
var h ErrorHook
h = &mockTestEventErrorHandler{}
s.OnError(h)
if len(s.hooks.error) != 1 {
t.Error("Expected 1 got ", len(s.hooks.change))
}
s.RemoveOnErrorHook(h)
if len(s.hooks.error) != 0 {
t.Error("Expected 0 got ", len(s.hooks.change))
}
}
...@@ -34,6 +34,13 @@ func (s *Settings[C]) writeProperties(writer io.Writer) error { ...@@ -34,6 +34,13 @@ func (s *Settings[C]) writeProperties(writer io.Writer) error {
m, errors := getMapForProperties[C](s.config) m, errors := getMapForProperties[C](s.config)
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if len(errors) > 0 { if len(errors) > 0 {
for _, err := range errors { for _, err := range errors {
s.errors = append(s.errors, err) s.errors = append(s.errors, err)
...@@ -43,6 +50,7 @@ func (s *Settings[C]) writeProperties(writer io.Writer) error { ...@@ -43,6 +50,7 @@ func (s *Settings[C]) writeProperties(writer io.Writer) error {
p := properties.LoadMap(m) p := properties.LoadMap(m)
_, err := p.Write(writer, properties.UTF8) _, err := p.Write(writer, properties.UTF8)
return err return err
} }
...@@ -53,6 +61,13 @@ func (s *Settings[C]) WriteFile(fn string, format Format) *Settings[C] { ...@@ -53,6 +61,13 @@ func (s *Settings[C]) WriteFile(fn string, format Format) *Settings[C] {
var file *os.File var file *os.File
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if fn == "" { if fn == "" {
file = os.Stdout file = os.Stdout
} else { } else {
...@@ -65,6 +80,7 @@ func (s *Settings[C]) WriteFile(fn string, format Format) *Settings[C] { ...@@ -65,6 +80,7 @@ func (s *Settings[C]) WriteFile(fn string, format Format) *Settings[C] {
defer file.Close() defer file.Close()
s.Write(io.Writer(file), format) s.Write(io.Writer(file), format)
return s return s
} }
...@@ -73,6 +89,13 @@ func (s *Settings[C]) Write(writer io.Writer, format Format) *Settings[C] { ...@@ -73,6 +89,13 @@ func (s *Settings[C]) Write(writer io.Writer, format Format) *Settings[C] {
var err error var err error
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
switch format { switch format {
case Json: case Json:
err = s.writeJson(writer) err = s.writeJson(writer)
......
...@@ -38,6 +38,13 @@ func (s *Settings[C]) AddFile(file string, format ...Format) *Settings[C] { ...@@ -38,6 +38,13 @@ func (s *Settings[C]) AddFile(file string, format ...Format) *Settings[C] {
var f Format var f Format
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if format == nil || len(format) == 0 { if format == nil || len(format) == 0 {
f = RecogniseFormat f = RecogniseFormat
} else if format != nil && len(format) == 1 { } else if format != nil && len(format) == 1 {
...@@ -104,6 +111,13 @@ func (s *Settings[C]) AddDirectory(d string) *Settings[C] { ...@@ -104,6 +111,13 @@ func (s *Settings[C]) AddDirectory(d string) *Settings[C] {
func (s *Settings[C]) sanitizeDirectories() { func (s *Settings[C]) sanitizeDirectories() {
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
s.errors = append(s.errors, err) s.errors = append(s.errors, err)
...@@ -146,9 +160,17 @@ func (s *Settings[C]) AddWorkingDirectory() *Settings[C] { ...@@ -146,9 +160,17 @@ func (s *Settings[C]) AddWorkingDirectory() *Settings[C] {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
current, err := os.Getwd() current, err := os.Getwd()
if err != nil { if err != nil {
s.errors = append(s.errors, err) s.errors = append(s.errors, err)
return s return s
} }
s.files.directories = append(s.files.directories, current) s.files.directories = append(s.files.directories, current)
...@@ -161,6 +183,13 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] { ...@@ -161,6 +183,13 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if s.mnemonic == "" { if s.mnemonic == "" {
s.errors = append(s.errors, MnemonicEmptyError) s.errors = append(s.errors, MnemonicEmptyError)
return s return s
...@@ -183,14 +212,21 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] { ...@@ -183,14 +212,21 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] {
// The mnemonic must be set for this function // The mnemonic must be set for this function
func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] { 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 == "" { if s.mnemonic == "" {
s.errors = append(s.errors, MnemonicEmptyError) s.errors = append(s.errors, MnemonicEmptyError)
return s return s
} }
s.Lock()
defer s.Unlock()
current, err := os.UserConfigDir() current, err := os.UserConfigDir()
if err != nil { if err != nil {
s.errors = append(s.errors, err) s.errors = append(s.errors, err)
...@@ -214,6 +250,13 @@ func (s *Settings[C]) SetDefaultDirectories() *Settings[C] { ...@@ -214,6 +250,13 @@ func (s *Settings[C]) SetDefaultDirectories() *Settings[C] {
func (s *Settings[C]) SetFileFormat(format Format) *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) { if slices.Contains(availableFormats, format) {
s.files.format = format s.files.format = format
} else { } else {
...@@ -226,6 +269,13 @@ func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] { ...@@ -226,6 +269,13 @@ func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] {
// Set the file name without extension // Set the file name without extension
func (s *Settings[C]) SetFileName(name string) *Settings[C] { func (s *Settings[C]) SetFileName(name string) *Settings[C] {
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if name == "" { if name == "" {
s.errors = append(s.errors, FileNameEmptyError) s.errors = append(s.errors, FileNameEmptyError)
} else { } else {
......
...@@ -13,6 +13,13 @@ import ( ...@@ -13,6 +13,13 @@ import (
// The file is read from the flag specified by the name // 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] { 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) flag := flagset.Lookup(name)
if flag == nil { if flag == nil {
s.errors = append(s.errors, newFlagDoesNotExistError(name)) s.errors = append(s.errors, newFlagDoesNotExistError(name))
...@@ -33,6 +40,13 @@ func (s *Settings[C]) InitFromFlagSet(flagset *flag.FlagSet) *Settings[C] { ...@@ -33,6 +40,13 @@ func (s *Settings[C]) InitFromFlagSet(flagset *flag.FlagSet) *Settings[C] {
s.Lock() s.Lock()
defer s.Unlock() 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) { err := runOnTags(&s.config, []string{flagTagKey}, func(k string, field reflect.Value) {
flag := flagset.Lookup(k) flag := flagset.Lookup(k)
......
// Copyright 2022 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0
package configuration
type ChangeEventHandler struct {
Callback func(event ChangeEvent)
}
func (c *ChangeEventHandler) Handle(event ChangeEvent) {
c.Callback(event)
}
type ErrorEventHandler struct {
Callback func(event ErrorEvent)
}
func (c *ErrorEventHandler) Handle(event ErrorEvent) {
c.Callback(event)
}
...@@ -272,9 +272,9 @@ func TestConfigurationServePostJson(t *testing.T) { ...@@ -272,9 +272,9 @@ func TestConfigurationServePostJson(t *testing.T) {
closeChan := make(chan bool) closeChan := make(chan bool)
counter := 0 counter := 0
var h EventHook var h ChangeHook
h = &ChangeEventTester{ h = &ChangeEventHandler{
callback: func(event ChangeEvent) { Callback: func(event ChangeEvent) {
counter++ counter++
closeChan <- true closeChan <- true
}, },
......
...@@ -6,6 +6,7 @@ package configuration ...@@ -6,6 +6,7 @@ package configuration
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"github.com/magiconair/properties" "github.com/magiconair/properties"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
...@@ -49,6 +50,13 @@ func (s *Settings[C]) importStream(r reader) { ...@@ -49,6 +50,13 @@ func (s *Settings[C]) importStream(r reader) {
var c C var c C
var err error var err error
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
err = nil // reset error err = nil // reset error
x := r.reader x := r.reader
...@@ -85,10 +93,21 @@ func (s *Settings[C]) importStreams() { ...@@ -85,10 +93,21 @@ func (s *Settings[C]) importStreams() {
func (s *Settings[C]) importFiles() { func (s *Settings[C]) importFiles() {
s.fileWatch.Lock() s.fileWatch.Lock()
defer s.fileWatch.Unlock()
// new files may have been added // new files may have been added
s.fileWatch.watchList = make(map[string]string) tmpWatchList := make(map[string]string)
defer func() {
s.fileWatch.watchList = tmpWatchList
s.fileWatch.Unlock()
}()
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
for _, d := range s.files.directories { for _, d := range s.files.directories {
fn := path.Join(d, s.files.name+s.files.format.Extension()) fn := path.Join(d, s.files.name+s.files.format.Extension())
...@@ -104,7 +123,7 @@ func (s *Settings[C]) importFiles() { ...@@ -104,7 +123,7 @@ func (s *Settings[C]) importFiles() {
s.importStream(reader{s.files.format, r}) s.importStream(reader{s.files.format, r})
f.Close() f.Close()
s.fileWatch.watchList[fn] = fn tmpWatchList[fn] = fn
} }
...@@ -117,8 +136,9 @@ func (s *Settings[C]) importFiles() { ...@@ -117,8 +136,9 @@ func (s *Settings[C]) importFiles() {
continue continue
} }
s.importStream(reader{f.format, r}) s.importStream(reader{f.format, r})
r.Close() r.Close()
s.fileWatch.watchList[f.path] = f.path tmpWatchList[f.path] = f.path
} }
} }
...@@ -128,6 +148,13 @@ func (s *Settings[C]) Import() *Settings[C] { ...@@ -128,6 +148,13 @@ func (s *Settings[C]) Import() *Settings[C] {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
defaults := s.config defaults := s.config
var n C var n C
......
{"version":"1.10.0"} {"version":"1.11.0"}
...@@ -28,7 +28,8 @@ type Settings[C any] struct { ...@@ -28,7 +28,8 @@ type Settings[C any] struct {
mnemonic string mnemonic string
importCounter int importCounter int
hooks struct { hooks struct {
change []EventHook change []ChangeHook
error []ErrorHook
} }
fileWatch fileWatch fileWatch fileWatch
...@@ -36,6 +37,13 @@ type Settings[C any] struct { ...@@ -36,6 +37,13 @@ type Settings[C any] struct {
func (s *Settings[C]) initDefaults() *Settings[C] { 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) { err := runOnTags(&s.config, []string{"default"}, func(v string, field reflect.Value) {
if field.CanSet() { if field.CanSet() {
......
...@@ -11,6 +11,13 @@ func (s *Settings[C]) initWatch() *Settings[C] { ...@@ -11,6 +11,13 @@ func (s *Settings[C]) initWatch() *Settings[C] {
var err error var err error
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if s.fileWatch.watcher != nil { if s.fileWatch.watcher != nil {
s.errors = append(s.errors, WatchAlreadyInitializedError) s.errors = append(s.errors, WatchAlreadyInitializedError)
return s return s
...@@ -31,6 +38,13 @@ func (s *Settings[C]) StopWatching() *Settings[C] { ...@@ -31,6 +38,13 @@ func (s *Settings[C]) StopWatching() *Settings[C] {
s.fileWatch.Lock() s.fileWatch.Lock()
defer s.fileWatch.Unlock() defer s.fileWatch.Unlock()
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if s.fileWatch.watcher == nil { if s.fileWatch.watcher == nil {
s.errors = append(s.errors, WatchNotInitializedError) s.errors = append(s.errors, WatchNotInitializedError)
return s return s
...@@ -52,6 +66,13 @@ func (s *Settings[C]) Watch() *Settings[C] { ...@@ -52,6 +66,13 @@ func (s *Settings[C]) Watch() *Settings[C] {
s.fileWatch.Lock() s.fileWatch.Lock()
defer s.fileWatch.Unlock() defer s.fileWatch.Unlock()
errorCount := len(s.errors)
defer func() {
if len(s.errors) > errorCount {
s.notifyErrorHooks()
}
}()
if s.fileWatch.watcher == nil { if s.fileWatch.watcher == nil {
s.initWatch() s.initWatch()
} }
......
...@@ -10,6 +10,135 @@ import ( ...@@ -10,6 +10,135 @@ import (
"time" "time"
) )
func runTestFilesChange(fn string, data []testHostTimeout, t *testing.T) {
for _, x := range data {
h := x.host
b := []byte("Host: \"" + h + "\"")
err := os.WriteFile(fn, b, 0644)
if err != nil {
t.Error(err)
return
}
time.Sleep(x.timeout)
}
}
func createTestFileForWatch1() (string, error) {
f, err := os.CreateTemp("", "watch_test")
if err != nil {
return "", err
}
f.WriteString("Host: \"127.0.0.1\"")
f.Close()
return f.Name(), nil
}
type testHostTimeout struct {
host string
timeout time.Duration
}
func TestMultiChange(t *testing.T) {
tmpFn, err := createTestFileForWatch1()
if err != nil {
t.Error(err)
return
}
defer os.Remove(tmpFn)
config := struct {
Host string `yaml:"Host"`
}{
Host: "localhost",
}
c := New(config)
c.SetMnemonic("my-app")
assert.Equal(t, c.Config().Host, "localhost")
result := []string{}
signal := make(chan string)
var h ChangeHook
h = &ChangeEventHandler{
Callback: func(event ChangeEvent) {
result = append(result, event.Changlog[0].To.(string))
signal <- event.Changlog[0].To.(string)
},
}
var e ErrorHook
e = &ErrorEventHandler{
Callback: func(event ErrorEvent) {
//for _, err := range c.Errors() {
// t.Error(err)
//}
},
}
c.AddFile(tmpFn, Yaml)
c.Import()
if c.HasErrors() {
t.Error(c.Errors())
}
data := []testHostTimeout{
{
host: "1.org",
timeout: time.Millisecond * 100,
},
{
host: "2.org",
timeout: time.Millisecond * 10,
},
{
host: "3.org",
timeout: time.Millisecond * 2,
},
{
host: "4.org",
timeout: time.Millisecond * 100,
},
{
host: "9.org",
timeout: time.Millisecond * 100,
},
}
c.OnChange(h).OnError(e).Watch()
go runTestFilesChange(tmpFn, data, t)
for loop := true; loop; {
select {
case <-signal:
if len(result) == len(data) {
loop = false
break
}
case <-time.After(time.Second * 10):
t.Log(result)
t.Fatalf("Timeout")
}
}
assert.Equal(t, "9.org", c.Config().Host)
}
func TestWatch(t *testing.T) { func TestWatch(t *testing.T) {
f, err := os.CreateTemp("", "watch_test") f, err := os.CreateTemp("", "watch_test")
...@@ -35,9 +164,9 @@ func TestWatch(t *testing.T) { ...@@ -35,9 +164,9 @@ func TestWatch(t *testing.T) {
signal := make(chan bool) signal := make(chan bool)
var h EventHook var h ChangeHook
h = &ChangeEventTester{ h = &ChangeEventHandler{
callback: func(event ChangeEvent) { Callback: func(event ChangeEvent) {
assert.Equal(t, event.Changlog[0].From, "localhost") assert.Equal(t, event.Changlog[0].From, "localhost")
assert.Equal(t, event.Changlog[0].To, "example.org") assert.Equal(t, event.Changlog[0].To, "example.org")
signal <- true signal <- true
......