Skip to content

Commit 6db3b24

Browse files
committed
feat: enforce single handler per notification type
- Change PushNotificationRegistry to allow only one handler per command - RegisterHandler methods now return error if handler already exists - Update UnregisterHandler to remove handler by command only - Update all client methods to return errors for duplicate registrations - Update comprehensive test suite to verify single handler behavior - Add specific test for duplicate handler error scenarios This prevents handler conflicts and ensures predictable notification routing with clear error handling for registration conflicts.
1 parent 8d2aa3c commit 6db3b24

File tree

3 files changed

+144
-108
lines changed

3 files changed

+144
-108
lines changed

push_notifications.go

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package redis
22

33
import (
44
"context"
5+
"fmt"
56
"sync"
67

78
"github.com/redis/go-redis/v9/internal"
@@ -26,27 +27,29 @@ func (f PushNotificationHandlerFunc) HandlePushNotification(ctx context.Context,
2627
// PushNotificationRegistry manages handlers for different types of push notifications.
2728
type PushNotificationRegistry struct {
2829
mu sync.RWMutex
29-
handlers map[string][]PushNotificationHandler // command -> handlers
30-
global []PushNotificationHandler // global handlers for all notifications
30+
handlers map[string]PushNotificationHandler // command -> single handler
31+
global []PushNotificationHandler // global handlers for all notifications
3132
}
3233

3334
// NewPushNotificationRegistry creates a new push notification registry.
3435
func NewPushNotificationRegistry() *PushNotificationRegistry {
3536
return &PushNotificationRegistry{
36-
handlers: make(map[string][]PushNotificationHandler),
37+
handlers: make(map[string]PushNotificationHandler),
3738
global: make([]PushNotificationHandler, 0),
3839
}
3940
}
4041

4142
// RegisterHandler registers a handler for a specific push notification command.
42-
func (r *PushNotificationRegistry) RegisterHandler(command string, handler PushNotificationHandler) {
43+
// Returns an error if a handler is already registered for this command.
44+
func (r *PushNotificationRegistry) RegisterHandler(command string, handler PushNotificationHandler) error {
4345
r.mu.Lock()
4446
defer r.mu.Unlock()
4547

46-
if r.handlers[command] == nil {
47-
r.handlers[command] = make([]PushNotificationHandler, 0)
48+
if _, exists := r.handlers[command]; exists {
49+
return fmt.Errorf("handler already registered for command: %s", command)
4850
}
49-
r.handlers[command] = append(r.handlers[command], handler)
51+
r.handlers[command] = handler
52+
return nil
5053
}
5154

5255
// RegisterGlobalHandler registers a handler that will receive all push notifications.
@@ -57,19 +60,12 @@ func (r *PushNotificationRegistry) RegisterGlobalHandler(handler PushNotificatio
5760
r.global = append(r.global, handler)
5861
}
5962

60-
// UnregisterHandler removes a handler for a specific command.
61-
func (r *PushNotificationRegistry) UnregisterHandler(command string, handler PushNotificationHandler) {
63+
// UnregisterHandler removes the handler for a specific push notification command.
64+
func (r *PushNotificationRegistry) UnregisterHandler(command string) {
6265
r.mu.Lock()
6366
defer r.mu.Unlock()
6467

65-
handlers := r.handlers[command]
66-
for i, h := range handlers {
67-
// Compare function pointers (this is a simplified approach)
68-
if &h == &handler {
69-
r.handlers[command] = append(handlers[:i], handlers[i+1:]...)
70-
break
71-
}
72-
}
68+
delete(r.handlers, command)
7369
}
7470

7571
// HandleNotification processes a push notification by calling all registered handlers.
@@ -96,12 +92,10 @@ func (r *PushNotificationRegistry) HandleNotification(ctx context.Context, notif
9692
}
9793
}
9894

99-
// Call specific handlers
100-
if handlers, exists := r.handlers[command]; exists {
101-
for _, handler := range handlers {
102-
if handler.HandlePushNotification(ctx, notification) {
103-
handled = true
104-
}
95+
// Call specific handler
96+
if handler, exists := r.handlers[command]; exists {
97+
if handler.HandlePushNotification(ctx, notification) {
98+
handled = true
10599
}
106100
}
107101

@@ -207,8 +201,9 @@ func (p *PushNotificationProcessor) ProcessPendingNotifications(ctx context.Cont
207201
}
208202

209203
// RegisterHandler is a convenience method to register a handler for a specific command.
210-
func (p *PushNotificationProcessor) RegisterHandler(command string, handler PushNotificationHandler) {
211-
p.registry.RegisterHandler(command, handler)
204+
// Returns an error if a handler is already registered for this command.
205+
func (p *PushNotificationProcessor) RegisterHandler(command string, handler PushNotificationHandler) error {
206+
return p.registry.RegisterHandler(command, handler)
212207
}
213208

214209
// RegisterGlobalHandler is a convenience method to register a global handler.
@@ -217,8 +212,9 @@ func (p *PushNotificationProcessor) RegisterGlobalHandler(handler PushNotificati
217212
}
218213

219214
// RegisterHandlerFunc is a convenience method to register a function as a handler.
220-
func (p *PushNotificationProcessor) RegisterHandlerFunc(command string, handlerFunc func(ctx context.Context, notification []interface{}) bool) {
221-
p.registry.RegisterHandler(command, PushNotificationHandlerFunc(handlerFunc))
215+
// Returns an error if a handler is already registered for this command.
216+
func (p *PushNotificationProcessor) RegisterHandlerFunc(command string, handlerFunc func(ctx context.Context, notification []interface{}) bool) error {
217+
return p.registry.RegisterHandler(command, PushNotificationHandlerFunc(handlerFunc))
222218
}
223219

224220
// RegisterGlobalHandlerFunc is a convenience method to register a function as a global handler.

0 commit comments

Comments
 (0)