Skip to content

Commit ada72ce

Browse files
committed
refactor: move push notification logic to pusnotif package
1 parent 9a7a5c8 commit ada72ce

File tree

4 files changed

+379
-196
lines changed

4 files changed

+379
-196
lines changed

internal/pushnotif/processor.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package pushnotif
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/redis/go-redis/v9/internal/proto"
8+
)
9+
10+
// Processor handles push notifications with a registry of handlers.
11+
type Processor struct {
12+
registry *Registry
13+
}
14+
15+
// NewProcessor creates a new push notification processor.
16+
func NewProcessor() *Processor {
17+
return &Processor{
18+
registry: NewRegistry(),
19+
}
20+
}
21+
22+
// GetHandler returns the handler for a specific push notification name.
23+
// Returns nil if no handler is registered for the given name.
24+
func (p *Processor) GetHandler(pushNotificationName string) Handler {
25+
return p.registry.GetHandler(pushNotificationName)
26+
}
27+
28+
// RegisterHandler registers a handler for a specific push notification name.
29+
// Returns an error if a handler is already registered for this push notification name.
30+
// If protected is true, the handler cannot be unregistered.
31+
func (p *Processor) RegisterHandler(pushNotificationName string, handler Handler, protected bool) error {
32+
return p.registry.RegisterHandler(pushNotificationName, handler, protected)
33+
}
34+
35+
// UnregisterHandler removes a handler for a specific push notification name.
36+
// Returns an error if the handler is protected or doesn't exist.
37+
func (p *Processor) UnregisterHandler(pushNotificationName string) error {
38+
return p.registry.UnregisterHandler(pushNotificationName)
39+
}
40+
41+
// GetRegistryForTesting returns the push notification registry for testing.
42+
// This method should only be used by tests.
43+
func (p *Processor) GetRegistryForTesting() *Registry {
44+
return p.registry
45+
}
46+
47+
// ProcessPendingNotifications checks for and processes any pending push notifications.
48+
func (p *Processor) ProcessPendingNotifications(ctx context.Context, rd *proto.Reader) error {
49+
// Check for nil reader
50+
if rd == nil {
51+
return nil
52+
}
53+
54+
// Check if there are any buffered bytes that might contain push notifications
55+
if rd.Buffered() == 0 {
56+
return nil
57+
}
58+
59+
// Process all available push notifications
60+
for {
61+
// Peek at the next reply type to see if it's a push notification
62+
replyType, err := rd.PeekReplyType()
63+
if err != nil {
64+
// No more data available or error reading
65+
break
66+
}
67+
68+
// Push notifications use RespPush type in RESP3
69+
if replyType != proto.RespPush {
70+
break
71+
}
72+
73+
// Try to read the push notification
74+
reply, err := rd.ReadReply()
75+
if err != nil {
76+
return fmt.Errorf("failed to read push notification: %w", err)
77+
}
78+
79+
// Convert to slice of interfaces
80+
notification, ok := reply.([]interface{})
81+
if !ok {
82+
continue
83+
}
84+
85+
// Handle the notification
86+
p.registry.HandleNotification(ctx, notification)
87+
}
88+
89+
return nil
90+
}
91+
92+
// VoidProcessor discards all push notifications without processing them.
93+
type VoidProcessor struct{}
94+
95+
// NewVoidProcessor creates a new void push notification processor.
96+
func NewVoidProcessor() *VoidProcessor {
97+
return &VoidProcessor{}
98+
}
99+
100+
// GetHandler returns nil for void processor since it doesn't maintain handlers.
101+
func (v *VoidProcessor) GetHandler(pushNotificationName string) Handler {
102+
return nil
103+
}
104+
105+
// RegisterHandler returns an error for void processor since it doesn't maintain handlers.
106+
func (v *VoidProcessor) RegisterHandler(pushNotificationName string, handler Handler, protected bool) error {
107+
return fmt.Errorf("void push notification processor does not support handler registration")
108+
}
109+
110+
// GetRegistryForTesting returns nil for void processor since it doesn't maintain handlers.
111+
// This method should only be used by tests.
112+
func (v *VoidProcessor) GetRegistryForTesting() *Registry {
113+
return nil
114+
}
115+
116+
// ProcessPendingNotifications reads and discards any pending push notifications.
117+
func (v *VoidProcessor) ProcessPendingNotifications(ctx context.Context, rd *proto.Reader) error {
118+
// Check for nil reader
119+
if rd == nil {
120+
return nil
121+
}
122+
123+
// Read and discard any pending push notifications to clean the buffer
124+
for {
125+
// Peek at the next reply type to see if it's a push notification
126+
replyType, err := rd.PeekReplyType()
127+
if err != nil {
128+
// No more data available or error reading
129+
break
130+
}
131+
132+
// Push notifications use RespPush type in RESP3
133+
if replyType != proto.RespPush {
134+
break
135+
}
136+
137+
// Read and discard the push notification
138+
_, err = rd.ReadReply()
139+
if err != nil {
140+
return fmt.Errorf("failed to read push notification for discarding: %w", err)
141+
}
142+
143+
// Notification discarded - continue to next one
144+
}
145+
146+
return nil
147+
}

