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