fix(go.sum): update ResolveSpec dependency to v1.0.87
This commit is contained in:
+176
@@ -0,0 +1,176 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Push notification error definitions
|
||||
// This file contains all error types and messages used by the push notification system
|
||||
|
||||
// Error reason constants
|
||||
const (
|
||||
// HandlerReasons
|
||||
ReasonHandlerNil = "handler cannot be nil"
|
||||
ReasonHandlerExists = "cannot overwrite existing handler"
|
||||
ReasonHandlerProtected = "handler is protected"
|
||||
|
||||
// ProcessorReasons
|
||||
ReasonPushNotificationsDisabled = "push notifications are disabled"
|
||||
)
|
||||
|
||||
// ProcessorType represents the type of processor involved in the error
|
||||
// defined as a custom type for better readability and easier maintenance
|
||||
type ProcessorType string
|
||||
|
||||
const (
|
||||
// ProcessorTypes
|
||||
ProcessorTypeProcessor = ProcessorType("processor")
|
||||
ProcessorTypeVoidProcessor = ProcessorType("void_processor")
|
||||
ProcessorTypeCustom = ProcessorType("custom")
|
||||
)
|
||||
|
||||
// ProcessorOperation represents the operation being performed by the processor
|
||||
// defined as a custom type for better readability and easier maintenance
|
||||
type ProcessorOperation string
|
||||
|
||||
const (
|
||||
// ProcessorOperations
|
||||
ProcessorOperationProcess = ProcessorOperation("process")
|
||||
ProcessorOperationRegister = ProcessorOperation("register")
|
||||
ProcessorOperationUnregister = ProcessorOperation("unregister")
|
||||
ProcessorOperationUnknown = ProcessorOperation("unknown")
|
||||
)
|
||||
|
||||
// Common error variables for reuse
|
||||
var (
|
||||
// ErrHandlerNil is returned when attempting to register a nil handler
|
||||
ErrHandlerNil = errors.New(ReasonHandlerNil)
|
||||
)
|
||||
|
||||
// Registry errors
|
||||
|
||||
// ErrHandlerExists creates an error for when attempting to overwrite an existing handler
|
||||
func ErrHandlerExists(pushNotificationName string) error {
|
||||
return NewHandlerError(ProcessorOperationRegister, pushNotificationName, ReasonHandlerExists, nil)
|
||||
}
|
||||
|
||||
// ErrProtectedHandler creates an error for when attempting to unregister a protected handler
|
||||
func ErrProtectedHandler(pushNotificationName string) error {
|
||||
return NewHandlerError(ProcessorOperationUnregister, pushNotificationName, ReasonHandlerProtected, nil)
|
||||
}
|
||||
|
||||
// VoidProcessor errors
|
||||
|
||||
// ErrVoidProcessorRegister creates an error for when attempting to register a handler on void processor
|
||||
func ErrVoidProcessorRegister(pushNotificationName string) error {
|
||||
return NewProcessorError(ProcessorTypeVoidProcessor, ProcessorOperationRegister, pushNotificationName, ReasonPushNotificationsDisabled, nil)
|
||||
}
|
||||
|
||||
// ErrVoidProcessorUnregister creates an error for when attempting to unregister a handler on void processor
|
||||
func ErrVoidProcessorUnregister(pushNotificationName string) error {
|
||||
return NewProcessorError(ProcessorTypeVoidProcessor, ProcessorOperationUnregister, pushNotificationName, ReasonPushNotificationsDisabled, nil)
|
||||
}
|
||||
|
||||
// Error type definitions for advanced error handling
|
||||
|
||||
// HandlerError represents errors related to handler operations
|
||||
type HandlerError struct {
|
||||
Operation ProcessorOperation
|
||||
PushNotificationName string
|
||||
Reason string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *HandlerError) Error() string {
|
||||
if e.Err != nil {
|
||||
return fmt.Sprintf("handler %s failed for '%s': %s (%v)", e.Operation, e.PushNotificationName, e.Reason, e.Err)
|
||||
}
|
||||
return fmt.Sprintf("handler %s failed for '%s': %s", e.Operation, e.PushNotificationName, e.Reason)
|
||||
}
|
||||
|
||||
func (e *HandlerError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewHandlerError creates a new HandlerError
|
||||
func NewHandlerError(operation ProcessorOperation, pushNotificationName, reason string, err error) *HandlerError {
|
||||
return &HandlerError{
|
||||
Operation: operation,
|
||||
PushNotificationName: pushNotificationName,
|
||||
Reason: reason,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessorError represents errors related to processor operations
|
||||
type ProcessorError struct {
|
||||
ProcessorType ProcessorType // "processor", "void_processor"
|
||||
Operation ProcessorOperation // "process", "register", "unregister"
|
||||
PushNotificationName string // Name of the push notification involved
|
||||
Reason string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *ProcessorError) Error() string {
|
||||
notifInfo := ""
|
||||
if e.PushNotificationName != "" {
|
||||
notifInfo = fmt.Sprintf(" for '%s'", e.PushNotificationName)
|
||||
}
|
||||
if e.Err != nil {
|
||||
return fmt.Sprintf("%s %s failed%s: %s (%v)", e.ProcessorType, e.Operation, notifInfo, e.Reason, e.Err)
|
||||
}
|
||||
return fmt.Sprintf("%s %s failed%s: %s", e.ProcessorType, e.Operation, notifInfo, e.Reason)
|
||||
}
|
||||
|
||||
func (e *ProcessorError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewProcessorError creates a new ProcessorError
|
||||
func NewProcessorError(processorType ProcessorType, operation ProcessorOperation, pushNotificationName, reason string, err error) *ProcessorError {
|
||||
return &ProcessorError{
|
||||
ProcessorType: processorType,
|
||||
Operation: operation,
|
||||
PushNotificationName: pushNotificationName,
|
||||
Reason: reason,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for common error scenarios
|
||||
|
||||
// IsHandlerNilError checks if an error is due to a nil handler
|
||||
func IsHandlerNilError(err error) bool {
|
||||
return errors.Is(err, ErrHandlerNil)
|
||||
}
|
||||
|
||||
// IsHandlerExistsError checks if an error is due to attempting to overwrite an existing handler.
|
||||
// This function works correctly even when the error is wrapped.
|
||||
func IsHandlerExistsError(err error) bool {
|
||||
var handlerErr *HandlerError
|
||||
if errors.As(err, &handlerErr) {
|
||||
return handlerErr.Operation == ProcessorOperationRegister && handlerErr.Reason == ReasonHandlerExists
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsProtectedHandlerError checks if an error is due to attempting to unregister a protected handler.
|
||||
// This function works correctly even when the error is wrapped.
|
||||
func IsProtectedHandlerError(err error) bool {
|
||||
var handlerErr *HandlerError
|
||||
if errors.As(err, &handlerErr) {
|
||||
return handlerErr.Operation == ProcessorOperationUnregister && handlerErr.Reason == ReasonHandlerProtected
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsVoidProcessorError checks if an error is due to void processor operations.
|
||||
// This function works correctly even when the error is wrapped.
|
||||
func IsVoidProcessorError(err error) bool {
|
||||
var procErr *ProcessorError
|
||||
if errors.As(err, &procErr) {
|
||||
return procErr.ProcessorType == ProcessorTypeVoidProcessor && procErr.Reason == ReasonPushNotificationsDisabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// NotificationHandler defines the interface for push notification handlers.
|
||||
type NotificationHandler interface {
|
||||
// HandlePushNotification processes a push notification with context information.
|
||||
// The handlerCtx provides information about the client, connection pool, and connection
|
||||
// on which the notification was received, allowing handlers to make informed decisions.
|
||||
// Returns an error if the notification could not be handled.
|
||||
HandlePushNotification(ctx context.Context, handlerCtx NotificationHandlerContext, notification []interface{}) error
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package push
|
||||
|
||||
// No imports needed for this file
|
||||
|
||||
// NotificationHandlerContext provides context information about where a push notification was received.
|
||||
// This struct allows handlers to make informed decisions based on the source of the notification
|
||||
// with strongly typed access to different client types using concrete types.
|
||||
type NotificationHandlerContext struct {
|
||||
// Client is the Redis client instance that received the notification.
|
||||
// It is interface to both allow for future expansion and to avoid
|
||||
// circular dependencies. The developer is responsible for type assertion.
|
||||
// It can be one of the following types:
|
||||
// - *redis.baseClient
|
||||
// - *redis.Client
|
||||
// - *redis.ClusterClient
|
||||
// - *redis.Conn
|
||||
Client interface{}
|
||||
|
||||
// ConnPool is the connection pool from which the connection was obtained.
|
||||
// It is interface to both allow for future expansion and to avoid
|
||||
// circular dependencies. The developer is responsible for type assertion.
|
||||
// It can be one of the following types:
|
||||
// - *pool.ConnPool
|
||||
// - *pool.SingleConnPool
|
||||
// - *pool.StickyConnPool
|
||||
ConnPool interface{}
|
||||
|
||||
// PubSub is the PubSub instance that received the notification.
|
||||
// It is interface to both allow for future expansion and to avoid
|
||||
// circular dependencies. The developer is responsible for type assertion.
|
||||
// It can be one of the following types:
|
||||
// - *redis.PubSub
|
||||
PubSub interface{}
|
||||
|
||||
// Conn is the specific connection on which the notification was received.
|
||||
// It is interface to both allow for future expansion and to avoid
|
||||
// circular dependencies. The developer is responsible for type assertion.
|
||||
// It can be one of the following types:
|
||||
// - *pool.Conn
|
||||
Conn interface{}
|
||||
|
||||
// IsBlocking indicates if the notification was received on a blocking connection.
|
||||
IsBlocking bool
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal"
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
// NotificationProcessor defines the interface for push notification processors.
|
||||
type NotificationProcessor interface {
|
||||
// GetHandler returns the handler for a specific push notification name.
|
||||
GetHandler(pushNotificationName string) NotificationHandler
|
||||
// ProcessPendingNotifications checks for and processes any pending push notifications.
|
||||
// To be used when it is known that there are notifications on the socket.
|
||||
// It will try to read from the socket and if it is empty - it may block.
|
||||
ProcessPendingNotifications(ctx context.Context, handlerCtx NotificationHandlerContext, rd *proto.Reader) error
|
||||
// RegisterHandler registers a handler for a specific push notification name.
|
||||
RegisterHandler(pushNotificationName string, handler NotificationHandler, protected bool) error
|
||||
// UnregisterHandler removes a handler for a specific push notification name.
|
||||
UnregisterHandler(pushNotificationName string) error
|
||||
}
|
||||
|
||||
// Processor handles push notifications with a registry of handlers
|
||||
type Processor struct {
|
||||
registry *Registry
|
||||
}
|
||||
|
||||
// NewProcessor creates a new push notification processor
|
||||
func NewProcessor() *Processor {
|
||||
return &Processor{
|
||||
registry: NewRegistry(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetHandler returns the handler for a specific push notification name
|
||||
func (p *Processor) GetHandler(pushNotificationName string) NotificationHandler {
|
||||
return p.registry.GetHandler(pushNotificationName)
|
||||
}
|
||||
|
||||
// RegisterHandler registers a handler for a specific push notification name
|
||||
func (p *Processor) RegisterHandler(pushNotificationName string, handler NotificationHandler, protected bool) error {
|
||||
return p.registry.RegisterHandler(pushNotificationName, handler, protected)
|
||||
}
|
||||
|
||||
// UnregisterHandler removes a handler for a specific push notification name
|
||||
func (p *Processor) UnregisterHandler(pushNotificationName string) error {
|
||||
return p.registry.UnregisterHandler(pushNotificationName)
|
||||
}
|
||||
|
||||
// ProcessPendingNotifications checks for and processes any pending push notifications
|
||||
// This method should be called by the client in WithReader before reading the reply
|
||||
// It will try to read from the socket and if it is empty - it may block.
|
||||
func (p *Processor) ProcessPendingNotifications(ctx context.Context, handlerCtx NotificationHandlerContext, rd *proto.Reader) error {
|
||||
if rd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
// Check if there's data available to read
|
||||
replyType, err := rd.PeekReplyType()
|
||||
if err != nil {
|
||||
// No more data available or error reading
|
||||
// if timeout, it will be handled by the caller
|
||||
break
|
||||
}
|
||||
|
||||
// Only process push notifications (arrays starting with >)
|
||||
if replyType != proto.RespPush {
|
||||
break
|
||||
}
|
||||
|
||||
// see if we should skip this notification
|
||||
notificationName, err := rd.PeekPushNotificationName()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if willHandleNotificationInClient(notificationName) {
|
||||
break
|
||||
}
|
||||
|
||||
// Read the push notification
|
||||
reply, err := rd.ReadReply()
|
||||
if err != nil {
|
||||
internal.Logger.Printf(ctx, "push: error reading push notification: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
// Convert to slice of interfaces
|
||||
notification, ok := reply.([]interface{})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
// Handle the notification directly
|
||||
if len(notification) > 0 {
|
||||
// Extract the notification type (first element)
|
||||
if notificationType, ok := notification[0].(string); ok {
|
||||
// Get the handler for this notification type
|
||||
if handler := p.registry.GetHandler(notificationType); handler != nil {
|
||||
// Handle the notification
|
||||
err := handler.HandlePushNotification(ctx, handlerCtx, notification)
|
||||
if err != nil {
|
||||
internal.Logger.Printf(ctx, "push: error handling push notification: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VoidProcessor discards all push notifications without processing them
|
||||
type VoidProcessor struct{}
|
||||
|
||||
// NewVoidProcessor creates a new void push notification processor
|
||||
func NewVoidProcessor() *VoidProcessor {
|
||||
return &VoidProcessor{}
|
||||
}
|
||||
|
||||
// GetHandler returns nil for void processor since it doesn't maintain handlers
|
||||
func (v *VoidProcessor) GetHandler(_ string) NotificationHandler {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterHandler returns an error for void processor since it doesn't maintain handlers
|
||||
func (v *VoidProcessor) RegisterHandler(pushNotificationName string, _ NotificationHandler, _ bool) error {
|
||||
return ErrVoidProcessorRegister(pushNotificationName)
|
||||
}
|
||||
|
||||
// UnregisterHandler returns an error for void processor since it doesn't maintain handlers
|
||||
func (v *VoidProcessor) UnregisterHandler(pushNotificationName string) error {
|
||||
return ErrVoidProcessorUnregister(pushNotificationName)
|
||||
}
|
||||
|
||||
// ProcessPendingNotifications for VoidProcessor does nothing since push notifications
|
||||
// are only available in RESP3 and this processor is used for RESP2 connections.
|
||||
// This avoids unnecessary buffer scanning overhead.
|
||||
// It does however read and discard all push notifications from the buffer to avoid
|
||||
// them being interpreted as a reply.
|
||||
// This method should be called by the client in WithReader before reading the reply
|
||||
// to be sure there are no buffered push notifications.
|
||||
// It will try to read from the socket and if it is empty - it may block.
|
||||
func (v *VoidProcessor) ProcessPendingNotifications(_ context.Context, handlerCtx NotificationHandlerContext, rd *proto.Reader) error {
|
||||
// read and discard all push notifications
|
||||
if rd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
// Check if there's data available to read
|
||||
replyType, err := rd.PeekReplyType()
|
||||
if err != nil {
|
||||
// No more data available or error reading
|
||||
// if timeout, it will be handled by the caller
|
||||
break
|
||||
}
|
||||
|
||||
// Only process push notifications (arrays starting with >)
|
||||
if replyType != proto.RespPush {
|
||||
break
|
||||
}
|
||||
// see if we should skip this notification
|
||||
notificationName, err := rd.PeekPushNotificationName()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if willHandleNotificationInClient(notificationName) {
|
||||
break
|
||||
}
|
||||
|
||||
// Read the push notification
|
||||
_, err = rd.ReadReply()
|
||||
if err != nil {
|
||||
internal.Logger.Printf(context.Background(), "push: error reading push notification: %v", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// willHandleNotificationInClient checks if a notification type should be ignored by the push notification
|
||||
// processor and handled by other specialized systems instead (pub/sub, streams, keyspace, etc.).
|
||||
func willHandleNotificationInClient(notificationType string) bool {
|
||||
switch notificationType {
|
||||
// Pub/Sub notifications - handled by pub/sub system
|
||||
case "message", // Regular pub/sub message
|
||||
"pmessage", // Pattern pub/sub message
|
||||
"subscribe", // Subscription confirmation
|
||||
"unsubscribe", // Unsubscription confirmation
|
||||
"psubscribe", // Pattern subscription confirmation
|
||||
"punsubscribe", // Pattern unsubscription confirmation
|
||||
"smessage", // Sharded pub/sub message (Redis 7.0+)
|
||||
"ssubscribe", // Sharded subscription confirmation
|
||||
"sunsubscribe": // Sharded unsubscription confirmation
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// Package push provides push notifications for Redis.
|
||||
// This is an EXPERIMENTAL API for handling push notifications from Redis.
|
||||
// It is not yet stable and may change in the future.
|
||||
// Although this is in a public package, in its current form public use is not advised.
|
||||
// Pending push notifications should be processed before executing any readReply from the connection
|
||||
// as per RESP3 specification push notifications can be sent at any time.
|
||||
package push
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Registry manages push notification handlers
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
handlers map[string]NotificationHandler
|
||||
protected map[string]bool
|
||||
}
|
||||
|
||||
// NewRegistry creates a new push notification registry
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
handlers: make(map[string]NotificationHandler),
|
||||
protected: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterHandler registers a handler for a specific push notification name
|
||||
func (r *Registry) RegisterHandler(pushNotificationName string, handler NotificationHandler, protected bool) error {
|
||||
if handler == nil {
|
||||
return ErrHandlerNil
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// Check if handler already exists
|
||||
if _, exists := r.protected[pushNotificationName]; exists {
|
||||
return ErrHandlerExists(pushNotificationName)
|
||||
}
|
||||
|
||||
r.handlers[pushNotificationName] = handler
|
||||
r.protected[pushNotificationName] = protected
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHandler returns the handler for a specific push notification name
|
||||
func (r *Registry) GetHandler(pushNotificationName string) NotificationHandler {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.handlers[pushNotificationName]
|
||||
}
|
||||
|
||||
// UnregisterHandler removes a handler for a specific push notification name
|
||||
func (r *Registry) UnregisterHandler(pushNotificationName string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// Check if handler is protected
|
||||
if protected, exists := r.protected[pushNotificationName]; exists && protected {
|
||||
return ErrProtectedHandler(pushNotificationName)
|
||||
}
|
||||
|
||||
delete(r.handlers, pushNotificationName)
|
||||
delete(r.protected, pushNotificationName)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user