1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-18 01:57:02 +00:00

extension: Implement an EventBroker (#315)

Signed-off-by: James Hillyerd <james@hillyerd.com>

Signed-off-by: James Hillyerd <james@hillyerd.com>
This commit is contained in:
James Hillyerd
2023-01-16 20:34:16 -08:00
committed by GitHub
parent 37806f222d
commit 3bf4b5c39b
2 changed files with 191 additions and 0 deletions

57
pkg/extension/broker.go Normal file
View File

@@ -0,0 +1,57 @@
package extension
import "sync"
// EventBroker maintains a list of listeners interested in a specific type
// of event.
type EventBroker[E any, R comparable] struct {
sync.RWMutex
listenerNames []string // Ordered listener names.
listenerFuncs []func(E) *R // Ordered listener functions.
}
// Emit sends the provided event to each registered listener in order, until
// one returns a non-nil result. That result will be returned to the caller.
func (eb *EventBroker[E, R]) Emit(event *E) *R {
eb.RLock()
defer eb.RUnlock()
for _, l := range eb.listenerFuncs {
// Events are copied to minimize the risk of mutation.
if result := l(*event); result != nil {
return result
}
}
return nil
}
// AddListener registers the named listener, replacing one with a duplicate
// name if present. Listeners should be added in order of priority, most
// significant first.
func (eb *EventBroker[E, R]) AddListener(name string, listener func(E) *R) {
eb.Lock()
defer eb.Unlock()
eb.lockedRemoveListener(name)
eb.listenerNames = append(eb.listenerNames, name)
eb.listenerFuncs = append(eb.listenerFuncs, listener)
}
// RemoveListener unregisters the named listener.
func (eb *EventBroker[E, R]) RemoveListener(name string) {
eb.Lock()
defer eb.Unlock()
eb.lockedRemoveListener(name)
}
func (eb *EventBroker[E, R]) lockedRemoveListener(name string) {
for i, entry := range eb.listenerNames {
if entry == name {
eb.listenerNames = append(eb.listenerNames[:i], eb.listenerNames[i+1:]...)
eb.listenerFuncs = append(eb.listenerFuncs[:i], eb.listenerFuncs[i+1:]...)
break
}
}
}

View File

@@ -0,0 +1,134 @@
package extension_test
import (
"testing"
"github.com/inbucket/inbucket/pkg/extension"
)
func testBrokerEmitCallsOneListener(t *testing.T) {
broker := &extension.EventBroker[string, bool]{}
// Setup listener.
var got string
listener := func(s string) *bool {
got = s
return nil
}
broker.AddListener("x", listener)
want := "bacon"
broker.Emit(&want)
if got != want {
t.Errorf("Emit got %q, want %q", got, want)
}
}
func testBrokerEmitCallsMultipleListeners(t *testing.T) {
broker := &extension.EventBroker[string, bool]{}
// Setup listeners.
var first_got, second_got string
first := func(s string) *bool {
first_got = s
return nil
}
second := func(s string) *bool {
second_got = s
return nil
}
broker.AddListener("1", first)
broker.AddListener("2", second)
want := "hi"
broker.Emit(&want)
if first_got != want {
t.Errorf("first got %q, want %q", first_got, want)
}
if second_got != want {
t.Errorf("second got %q, want %q", second_got, want)
}
}
func testBrokerEmitCapturesFirstResult(t *testing.T) {
broker := &extension.EventBroker[struct{}, string]{}
// Setup listeners.
makeListener := func(result *string) func(struct{}) *string {
return func(s struct{}) *string { return result }
}
first := "first"
second := "second"
broker.AddListener("0", makeListener(nil))
broker.AddListener("1", makeListener(&first))
broker.AddListener("2", makeListener(&second))
want := first
got := broker.Emit(&struct{}{})
if got == nil {
t.Errorf("Emit got nil, want %q", want)
} else if *got != want {
t.Errorf("Emit got %q, want %q", *got, want)
}
}
func testBrokerAddingDuplicateNameReplacesPrevious(t *testing.T) {
broker := &extension.EventBroker[string, bool]{}
// Setup listeners.
var first_got, second_got string
first := func(s string) *bool {
first_got = s
return nil
}
second := func(s string) *bool {
second_got = s
return nil
}
broker.AddListener("dup", first)
broker.AddListener("dup", second)
want := "hi"
broker.Emit(&want)
if first_got != "" {
t.Errorf("first got %q, want empty string", first_got)
}
if second_got != want {
t.Errorf("second got %q, want %q", second_got, want)
}
}
func testBrokerRemovingListenerSuccessful(t *testing.T) {
broker := &extension.EventBroker[string, bool]{}
// Setup listeners.
var first_got, second_got string
first := func(s string) *bool {
first_got = s
return nil
}
second := func(s string) *bool {
second_got = s
return nil
}
broker.AddListener("1", first)
broker.AddListener("2", second)
broker.RemoveListener("1")
want := "hi"
broker.Emit(&want)
if first_got != "" {
t.Errorf("first got %q, want empty string", first_got)
}
if second_got != want {
t.Errorf("second got %q, want %q", second_got, want)
}
}
func testBrokerRemovingMissingListener(t *testing.T) {
broker := &extension.EventBroker[string, bool]{}
broker.RemoveListener("doesn't crash")
}