Files
IRC-kosmi-relay/bridge/jackbox/errors.go
cottongin dd398c9a8c sync
2025-11-01 21:00:16 -04:00

140 lines
3.6 KiB
Go

package jackbox
import (
"errors"
"fmt"
)
// Sentinel errors for common failure scenarios
var (
// ErrNotAuthenticated indicates the client is not authenticated
ErrNotAuthenticated = errors.New("not authenticated")
// ErrAuthFailed indicates authentication failed
ErrAuthFailed = errors.New("authentication failed")
// ErrConnectionLost indicates the WebSocket connection was lost
ErrConnectionLost = errors.New("connection lost")
// ErrTokenExpired indicates the authentication token has expired
ErrTokenExpired = errors.New("token expired")
// ErrInvalidResponse indicates an unexpected response from the API
ErrInvalidResponse = errors.New("invalid response from API")
// ErrSessionNotFound indicates the specified session does not exist
ErrSessionNotFound = errors.New("session not found")
// ErrNotSubscribed indicates not subscribed to any session
ErrNotSubscribed = errors.New("not subscribed to any session")
)
// APIError represents an API-related error with context
type APIError struct {
Op string // Operation that failed (e.g., "vote", "get_session", "authenticate")
StatusCode int // HTTP status code
Message string // Error message from API
Err error // Underlying error
}
func (e *APIError) Error() string {
if e.StatusCode > 0 {
return fmt.Sprintf("API error during %s (HTTP %d): %s", e.Op, e.StatusCode, e.Message)
}
if e.Err != nil {
return fmt.Sprintf("API error during %s: %s (%v)", e.Op, e.Message, e.Err)
}
return fmt.Sprintf("API error during %s: %s", e.Op, e.Message)
}
func (e *APIError) Unwrap() error {
return e.Err
}
// WebSocketError represents a WebSocket-related error with context
type WebSocketError struct {
Op string // Operation that failed (e.g., "connect", "subscribe", "send")
Err error // Underlying error
}
func (e *WebSocketError) Error() string {
return fmt.Sprintf("WebSocket error during %s: %v", e.Op, e.Err)
}
func (e *WebSocketError) Unwrap() error {
return e.Err
}
// SessionError represents a session-related error with context
type SessionError struct {
Op string // Operation that failed (e.g., "subscribe", "get_active")
SessionID int // Session ID
Err error // Underlying error
}
func (e *SessionError) Error() string {
return fmt.Sprintf("session error during %s for session %d: %v", e.Op, e.SessionID, e.Err)
}
func (e *SessionError) Unwrap() error {
return e.Err
}
// IsRetryable returns true if the error is transient and the operation should be retried
func IsRetryable(err error) bool {
if err == nil {
return false
}
// Check for known retryable errors
if errors.Is(err, ErrConnectionLost) ||
errors.Is(err, ErrTokenExpired) {
return true
}
// Check for API errors with retryable status codes
var apiErr *APIError
if errors.As(err, &apiErr) {
// 5xx errors are typically retryable
if apiErr.StatusCode >= 500 && apiErr.StatusCode < 600 {
return true
}
// 429 Too Many Requests is retryable
if apiErr.StatusCode == 429 {
return true
}
}
// WebSocket errors are generally retryable
var wsErr *WebSocketError
if errors.As(err, &wsErr) {
return true
}
return false
}
// IsFatal returns true if the error is fatal and reconnection should not be attempted
func IsFatal(err error) bool {
if err == nil {
return false
}
// Check for known fatal errors
if errors.Is(err, ErrAuthFailed) ||
errors.Is(err, ErrSessionNotFound) {
return true
}
// Check for API errors with fatal status codes
var apiErr *APIError
if errors.As(err, &apiErr) {
// 4xx errors (except 429) are typically fatal
if apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 && apiErr.StatusCode != 429 {
return true
}
}
return false
}