package models import ( "context" "fmt" "sync" ) type ServiceHook[T any] interface { PreUpdate(context.Context, *T) error PreDelete(context.Context, *T) error PostCreate(context.Context, *T) error PostUpdate(context.Context, *T) error PostDelete(context.Context, *T) error } type ServiceHooks[T any] interface { RegisterHooks(...ServiceHook[T]) } var ( _ ServiceHooks[any] = (*ServiceHookRegistry[any])(nil) _ ServiceHook[any] = (*ServiceHookRegistry[any])(nil) ) type loopKey string var loopVal = struct{}{} type ServiceHookRegistry[T any] struct { hooks []ServiceHook[T] mu sync.RWMutex id loopKey once sync.Once } func (r *ServiceHookRegistry[T]) init() { r.once.Do(func() { r.id = loopKey(fmt.Sprintf("service-hook-registry-%p", r)) }) } func (r *ServiceHookRegistry[T]) PreUpdate(ctx context.Context, t *T) error { r.init() if v := ctx.Value(r.id); v != nil { return nil } ctx = context.WithValue(ctx, r.id, loopVal) r.mu.RLock() defer r.mu.RUnlock() for _, hook := range r.hooks { if err := hook.PreUpdate(ctx, t); err != nil { return err } } return nil } func (r *ServiceHookRegistry[T]) PreDelete(ctx context.Context, t *T) error { r.init() if v := ctx.Value(r.id); v != nil { return nil } ctx = context.WithValue(ctx, r.id, loopVal) r.mu.RLock() defer r.mu.RUnlock() for _, hook := range r.hooks { if err := hook.PreDelete(ctx, t); err != nil { return err } } return nil } func (r *ServiceHookRegistry[T]) PostCreate(ctx context.Context, t *T) error { r.init() if v := ctx.Value(r.id); v != nil { return nil } ctx = context.WithValue(ctx, r.id, loopVal) r.mu.RLock() defer r.mu.RUnlock() for _, hook := range r.hooks { if err := hook.PostCreate(ctx, t); err != nil { return err } } return nil } func (r *ServiceHookRegistry[T]) PostUpdate(ctx context.Context, t *T) error { r.init() if v := ctx.Value(r.id); v != nil { return nil } ctx = context.WithValue(ctx, r.id, loopVal) r.mu.RLock() defer r.mu.RUnlock() for _, hook := range r.hooks { if err := hook.PostUpdate(ctx, t); err != nil { return err } } return nil } func (r *ServiceHookRegistry[T]) PostDelete(ctx context.Context, t *T) error { r.init() if v := ctx.Value(r.id); v != nil { return nil } ctx = context.WithValue(ctx, r.id, loopVal) r.mu.RLock() defer r.mu.RUnlock() for _, hook := range r.hooks { if err := hook.PostDelete(ctx, t); err != nil { return err } } return nil } func (r *ServiceHookRegistry[T]) RegisterHooks(hooks ...ServiceHook[T]) { r.mu.Lock() defer r.mu.Unlock() r.hooks = append(r.hooks, hooks...) } func NewServiceHookRegistry[T any]() *ServiceHookRegistry[T] { return &ServiceHookRegistry[T]{ hooks: []ServiceHook[T]{}, mu: sync.RWMutex{}, } } var _ ServiceHook[any] = (*NoopServiceHook[any])(nil) type NoopServiceHook[T any] struct{} func (n NoopServiceHook[T]) PreUpdate(context.Context, *T) error { return nil } func (n NoopServiceHook[T]) PreDelete(context.Context, *T) error { return nil } func (n NoopServiceHook[T]) PostCreate(context.Context, *T) error { return nil } func (n NoopServiceHook[T]) PostUpdate(context.Context, *T) error { return nil } func (n NoopServiceHook[T]) PostDelete(context.Context, *T) error { return nil }