internal/pushnotif/registry.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package pushnotif
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sync"
7+
)
8+
9+
// Registry manages push notification handlers.
10+
type Registry struct {
11+
mu sync.RWMutex
12+
handlers map[string]handlerEntry
13+
}
14+
15+
// NewRegistry creates a new push notification registry.
16+
func NewRegistry() *Registry {
17+
return &Registry{
18+
handlers: make(map[string]handlerEntry),
19+
}
20+
}
21+
22+
// RegisterHandler registers a handler for a specific push notification name.
23+
// Returns an error if a handler is already registered for this push notification name.
24+
// If protected is true, the handler cannot be unregistered.
25+
func (r *Registry) RegisterHandler(pushNotificationName string, handler Handler, protected bool) error {
26+
r.mu.Lock()
27+
defer r.mu.Unlock()
28+
29+
if _, exists := r.handlers[pushNotificationName]; exists {
30+
return fmt.Errorf("handler already registered for push notification: %s", pushNotificationName)
31+
}
32+
33+
r.handlers[pushNotificationName] = handlerEntry{
34+
handler: handler,
35+
protected: protected,
36+
}
37+
return nil
38+
}
39+
40+
// UnregisterHandler removes a handler for a specific push notification name.
41+
// Returns an error if the handler is protected or doesn't exist.
42+
func (r *Registry) UnregisterHandler(pushNotificationName string) error {
43+
r.mu.Lock()
44+
defer r.mu.Unlock()
45+
46+
entry, exists := r.handlers[pushNotificationName]
47+
if !exists {
48+
return fmt.Errorf("no handler registered for push notification: %s", pushNotificationName)
49+
}
50+
51+
if entry.protected {
52+
return fmt.Errorf("cannot unregister protected handler for push notification: %s", pushNotificationName)
53+
}
54+
55+
delete(r.handlers, pushNotificationName)
56+
return nil
57+
}
58+
59+
// GetHandler returns the handler for a specific push notification name.
60+
// Returns nil if no handler is registered for the given name.
61+
func (r *Registry) GetHandler(pushNotificationName string) Handler {
62+
r.mu.RLock()
63+
defer r.mu.RUnlock()
64+
65+
entry, exists := r.handlers[pushNotificationName]
66+
if !exists {
67+
return nil
68+
}
69+
return entry.handler
70+
}
71+
72+
// GetRegisteredPushNotificationNames returns a list of all registered push notification names.
73+
func (r *Registry) GetRegisteredPushNotificationNames() []string {
74+
r.mu.RLock()
75+
defer r.mu.RUnlock()
76+
77+
names := make([]string, 0, len(r.handlers))
78+
for name := range r.handlers {
79+
names = append(names, name)
80+
}
81+
return names
82+
}
83+
84+
// HandleNotification attempts to handle a push notification using registered handlers.
85+
// Returns true if a handler was found and successfully processed the notification.
86+
func (r *Registry) HandleNotification(ctx context.Context, notification []interface{}) bool {
87+
if len(notification) == 0 {
88+
return false
89+
}
90+
91+
// Extract the notification type (first element)
92+
notificationType, ok := notification[0].(string)
93+
if !ok {
94+
return false
95+
}
96+
97+
// Get the handler for this notification type
98+
handler := r.GetHandler(notificationType)
99+
if handler == nil {
100+
return false
101+
}
102+
103+
// Handle the notification
104+
return handler.HandlePushNotification(ctx, notification)
105+
}

internal/pushnotif/types.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package pushnotif
2+
3+
import (
4+
"context"
5+
6+
"github.com/redis/go-redis/v9/internal/proto"
7+
)
8+
9+
// Handler defines the interface for push notification handlers.
10+
type Handler interface {
11+
// HandlePushNotification processes a push notification.
12+
// Returns true if the notification was handled, false otherwise.
13+
HandlePushNotification(ctx context.Context, notification []interface{}) bool
14+
}
15+
16+
// ProcessorInterface defines the interface for push notification processors.
17+
type ProcessorInterface interface {
18+
GetHandler(pushNotificationName string) Handler
19+
ProcessPendingNotifications(ctx context.Context, rd *proto.Reader) error
20+
RegisterHandler(pushNotificationName string, handler Handler, protected bool) error
21+
}
22+
23+
// RegistryInterface defines the interface for push notification registries.
24+
type RegistryInterface interface {
25+
RegisterHandler(pushNotificationName string, handler Handler, protected bool) error
26+
UnregisterHandler(pushNotificationName string) error
27+
GetHandler(pushNotificationName string) Handler
28+
GetRegisteredPushNotificationNames() []string
29+
HandleNotification(ctx context.Context, notification []interface{}) bool
30+
}
31+
32+
// handlerEntry represents a registered handler with its protection status.
33+
type handlerEntry struct {
34+
handler Handler
35+
protected bool
36+
}

0 commit comments

Comments
 (0)