Skip to content
Snippets Groups Projects
Verified Commit 4fec0239 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

feat error handler and changed update handler

parent aedecfeb
No related branches found
No related tags found
No related merge requests found
// 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,9 +22,12 @@ func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] { ...@@ -64,9 +22,12 @@ 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)
} }
}() }()
......
...@@ -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)
...@@ -60,7 +60,7 @@ func TestReadmeExample(t *testing.T) { ...@@ -60,7 +60,7 @@ func TestReadmeExample(t *testing.T) {
msg := "" msg := ""
var h EventHook var h ChangeHook
h = &ChangeEventHandler{ h = &ChangeEventHandler{
Callback: func(event ChangeEvent) { Callback: func(event ChangeEvent) {
log := event.Changlog log := event.Changlog
...@@ -126,7 +126,7 @@ func TestCangeOnChange(t *testing.T) { ...@@ -126,7 +126,7 @@ func TestCangeOnChange(t *testing.T) {
counter := 0 counter := 0
var h EventHook var h ChangeHook
h = &ChangeEventHandler{ h = &ChangeEventHandler{
Callback: func(event ChangeEvent) { Callback: func(event ChangeEvent) {
counter++ counter++
......
...@@ -3,28 +3,22 @@ ...@@ -3,28 +3,22 @@
package configuration package configuration
import ( type ErrorEvent struct {
"fmt"
"github.com/r3labs/diff/v3"
)
type ChangeEvent struct {
Changlog diff.Changelog
} }
type EventHook interface { type ErrorHook interface {
Handle(event ChangeEvent) Handle(event ErrorEvent)
} }
// OnChange registers a hook that is called when the configuration changes. // OnChange registers a hook that is called when the configuration changes.
func (s *Settings[C]) OnChange(hook EventHook) *Settings[C] { func (s *Settings[C]) OnError(hook ErrorHook) *Settings[C] {
s.hooks.change = append(s.hooks.change, hook) s.hooks.error = append(s.hooks.error, hook)
return s return s
} }
// HasOnChangeHook returns true if there are registered hooks. // HasOnChangeHook returns true if there are registered hooks.
func (s *Settings[C]) HasOnChangeHook(hook EventHook) *Settings[C] { func (s *Settings[C]) HasOnErrorHook(hook ErrorHook) *Settings[C] {
for _, h := range s.hooks.change { for _, h := range s.hooks.error {
if h == hook { if h == hook {
break break
} }
...@@ -33,73 +27,19 @@ func (s *Settings[C]) HasOnChangeHook(hook EventHook) *Settings[C] { ...@@ -33,73 +27,19 @@ func (s *Settings[C]) HasOnChangeHook(hook EventHook) *Settings[C] {
} }
// RemoveOnChangeHook removes a change hook from the list of hooks. // RemoveOnChangeHook removes a change hook from the list of hooks.
func (s *Settings[C]) RemoveOnChangeHook(hook EventHook) *Settings[C] { func (s *Settings[C]) RemoveOnErrorHook(hook ErrorHook) *Settings[C] {
for i, h := range s.hooks.change { for i, h := range s.hooks.error {
if h == hook { 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 break
} }
} }
return s return s
} }
func (s *Settings[C]) notifyChangeHooks(changelog diff.Changelog) *Settings[C] { func (s *Settings[C]) notifyErrorHooks() *Settings[C] {
for _, h := range s.hooks.change { for _, h := range s.hooks.error {
h.Handle(ChangeEvent{Changlog: changelog}) 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 return s
} }
func (s *Settings[C]) SetConfig(config C) *Settings[C] {
return s.setConfigInternal(config, true)
}
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))
}
}
...@@ -50,6 +50,7 @@ func (s *Settings[C]) writeProperties(writer io.Writer) error { ...@@ -50,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
} }
...@@ -79,6 +80,7 @@ func (s *Settings[C]) WriteFile(fn string, format Format) *Settings[C] { ...@@ -79,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
} }
......
...@@ -227,9 +227,6 @@ func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] { ...@@ -227,9 +227,6 @@ func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] {
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)
......
...@@ -7,3 +7,11 @@ type ChangeEventHandler struct { ...@@ -7,3 +7,11 @@ type ChangeEventHandler struct {
func (c *ChangeEventHandler) Handle(event ChangeEvent) { func (c *ChangeEventHandler) Handle(event ChangeEvent) {
c.Callback(event) c.Callback(event)
} }
type ErrorEventHandler struct {
Callback func(event ErrorEvent)
}
func (c *ErrorEventHandler) Handle(event ErrorEvent) {
c.Callback(event)
}
...@@ -272,7 +272,7 @@ func TestConfigurationServePostJson(t *testing.T) { ...@@ -272,7 +272,7 @@ func TestConfigurationServePostJson(t *testing.T) {
closeChan := make(chan bool) closeChan := make(chan bool)
counter := 0 counter := 0
var h EventHook var h ChangeHook
h = &ChangeEventHandler{ h = &ChangeEventHandler{
Callback: func(event ChangeEvent) { Callback: func(event ChangeEvent) {
counter++ counter++
......
...@@ -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"
......
...@@ -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
......
...@@ -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,7 +164,7 @@ func TestWatch(t *testing.T) { ...@@ -35,7 +164,7 @@ func TestWatch(t *testing.T) {
signal := make(chan bool) signal := make(chan bool)
var h EventHook var h ChangeHook
h = &ChangeEventHandler{ 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")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment