You've already forked pocketbase
mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-11-27 08:27:06 +02:00
merge v0.23.0-rc changes
This commit is contained in:
@@ -1,126 +1,179 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
var StopPropagation = errors.New("Event hook propagation stopped")
|
||||
// HandlerFunc defines a hook handler function.
|
||||
type HandlerFunc[T Resolver] func(e T) error
|
||||
|
||||
// Handler defines a hook handler function.
|
||||
type Handler[T any] func(e T) error
|
||||
// Handler defines a single Hook handler.
|
||||
// Multiple handlers can share the same id.
|
||||
// If Id is not explicitly set it will be autogenerated by Hook.Add and Hook.AddHandler.
|
||||
type Handler[T Resolver] struct {
|
||||
// Func defines the handler function to execute.
|
||||
//
|
||||
// Note that users need to call e.Next() in order to proceed with
|
||||
// the execution of the hook chain.
|
||||
Func HandlerFunc[T]
|
||||
|
||||
// handlerPair defines a pair of string id and Handler.
|
||||
type handlerPair[T any] struct {
|
||||
id string
|
||||
handler Handler[T]
|
||||
// Id is the unique identifier of the handler.
|
||||
//
|
||||
// It could be used later to remove the handler from a hook via [Hook.Remove].
|
||||
//
|
||||
// If missing, an autogenerated value will be assigned when adding
|
||||
// the handler to a hook.
|
||||
Id string
|
||||
|
||||
// Priority allows changing the default exec priority of the handler within a hook.
|
||||
//
|
||||
// If 0, the handler will be executed in the same order it was registered.
|
||||
Priority int
|
||||
}
|
||||
|
||||
// Hook defines a concurrent safe structure for handling event hooks
|
||||
// (aka. callbacks propagation).
|
||||
type Hook[T any] struct {
|
||||
mux sync.RWMutex
|
||||
handlers []*handlerPair[T]
|
||||
}
|
||||
|
||||
// PreAdd registers a new handler to the hook by prepending it to the existing queue.
|
||||
// Hook defines a generic concurrent safe structure for managing event hooks.
|
||||
//
|
||||
// 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] = &handlerPair[T]{id, fn}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Add registers a new handler to the hook by appending it to the existing queue.
|
||||
// When using custom a event it must embed the base [hook.Event].
|
||||
//
|
||||
// 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()
|
||||
|
||||
id := generateHookId()
|
||||
|
||||
h.handlers = append(h.handlers, &handlerPair[T]{id, fn})
|
||||
|
||||
return id
|
||||
// Example:
|
||||
//
|
||||
// type CustomEvent struct {
|
||||
// hook.Event
|
||||
// SomeField int
|
||||
// }
|
||||
//
|
||||
// h := Hook[*CustomEvent]{}
|
||||
//
|
||||
// h.BindFunc(func(e *CustomEvent) error {
|
||||
// println(e.SomeField)
|
||||
//
|
||||
// return e.Next()
|
||||
// })
|
||||
//
|
||||
// h.Trigger(&CustomEvent{ SomeField: 123 })
|
||||
type Hook[T Resolver] struct {
|
||||
handlers []*Handler[T]
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Remove removes a single hook handler by its id.
|
||||
func (h *Hook[T]) Remove(id string) {
|
||||
h.mux.Lock()
|
||||
defer h.mux.Unlock()
|
||||
// Bind registers the provided handler to the current hooks queue.
|
||||
//
|
||||
// If handler.Id is empty it is updated with autogenerated value.
|
||||
//
|
||||
// If a handler from the current hook list has Id matching handler.Id
|
||||
// then the old handler is replaced with the new one.
|
||||
func (h *Hook[T]) Bind(handler *Handler[T]) string {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
var exists bool
|
||||
|
||||
if handler.Id == "" {
|
||||
handler.Id = generateHookId()
|
||||
|
||||
// ensure that it doesn't exist
|
||||
DUPLICATE_CHECK:
|
||||
for _, existing := range h.handlers {
|
||||
if existing.Id == handler.Id {
|
||||
handler.Id = generateHookId()
|
||||
goto DUPLICATE_CHECK
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// replace existing
|
||||
for i, existing := range h.handlers {
|
||||
if existing.Id == handler.Id {
|
||||
h.handlers[i] = handler
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append new
|
||||
if !exists {
|
||||
h.handlers = append(h.handlers, handler)
|
||||
}
|
||||
|
||||
// sort handlers by Priority, preserving the original order of equal items
|
||||
sort.SliceStable(h.handlers, func(i, j int) bool {
|
||||
return h.handlers[i].Priority < h.handlers[j].Priority
|
||||
})
|
||||
|
||||
return handler.Id
|
||||
}
|
||||
|
||||
// BindFunc is similar to Bind but registers a new handler from just the provided function.
|
||||
//
|
||||
// The registered handler is added with a default 0 priority and the id will be autogenerated.
|
||||
//
|
||||
// If you want to register a handler with custom priority or id use the [Hook.Bind] method.
|
||||
func (h *Hook[T]) BindFunc(fn HandlerFunc[T]) string {
|
||||
return h.Bind(&Handler[T]{Func: fn})
|
||||
}
|
||||
|
||||
// Unbind removes a single hook handler by its id.
|
||||
func (h *Hook[T]) Unbind(id string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
for i := len(h.handlers) - 1; i >= 0; i-- {
|
||||
if h.handlers[i].id == id {
|
||||
if h.handlers[i].Id == id {
|
||||
h.handlers = append(h.handlers[:i], h.handlers[i+1:]...)
|
||||
return
|
||||
break // for now stop on the first occurrence since we don't allow handlers with duplicated ids
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAll removes all registered handlers.
|
||||
func (h *Hook[T]) RemoveAll() {
|
||||
h.mux.Lock()
|
||||
defer h.mux.Unlock()
|
||||
// UnbindAll removes all registered handlers.
|
||||
func (h *Hook[T]) UnbindAll() {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
h.handlers = nil
|
||||
}
|
||||
|
||||
// Length returns to total number of registered hook handlers.
|
||||
func (h *Hook[T]) Length() int {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
return len(h.handlers)
|
||||
}
|
||||
|
||||
// Trigger executes all registered hook handlers one by one
|
||||
// with the specified `data` as an argument.
|
||||
// with the specified event 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.RLock()
|
||||
// NB! Each hook handler must call event.Next() in order the hook chain to proceed.
|
||||
func (h *Hook[T]) Trigger(event T, oneOffHandlers ...HandlerFunc[T]) error {
|
||||
h.mu.RLock()
|
||||
handlers := make([]HandlerFunc[T], 0, len(h.handlers)+len(oneOffHandlers))
|
||||
for _, handler := range h.handlers {
|
||||
handlers = append(handlers, handler.Func)
|
||||
}
|
||||
handlers = append(handlers, oneOffHandlers...)
|
||||
h.mu.RUnlock()
|
||||
|
||||
handlers := make([]*handlerPair[T], 0, len(h.handlers)+len(oneOffHandlers))
|
||||
handlers = append(handlers, h.handlers...)
|
||||
event.setNextFunc(nil) // reset in case the event is being reused
|
||||
|
||||
// append the one off handlers
|
||||
for i, oneOff := range oneOffHandlers {
|
||||
handlers = append(handlers, &handlerPair[T]{
|
||||
id: fmt.Sprintf("@%d", i),
|
||||
handler: oneOff,
|
||||
for i := len(handlers) - 1; i >= 0; i-- {
|
||||
i := i
|
||||
old := event.nextFunc()
|
||||
event.setNextFunc(func() error {
|
||||
event.setNextFunc(old)
|
||||
return handlers[i](event)
|
||||
})
|
||||
}
|
||||
|
||||
// unlock is not deferred to avoid deadlocks in case Trigger
|
||||
// is called recursively by the handlers
|
||||
h.mux.RUnlock()
|
||||
|
||||
for _, item := range handlers {
|
||||
err := item.handler(data)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if errors.Is(err, StopPropagation) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return event.Next()
|
||||
}
|
||||
|
||||
func generateHookId() string {
|
||||
return security.PseudorandomString(8)
|
||||
return security.PseudorandomString(20)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user