You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

561 lines
13 KiB

// Package errors provides domain-specific error types for the ORLY relay.
// These typed errors enable structured error handling, machine-readable error codes,
// and proper error categorization throughout the codebase.
package errors
import (
"fmt"
)
// DomainError is the base interface for all domain errors.
// It extends the standard error interface with structured metadata.
type DomainError interface {
error
Code() string // Machine-readable error code (e.g., "INVALID_ID")
Category() string // Error category for grouping (e.g., "validation")
IsRetryable() bool // Whether the operation can be retried
}
// Base provides common implementation for all domain errors.
type Base struct {
code string
category string
message string
retryable bool
cause error
}
func (e *Base) Error() string {
if e.cause != nil {
return fmt.Sprintf("%s: %v", e.message, e.cause)
}
return e.message
}
func (e *Base) Code() string { return e.code }
func (e *Base) Category() string { return e.category }
func (e *Base) IsRetryable() bool { return e.retryable }
func (e *Base) Unwrap() error { return e.cause }
// WithCause returns a copy of the error with the given cause.
func (e *Base) WithCause(cause error) *Base {
return &Base{
code: e.code,
category: e.category,
message: e.message,
retryable: e.retryable,
cause: cause,
}
}
// WithMessage returns a copy of the error with the given message.
func (e *Base) WithMessage(msg string) *Base {
return &Base{
code: e.code,
category: e.category,
message: msg,
retryable: e.retryable,
cause: e.cause,
}
}
// =============================================================================
// Validation Errors
// =============================================================================
// ValidationError represents an error in event validation.
type ValidationError struct {
Base
Field string // The field that failed validation
}
// NewValidationError creates a new validation error.
func NewValidationError(code, field, message string) *ValidationError {
return &ValidationError{
Base: Base{
code: code,
category: "validation",
message: message,
},
Field: field,
}
}
// WithField returns a copy with the specified field.
func (e *ValidationError) WithField(field string) *ValidationError {
return &ValidationError{
Base: e.Base,
Field: field,
}
}
// Validation error constants
var (
ErrInvalidEventID = NewValidationError(
"INVALID_ID",
"id",
"event ID does not match computed hash",
)
ErrInvalidSignature = NewValidationError(
"INVALID_SIG",
"sig",
"signature verification failed",
)
ErrFutureTimestamp = NewValidationError(
"FUTURE_TS",
"created_at",
"timestamp too far in future",
)
ErrPastTimestamp = NewValidationError(
"PAST_TS",
"created_at",
"timestamp too far in past",
)
ErrUppercaseHex = NewValidationError(
"UPPERCASE_HEX",
"id/pubkey",
"hex values must be lowercase",
)
ErrProtectedTagMismatch = NewValidationError(
"PROTECTED_TAG",
"tags",
"protected event can only be modified by author",
)
ErrInvalidJSON = NewValidationError(
"INVALID_JSON",
"",
"malformed JSON",
)
ErrEventTooLarge = NewValidationError(
"EVENT_TOO_LARGE",
"content",
"event exceeds size limit",
)
ErrInvalidKind = NewValidationError(
"INVALID_KIND",
"kind",
"event kind not allowed",
)
ErrMissingTag = NewValidationError(
"MISSING_TAG",
"tags",
"required tag missing",
)
ErrInvalidTagValue = NewValidationError(
"INVALID_TAG",
"tags",
"tag value validation failed",
)
)
// =============================================================================
// Authorization Errors
// =============================================================================
// AuthorizationError represents an authorization failure.
type AuthorizationError struct {
Base
Pubkey []byte // The pubkey that was denied
AccessLevel string // The access level that was required
RequireAuth bool // Whether authentication might resolve this
}
// NeedsAuth returns true if authentication might resolve this error.
func (e *AuthorizationError) NeedsAuth() bool { return e.RequireAuth }
// NewAuthRequired creates an error indicating authentication is required.
func NewAuthRequired(reason string) *AuthorizationError {
return &AuthorizationError{
Base: Base{
code: "AUTH_REQUIRED",
category: "authorization",
message: reason,
},
RequireAuth: true,
}
}
// NewAccessDenied creates an error indicating access was denied.
func NewAccessDenied(level, reason string) *AuthorizationError {
return &AuthorizationError{
Base: Base{
code: "ACCESS_DENIED",
category: "authorization",
message: reason,
},
AccessLevel: level,
}
}
// WithPubkey returns a copy with the specified pubkey.
func (e *AuthorizationError) WithPubkey(pubkey []byte) *AuthorizationError {
return &AuthorizationError{
Base: e.Base,
Pubkey: pubkey,
AccessLevel: e.AccessLevel,
RequireAuth: e.RequireAuth,
}
}
// Authorization error constants
var (
ErrAuthRequired = NewAuthRequired("authentication required")
ErrBanned = &AuthorizationError{
Base: Base{
code: "BANNED",
category: "authorization",
message: "pubkey banned",
},
}
ErrIPBlocked = &AuthorizationError{
Base: Base{
code: "IP_BLOCKED",
category: "authorization",
message: "IP address blocked",
},
}
ErrNotFollowed = &AuthorizationError{
Base: Base{
code: "NOT_FOLLOWED",
category: "authorization",
message: "write access requires being followed by admin",
},
}
ErrNotMember = &AuthorizationError{
Base: Base{
code: "NOT_MEMBER",
category: "authorization",
message: "membership required",
},
}
ErrInsufficientAccess = &AuthorizationError{
Base: Base{
code: "INSUFFICIENT_ACCESS",
category: "authorization",
message: "insufficient access level",
},
}
)
// =============================================================================
// Processing Errors
// =============================================================================
// ProcessingError represents an error during event processing.
type ProcessingError struct {
Base
EventID []byte // The event ID (if known)
Kind uint16 // The event kind (if known)
}
// NewProcessingError creates a new processing error.
func NewProcessingError(code string, message string, retryable bool) *ProcessingError {
return &ProcessingError{
Base: Base{
code: code,
category: "processing",
message: message,
retryable: retryable,
},
}
}
// WithEventID returns a copy with the specified event ID.
func (e *ProcessingError) WithEventID(id []byte) *ProcessingError {
return &ProcessingError{
Base: e.Base,
EventID: id,
Kind: e.Kind,
}
}
// WithKind returns a copy with the specified kind.
func (e *ProcessingError) WithKind(kind uint16) *ProcessingError {
return &ProcessingError{
Base: e.Base,
EventID: e.EventID,
Kind: kind,
}
}
// Processing error constants
var (
ErrDuplicate = NewProcessingError(
"DUPLICATE",
"event already exists",
false,
)
ErrReplaceNotAllowed = NewProcessingError(
"REPLACE_DENIED",
"cannot replace event from different author",
false,
)
ErrDeletedEvent = NewProcessingError(
"DELETED",
"event has been deleted",
false,
)
ErrEphemeralNotStored = NewProcessingError(
"EPHEMERAL",
"ephemeral events are not stored",
false,
)
ErrRateLimited = NewProcessingError(
"RATE_LIMITED",
"rate limit exceeded",
true,
)
ErrSprocketRejected = NewProcessingError(
"SPROCKET_REJECTED",
"rejected by sprocket",
false,
)
)
// =============================================================================
// Policy Errors
// =============================================================================
// PolicyError represents a policy violation.
type PolicyError struct {
Base
RuleName string // The rule that was violated
Action string // The action taken (block, reject)
}
// NewPolicyBlocked creates an error for a blocked event.
func NewPolicyBlocked(ruleName, reason string) *PolicyError {
return &PolicyError{
Base: Base{
code: "POLICY_BLOCKED",
category: "policy",
message: reason,
},
RuleName: ruleName,
Action: "block",
}
}
// NewPolicyRejected creates an error for a rejected event.
func NewPolicyRejected(ruleName, reason string) *PolicyError {
return &PolicyError{
Base: Base{
code: "POLICY_REJECTED",
category: "policy",
message: reason,
},
RuleName: ruleName,
Action: "reject",
}
}
// Policy error constants
var (
ErrKindBlocked = &PolicyError{
Base: Base{
code: "KIND_BLOCKED",
category: "policy",
message: "event kind not allowed by policy",
},
Action: "block",
}
ErrPubkeyBlocked = &PolicyError{
Base: Base{
code: "PUBKEY_BLOCKED",
category: "policy",
message: "pubkey blocked by policy",
},
Action: "block",
}
ErrContentBlocked = &PolicyError{
Base: Base{
code: "CONTENT_BLOCKED",
category: "policy",
message: "content blocked by policy",
},
Action: "block",
}
ErrScriptRejected = &PolicyError{
Base: Base{
code: "SCRIPT_REJECTED",
category: "policy",
message: "rejected by policy script",
},
Action: "reject",
}
)
// =============================================================================
// Storage Errors
// =============================================================================
// StorageError represents a storage-layer error.
type StorageError struct {
Base
}
// NewStorageError creates a new storage error.
func NewStorageError(code, message string, cause error, retryable bool) *StorageError {
return &StorageError{
Base: Base{
code: code,
category: "storage",
message: message,
cause: cause,
retryable: retryable,
},
}
}
// Storage error constants
var (
ErrDatabaseUnavailable = NewStorageError(
"DB_UNAVAILABLE",
"database not available",
nil,
true,
)
ErrWriteTimeout = NewStorageError(
"WRITE_TIMEOUT",
"write operation timed out",
nil,
true,
)
ErrReadTimeout = NewStorageError(
"READ_TIMEOUT",
"read operation timed out",
nil,
true,
)
ErrStorageFull = NewStorageError(
"STORAGE_FULL",
"storage capacity exceeded",
nil,
false,
)
ErrCorruptedData = NewStorageError(
"CORRUPTED_DATA",
"data corruption detected",
nil,
false,
)
)
// =============================================================================
// Service Errors
// =============================================================================
// ServiceError represents a service-level error.
type ServiceError struct {
Base
ServiceName string
}
// NewServiceError creates a new service error.
func NewServiceError(code, service, message string, retryable bool) *ServiceError {
return &ServiceError{
Base: Base{
code: code,
category: "service",
message: message,
retryable: retryable,
},
ServiceName: service,
}
}
// Service error constants
var (
ErrServiceUnavailable = NewServiceError(
"SERVICE_UNAVAILABLE",
"",
"service temporarily unavailable",
true,
)
ErrServiceTimeout = NewServiceError(
"SERVICE_TIMEOUT",
"",
"service request timed out",
true,
)
ErrServiceOverloaded = NewServiceError(
"SERVICE_OVERLOADED",
"",
"service is overloaded",
true,
)
)
// =============================================================================
// Helper Functions
// =============================================================================
// Is checks if an error matches a target domain error by code.
func Is(err error, target DomainError) bool {
if de, ok := err.(DomainError); ok {
return de.Code() == target.Code()
}
return false
}
// Code returns the error code if err is a DomainError, empty string otherwise.
func Code(err error) string {
if de, ok := err.(DomainError); ok {
return de.Code()
}
return ""
}
// Category returns the category of a domain error, or "unknown" for other errors.
func Category(err error) string {
if de, ok := err.(DomainError); ok {
return de.Category()
}
return "unknown"
}
// IsRetryable checks if an error indicates the operation can be retried.
func IsRetryable(err error) bool {
if de, ok := err.(DomainError); ok {
return de.IsRetryable()
}
return false
}
// IsValidation checks if an error is a validation error.
func IsValidation(err error) bool {
_, ok := err.(*ValidationError)
return ok
}
// IsAuthorization checks if an error is an authorization error.
func IsAuthorization(err error) bool {
_, ok := err.(*AuthorizationError)
return ok
}
// IsProcessing checks if an error is a processing error.
func IsProcessing(err error) bool {
_, ok := err.(*ProcessingError)
return ok
}
// IsPolicy checks if an error is a policy error.
func IsPolicy(err error) bool {
_, ok := err.(*PolicyError)
return ok
}
// IsStorage checks if an error is a storage error.
func IsStorage(err error) bool {
_, ok := err.(*StorageError)
return ok
}
// NeedsAuth checks if an authorization error requires authentication.
func NeedsAuth(err error) bool {
if ae, ok := err.(*AuthorizationError); ok {
return ae.NeedsAuth()
}
return false
}