From 4fec023952c6099ae8f5bebd9ef8af9bb19762d7 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Sun, 23 Oct 2022 09:47:41 +0200 Subject: [PATCH] feat error handler and changed update handler --- change-handler.go | 50 ++++++++++++++++ change.go | 47 ++------------- change_test.go | 8 +-- error-handler.go | 86 +++++---------------------- error-handler_test.go | 68 ++++++++++++++++++++++ export.go | 2 + file.go | 3 - handler.go | 8 +++ http-handler_test.go | 2 +- import.go | 1 + settings.go | 3 +- watch_test.go | 131 +++++++++++++++++++++++++++++++++++++++++- 12 files changed, 283 insertions(+), 126 deletions(-) diff --git a/change-handler.go b/change-handler.go index e69de29..d6e66fc 100644 --- a/change-handler.go +++ b/change-handler.go @@ -0,0 +1,50 @@ +// 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 +} diff --git a/change.go b/change.go index cb083d7..c3000e4 100644 --- a/change.go +++ b/change.go @@ -7,48 +7,6 @@ import ( "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 ( @@ -64,9 +22,12 @@ func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] { if lock { s.Unlock() } + }() + + defer func() { if len(changelog) > 0 { - go s.notifyChangeHooks(changelog) + s.notifyChangeHooks(changelog) } }() diff --git a/change_test.go b/change_test.go index 0e8616f..6dea9f6 100644 --- a/change_test.go +++ b/change_test.go @@ -12,7 +12,7 @@ import ( ) type mockTestEventHandler struct { - EventHook + ChangeHook } func (m *mockTestEventHandler) Handle(event ChangeEvent) { @@ -30,7 +30,7 @@ func TestAddRemoveHook(t *testing.T) { s := New(config) - var h EventHook + var h ChangeHook h = &mockTestEventHandler{} s.OnChange(h) @@ -60,7 +60,7 @@ func TestReadmeExample(t *testing.T) { msg := "" - var h EventHook + var h ChangeHook h = &ChangeEventHandler{ Callback: func(event ChangeEvent) { log := event.Changlog @@ -126,7 +126,7 @@ func TestCangeOnChange(t *testing.T) { counter := 0 - var h EventHook + var h ChangeHook h = &ChangeEventHandler{ Callback: func(event ChangeEvent) { counter++ diff --git a/error-handler.go b/error-handler.go index 91f3ae4..5eb3752 100644 --- a/error-handler.go +++ b/error-handler.go @@ -3,28 +3,22 @@ package configuration -import ( - "fmt" - "github.com/r3labs/diff/v3" -) - -type ChangeEvent struct { - Changlog diff.Changelog +type ErrorEvent struct { } -type EventHook interface { - Handle(event ChangeEvent) +type ErrorHook interface { + Handle(event ErrorEvent) } // 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) +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]) HasOnChangeHook(hook EventHook) *Settings[C] { - for _, h := range s.hooks.change { +func (s *Settings[C]) HasOnErrorHook(hook ErrorHook) *Settings[C] { + for _, h := range s.hooks.error { if h == hook { break } @@ -33,73 +27,19 @@ func (s *Settings[C]) HasOnChangeHook(hook EventHook) *Settings[C] { } // 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 { +func (s *Settings[C]) RemoveOnErrorHook(hook ErrorHook) *Settings[C] { + for i, h := range s.hooks.error { if h == hook { - s.hooks.change = append(s.hooks.change[:i], s.hooks.change[i+1:]...) + s.hooks.error = append(s.hooks.error[:i], s.hooks.error[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}) +func (s *Settings[C]) notifyErrorHooks() *Settings[C] { + for _, h := range s.hooks.error { + go h.Handle(ErrorEvent{}) } 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 index e69de29..96279ff 100644 --- a/error-handler_test.go +++ b/error-handler_test.go @@ -0,0 +1,68 @@ +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)) + } + +} diff --git a/export.go b/export.go index 0c57483..e1d9aba 100644 --- a/export.go +++ b/export.go @@ -50,6 +50,7 @@ func (s *Settings[C]) writeProperties(writer io.Writer) error { p := properties.LoadMap(m) _, err := p.Write(writer, properties.UTF8) + return err } @@ -79,6 +80,7 @@ func (s *Settings[C]) WriteFile(fn string, format Format) *Settings[C] { defer file.Close() s.Write(io.Writer(file), format) + return s } diff --git a/file.go b/file.go index 252529e..633ac37 100644 --- a/file.go +++ b/file.go @@ -227,9 +227,6 @@ func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] { return s } - s.Lock() - defer s.Unlock() - current, err := os.UserConfigDir() if err != nil { s.errors = append(s.errors, err) diff --git a/handler.go b/handler.go index eb9bece..20a59e2 100644 --- a/handler.go +++ b/handler.go @@ -7,3 +7,11 @@ type ChangeEventHandler struct { 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) +} diff --git a/http-handler_test.go b/http-handler_test.go index 61b46f6..12981d0 100644 --- a/http-handler_test.go +++ b/http-handler_test.go @@ -272,7 +272,7 @@ func TestConfigurationServePostJson(t *testing.T) { closeChan := make(chan bool) counter := 0 - var h EventHook + var h ChangeHook h = &ChangeEventHandler{ Callback: func(event ChangeEvent) { counter++ diff --git a/import.go b/import.go index 074a413..576b508 100644 --- a/import.go +++ b/import.go @@ -6,6 +6,7 @@ package configuration import ( "bytes" "encoding/json" + "github.com/imdario/mergo" "github.com/magiconair/properties" "github.com/pelletier/go-toml/v2" diff --git a/settings.go b/settings.go index 12d57bd..15f66f6 100644 --- a/settings.go +++ b/settings.go @@ -28,7 +28,8 @@ type Settings[C any] struct { mnemonic string importCounter int hooks struct { - change []EventHook + change []ChangeHook + error []ErrorHook } fileWatch fileWatch diff --git a/watch_test.go b/watch_test.go index e796abd..494970b 100644 --- a/watch_test.go +++ b/watch_test.go @@ -10,6 +10,135 @@ import ( "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) { f, err := os.CreateTemp("", "watch_test") @@ -35,7 +164,7 @@ func TestWatch(t *testing.T) { signal := make(chan bool) - var h EventHook + var h ChangeHook h = &ChangeEventHandler{ Callback: func(event ChangeEvent) { assert.Equal(t, event.Changlog[0].From, "localhost") -- GitLab