initial public commit

This commit is contained in:
Gani Georgiev
2022-07-07 00:19:05 +03:00
commit 3d07f0211d
484 changed files with 92412 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
package hook
import (
"errors"
"sync"
)
var StopPropagation = errors.New("Event hook propagation stopped")
// Handler defines a hook handler function.
type Handler[T any] func(data T) error
// Hook defines a concurrent safe structure for handling event hooks
// (aka. callbacks propagation).
type Hook[T any] struct {
mux sync.RWMutex
handlers []Handler[T]
}
// Add registers a new handler to the hook.
func (h *Hook[T]) Add(fn Handler[T]) {
h.mux.Lock()
defer h.mux.Unlock()
h.handlers = append(h.handlers, fn)
}
// Reset removes all registered handlers.
func (h *Hook[T]) Reset() {
h.mux.Lock()
defer h.mux.Unlock()
h.handlers = nil
}
// Trigger executes all registered hook handlers one by one
// with the specified `data` as an argument.
//
// Optionally, this method allows also to register additional one off
// handlers that will be temporary appended to the handlers queue.
//
// The execution stops when:
// - hook.StopPropagation is returned in one of the handlers
// - any non-nil error is returned in one of the handlers
func (h *Hook[T]) Trigger(data T, oneOffHandlers ...Handler[T]) error {
h.mux.Lock()
handlers := append(h.handlers, oneOffHandlers...)
h.mux.Unlock() // unlock is not deferred to avoid deadlocks when Trigger is called recursive in the handlers
for _, fn := range handlers {
err := fn(data)
if err == nil {
continue
}
if errors.Is(err, StopPropagation) {
return nil
}
return err
}
return nil
}
+129
View File
@@ -0,0 +1,129 @@
package hook
import (
"errors"
"testing"
)
func TestAdd(t *testing.T) {
h := Hook[int]{}
if total := len(h.handlers); total != 0 {
t.Fatalf("Expected no handlers, found %d", total)
}
h.Add(func(data int) error { return nil })
h.Add(func(data int) error { return nil })
if total := len(h.handlers); total != 2 {
t.Fatalf("Expected 2 handlers, found %d", total)
}
}
func TestReset(t *testing.T) {
h := Hook[int]{}
h.Reset() // should do nothing and not panic
h.Add(func(data int) error { return nil })
h.Add(func(data int) error { return nil })
if total := len(h.handlers); total != 2 {
t.Fatalf("Expected 2 handlers before Reset, found %d", total)
}
h.Reset()
if total := len(h.handlers); total != 0 {
t.Fatalf("Expected no handlers after Reset, found %d", total)
}
}
func TestTrigger(t *testing.T) {
err1 := errors.New("demo")
err2 := errors.New("demo")
scenarios := []struct {
handlers []Handler[int]
expectedError error
}{
{
[]Handler[int]{
func(data int) error { return nil },
func(data int) error { return nil },
},
nil,
},
{
[]Handler[int]{
func(data int) error { return nil },
func(data int) error { return err1 },
func(data int) error { return err2 },
},
err1,
},
}
for i, scenario := range scenarios {
h := Hook[int]{}
for _, handler := range scenario.handlers {
h.Add(handler)
}
result := h.Trigger(1)
if result != scenario.expectedError {
t.Fatalf("(%d) Expected %v, got %v", i, scenario.expectedError, result)
}
}
}
func TestTriggerStopPropagation(t *testing.T) {
called1 := false
f1 := func(data int) error { called1 = true; return nil }
called2 := false
f2 := func(data int) error { called2 = true; return nil }
called3 := false
f3 := func(data int) error { called3 = true; return nil }
called4 := false
f4 := func(data int) error { called4 = true; return StopPropagation }
called5 := false
f5 := func(data int) error { called5 = true; return nil }
called6 := false
f6 := func(data int) error { called6 = true; return nil }
h := Hook[int]{}
h.Add(f1)
h.Add(f2)
result := h.Trigger(123, f3, f4, f5, f6)
if result != nil {
t.Fatalf("Expected nil after StopPropagation, got %v", result)
}
// ensure that the trigger handler were not persisted
if total := len(h.handlers); total != 2 {
t.Fatalf("Expected 2 handlers, found %d", total)
}
scenarios := []struct {
called bool
expected bool
}{
{called1, true},
{called2, true},
{called3, true},
{called4, true}, // StopPropagation
{called5, false},
{called6, false},
}
for i, scenario := range scenarios {
if scenario.called != scenario.expected {
t.Errorf("(%d) Expected %v, got %v", i, scenario.expected, scenario.called)
}
}
}