mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-18 10:07: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:
57
pkg/extension/broker.go
Normal file
57
pkg/extension/broker.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
134
pkg/extension/broker_test.go
Normal file
134
pkg/extension/broker_test.go
Normal 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")
|
||||
}
|
||||
Reference in New Issue
Block a user