mirror of
https://github.com/pocketbase/pocketbase.git
synced 2024-11-28 10:03:42 +02:00
added option to remove single registered hook handler
This commit is contained in:
parent
97f29e4305
commit
9d8df8d05d
@ -4,6 +4,10 @@
|
||||
|
||||
- Added VK OAuth2 provider ([#2533](https://github.com/pocketbase/pocketbase/pull/2533); thanks @imperatrona).
|
||||
|
||||
- Renamed `Hook.Reset()` -> `Hook.RemoveAll()`.
|
||||
|
||||
- `Hook.Add()` and `Hook.PreAdd` now returns a unique string identifier that could be used to remove the registered hook handler via `Hook.Remove(handlerId)`.
|
||||
|
||||
|
||||
## v0.16.4-WIP
|
||||
|
||||
|
@ -2,7 +2,10 @@ package hook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
var StopPropagation = errors.New("Event hook propagation stopped")
|
||||
@ -10,36 +13,65 @@ var StopPropagation = errors.New("Event hook propagation stopped")
|
||||
// Handler defines a hook handler function.
|
||||
type Handler[T any] func(e T) error
|
||||
|
||||
// handlerPair defines a pair of string id and Handler.
|
||||
type handlerPair[T any] struct {
|
||||
id string
|
||||
handler Handler[T]
|
||||
}
|
||||
|
||||
// Hook defines a concurrent safe structure for handling event hooks
|
||||
// (aka. callbacks propagation).
|
||||
type Hook[T any] struct {
|
||||
mux sync.RWMutex
|
||||
handlers []Handler[T]
|
||||
handlers []*handlerPair[T]
|
||||
}
|
||||
|
||||
// PreAdd registers a new handler to the hook by prepending it to the existing queue.
|
||||
func (h *Hook[T]) PreAdd(fn Handler[T]) {
|
||||
//
|
||||
// Returns an autogenerated hook id that could be used later to remove the hook with Hook.Remove(id).
|
||||
func (h *Hook[T]) PreAdd(fn Handler[T]) string {
|
||||
h.mux.Lock()
|
||||
defer h.mux.Unlock()
|
||||
|
||||
id := generateHookId()
|
||||
|
||||
// minimize allocations by shifting the slice
|
||||
h.handlers = append(h.handlers, nil)
|
||||
copy(h.handlers[1:], h.handlers)
|
||||
h.handlers[0] = fn
|
||||
h.handlers[0] = &handlerPair[T]{id, fn}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Add registers a new handler to the hook by appending it to the existing queue.
|
||||
func (h *Hook[T]) Add(fn Handler[T]) {
|
||||
//
|
||||
// Returns an autogenerated hook id that could be used later to remove the hook with Hook.Remove(id).
|
||||
func (h *Hook[T]) Add(fn Handler[T]) string {
|
||||
h.mux.Lock()
|
||||
defer h.mux.Unlock()
|
||||
|
||||
h.handlers = append(h.handlers, fn)
|
||||
id := generateHookId()
|
||||
|
||||
h.handlers = append(h.handlers, &handlerPair[T]{id, fn})
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Reset removes all registered handlers.
|
||||
//
|
||||
// @todo for consistency with other Go methods consider renaming it to Clear.
|
||||
func (h *Hook[T]) Reset() {
|
||||
// Remove removes a single hook handler by its id.
|
||||
func (h *Hook[T]) Remove(id string) {
|
||||
h.mux.Lock()
|
||||
defer h.mux.Unlock()
|
||||
|
||||
for i := len(h.handlers) - 1; i >= 0; i-- {
|
||||
if h.handlers[i].id == id {
|
||||
h.handlers = append(h.handlers[:i], h.handlers[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAll removes all registered handlers.
|
||||
func (h *Hook[T]) RemoveAll() {
|
||||
h.mux.Lock()
|
||||
defer h.mux.Unlock()
|
||||
|
||||
@ -57,14 +89,24 @@ func (h *Hook[T]) Reset() {
|
||||
// - any non-nil error is returned in one of the handlers
|
||||
func (h *Hook[T]) Trigger(data T, oneOffHandlers ...Handler[T]) error {
|
||||
h.mux.RLock()
|
||||
handlers := make([]Handler[T], 0, len(h.handlers)+len(oneOffHandlers))
|
||||
|
||||
handlers := make([]*handlerPair[T], 0, len(h.handlers)+len(oneOffHandlers))
|
||||
handlers = append(handlers, h.handlers...)
|
||||
handlers = append(handlers, oneOffHandlers...)
|
||||
// unlock is not deferred to avoid deadlocks when Trigger is called recursive by the handlers
|
||||
|
||||
// append the one off handlers
|
||||
for i, oneOff := range oneOffHandlers {
|
||||
handlers = append(handlers, &handlerPair[T]{
|
||||
id: fmt.Sprintf("@%d", i),
|
||||
handler: oneOff,
|
||||
})
|
||||
}
|
||||
|
||||
// unlock is not deferred to avoid deadlocks in case Trigger
|
||||
// is called recursively by the handlers
|
||||
h.mux.RUnlock()
|
||||
|
||||
for _, fn := range handlers {
|
||||
err := fn(data)
|
||||
for _, item := range handlers {
|
||||
err := item.handler(data)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
@ -78,3 +120,7 @@ func (h *Hook[T]) Trigger(data T, oneOffHandlers ...Handler[T]) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateHookId() string {
|
||||
return security.PseudorandomString(8)
|
||||
}
|
||||
|
@ -36,22 +36,56 @@ func TestHookAddAndPreAdd(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookReset(t *testing.T) {
|
||||
func TestHookRemove(t *testing.T) {
|
||||
h := Hook[int]{}
|
||||
|
||||
h.Reset() // should do nothing and not panic
|
||||
h1Called := false
|
||||
h2Called := false
|
||||
|
||||
id1 := h.Add(func(data int) error { h1Called = true; return nil })
|
||||
h.Add(func(data int) error { h2Called = true; return nil })
|
||||
|
||||
h.Remove("missing") // should do nothing and not panic
|
||||
|
||||
if total := len(h.handlers); total != 2 {
|
||||
t.Fatalf("Expected %d handlers, got %d", 2, total)
|
||||
}
|
||||
|
||||
h.Remove(id1)
|
||||
|
||||
if total := len(h.handlers); total != 1 {
|
||||
t.Fatalf("Expected %d handlers, got %d", 1, total)
|
||||
}
|
||||
|
||||
if err := h.Trigger(1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if h1Called {
|
||||
t.Fatalf("Expected hook 1 to be removed and not called")
|
||||
}
|
||||
|
||||
if !h2Called {
|
||||
t.Fatalf("Expected hook 2 to be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRemoveAll(t *testing.T) {
|
||||
h := Hook[int]{}
|
||||
|
||||
h.RemoveAll() // 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)
|
||||
t.Fatalf("Expected 2 handlers before RemoveAll, found %d", total)
|
||||
}
|
||||
|
||||
h.Reset()
|
||||
h.RemoveAll()
|
||||
|
||||
if total := len(h.handlers); total != 0 {
|
||||
t.Fatalf("Expected no handlers after Reset, found %d", total)
|
||||
t.Fatalf("Expected no handlers after RemoveAll, found %d", total)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user