errors.mx raw

   1  // Package errors provides domain-specific error types for structured
   2  // error handling with machine-readable codes and categories.
   3  package errors
   4  
   5  import "fmt"
   6  
   7  // DomainError is the base interface for all domain errors.
   8  type DomainError interface {
   9  	error
  10  	Code() []byte
  11  	Category() []byte
  12  	IsRetryable() bool
  13  }
  14  
  15  // Base provides common fields for domain errors.
  16  type Base struct {
  17  	code      []byte
  18  	category  []byte
  19  	message   []byte
  20  	retryable bool
  21  	cause     error
  22  }
  23  
  24  func (e *Base) Error() string {
  25  	if e.cause != nil {
  26  		return string(append([]byte(nil), fmt.Sprintf("%s: %v", e.message, e.cause)...))
  27  	}
  28  	return string(e.message)
  29  }
  30  
  31  func (e *Base) Code() []byte      { return e.code }
  32  func (e *Base) Category() []byte  { return e.category }
  33  func (e *Base) IsRetryable() bool { return e.retryable }
  34  func (e *Base) Unwrap() error     { return e.cause }
  35  
  36  func (e *Base) WithCause(cause error) *Base {
  37  	return &Base{
  38  		code: e.code, category: e.category,
  39  		message: e.message, retryable: e.retryable, cause: cause,
  40  	}
  41  }
  42  
  43  func (e *Base) WithMessage(msg []byte) *Base {
  44  	return &Base{
  45  		code: e.code, category: e.category,
  46  		message: msg, retryable: e.retryable, cause: e.cause,
  47  	}
  48  }
  49  
  50  // --- Validation ---
  51  
  52  type ValidationError struct {
  53  	Base
  54  	Field []byte
  55  }
  56  
  57  func NewValidationError(code, field, message []byte) *ValidationError {
  58  	return &ValidationError{
  59  		Base:  Base{code: code, category: []byte("validation"), message: message},
  60  		Field: field,
  61  	}
  62  }
  63  
  64  func (e *ValidationError) WithField(field []byte) *ValidationError {
  65  	return &ValidationError{Base: e.Base, Field: field}
  66  }
  67  
  68  var (
  69  	ErrInvalidEventID       = NewValidationError([]byte("INVALID_ID"), []byte("id"), []byte("event ID does not match computed hash"))
  70  	ErrInvalidSignature     = NewValidationError([]byte("INVALID_SIG"), []byte("sig"), []byte("signature verification failed"))
  71  	ErrFutureTimestamp      = NewValidationError([]byte("FUTURE_TS"), []byte("created_at"), []byte("timestamp too far in future"))
  72  	ErrPastTimestamp        = NewValidationError([]byte("PAST_TS"), []byte("created_at"), []byte("timestamp too far in past"))
  73  	ErrUppercaseHex         = NewValidationError([]byte("UPPERCASE_HEX"), []byte("id/pubkey"), []byte("hex values must be lowercase"))
  74  	ErrProtectedTagMismatch = NewValidationError([]byte("PROTECTED_TAG"), []byte("tags"), []byte("protected event can only be modified by author"))
  75  	ErrInvalidJSON          = NewValidationError([]byte("INVALID_JSON"), nil, []byte("malformed JSON"))
  76  	ErrEventTooLarge        = NewValidationError([]byte("EVENT_TOO_LARGE"), []byte("content"), []byte("event exceeds size limit"))
  77  	ErrInvalidKind          = NewValidationError([]byte("INVALID_KIND"), []byte("kind"), []byte("event kind not allowed"))
  78  	ErrMissingTag           = NewValidationError([]byte("MISSING_TAG"), []byte("tags"), []byte("required tag missing"))
  79  	ErrInvalidTagValue      = NewValidationError([]byte("INVALID_TAG"), []byte("tags"), []byte("tag value validation failed"))
  80  )
  81  
  82  // --- Authorization ---
  83  
  84  type AuthorizationError struct {
  85  	Base
  86  	Pubkey      []byte
  87  	AccessLevel []byte
  88  	RequireAuth bool
  89  }
  90  
  91  func (e *AuthorizationError) NeedsAuth() bool { return e.RequireAuth }
  92  
  93  func NewAuthRequired(reason []byte) *AuthorizationError {
  94  	return &AuthorizationError{
  95  		Base:        Base{code: []byte("AUTH_REQUIRED"), category: []byte("authorization"), message: reason},
  96  		RequireAuth: true,
  97  	}
  98  }
  99  
 100  func NewAccessDenied(level, reason []byte) *AuthorizationError {
 101  	return &AuthorizationError{
 102  		Base:        Base{code: []byte("ACCESS_DENIED"), category: []byte("authorization"), message: reason},
 103  		AccessLevel: level,
 104  	}
 105  }
 106  
 107  func (e *AuthorizationError) WithPubkey(pubkey []byte) *AuthorizationError {
 108  	return &AuthorizationError{
 109  		Base: e.Base, Pubkey: pubkey,
 110  		AccessLevel: e.AccessLevel, RequireAuth: e.RequireAuth,
 111  	}
 112  }
 113  
 114  var (
 115  	ErrAuthRequired = NewAuthRequired([]byte("authentication required"))
 116  	ErrBanned       = &AuthorizationError{
 117  		Base: Base{code: []byte("BANNED"), category: []byte("authorization"), message: []byte("pubkey banned")},
 118  	}
 119  	ErrIPBlocked = &AuthorizationError{
 120  		Base: Base{code: []byte("IP_BLOCKED"), category: []byte("authorization"), message: []byte("IP address blocked")},
 121  	}
 122  	ErrNotFollowed = &AuthorizationError{
 123  		Base: Base{code: []byte("NOT_FOLLOWED"), category: []byte("authorization"), message: []byte("write access requires being followed by admin")},
 124  	}
 125  	ErrNotMember = &AuthorizationError{
 126  		Base: Base{code: []byte("NOT_MEMBER"), category: []byte("authorization"), message: []byte("membership required")},
 127  	}
 128  	ErrInsufficientAccess = &AuthorizationError{
 129  		Base: Base{code: []byte("INSUFFICIENT_ACCESS"), category: []byte("authorization"), message: []byte("insufficient access level")},
 130  	}
 131  )
 132  
 133  // --- Processing ---
 134  
 135  type ProcessingError struct {
 136  	Base
 137  	EventID []byte
 138  	Kind    uint16
 139  }
 140  
 141  func NewProcessingError(code, message []byte, retryable bool) *ProcessingError {
 142  	return &ProcessingError{
 143  		Base: Base{code: code, category: []byte("processing"), message: message, retryable: retryable},
 144  	}
 145  }
 146  
 147  func (e *ProcessingError) WithEventID(id []byte) *ProcessingError {
 148  	return &ProcessingError{Base: e.Base, EventID: id, Kind: e.Kind}
 149  }
 150  
 151  func (e *ProcessingError) WithKind(kind uint16) *ProcessingError {
 152  	return &ProcessingError{Base: e.Base, EventID: e.EventID, Kind: kind}
 153  }
 154  
 155  var (
 156  	ErrDuplicate          = NewProcessingError([]byte("DUPLICATE"), []byte("event already exists"), false)
 157  	ErrReplaceNotAllowed  = NewProcessingError([]byte("REPLACE_DENIED"), []byte("cannot replace event from different author"), false)
 158  	ErrDeletedEvent       = NewProcessingError([]byte("DELETED"), []byte("event has been deleted"), false)
 159  	ErrEphemeralNotStored = NewProcessingError([]byte("EPHEMERAL"), []byte("ephemeral events are not stored"), false)
 160  	ErrRateLimited        = NewProcessingError([]byte("RATE_LIMITED"), []byte("rate limit exceeded"), true)
 161  )
 162  
 163  // --- Policy ---
 164  
 165  type PolicyError struct {
 166  	Base
 167  	RuleName []byte
 168  	Action   []byte
 169  }
 170  
 171  func NewPolicyBlocked(ruleName, reason []byte) *PolicyError {
 172  	return &PolicyError{
 173  		Base:     Base{code: []byte("POLICY_BLOCKED"), category: []byte("policy"), message: reason},
 174  		RuleName: ruleName, Action: []byte("block"),
 175  	}
 176  }
 177  
 178  func NewPolicyRejected(ruleName, reason []byte) *PolicyError {
 179  	return &PolicyError{
 180  		Base:     Base{code: []byte("POLICY_REJECTED"), category: []byte("policy"), message: reason},
 181  		RuleName: ruleName, Action: []byte("reject"),
 182  	}
 183  }
 184  
 185  var (
 186  	ErrKindBlocked = &PolicyError{
 187  		Base: Base{code: []byte("KIND_BLOCKED"), category: []byte("policy"), message: []byte("event kind not allowed by policy")},
 188  		Action: []byte("block"),
 189  	}
 190  	ErrPubkeyBlocked = &PolicyError{
 191  		Base: Base{code: []byte("PUBKEY_BLOCKED"), category: []byte("policy"), message: []byte("pubkey blocked by policy")},
 192  		Action: []byte("block"),
 193  	}
 194  	ErrContentBlocked = &PolicyError{
 195  		Base: Base{code: []byte("CONTENT_BLOCKED"), category: []byte("policy"), message: []byte("content blocked by policy")},
 196  		Action: []byte("block"),
 197  	}
 198  )
 199  
 200  // --- Storage ---
 201  
 202  type StorageError struct{ Base }
 203  
 204  func NewStorageError(code, message []byte, cause error, retryable bool) *StorageError {
 205  	return &StorageError{Base: Base{code: code, category: []byte("storage"), message: message, cause: cause, retryable: retryable}}
 206  }
 207  
 208  var (
 209  	ErrDatabaseUnavailable = NewStorageError([]byte("DB_UNAVAILABLE"), []byte("database not available"), nil, true)
 210  	ErrWriteTimeout        = NewStorageError([]byte("WRITE_TIMEOUT"), []byte("write operation timed out"), nil, true)
 211  	ErrReadTimeout         = NewStorageError([]byte("READ_TIMEOUT"), []byte("read operation timed out"), nil, true)
 212  	ErrStorageFull         = NewStorageError([]byte("STORAGE_FULL"), []byte("storage capacity exceeded"), nil, false)
 213  	ErrCorruptedData       = NewStorageError([]byte("CORRUPTED_DATA"), []byte("data corruption detected"), nil, false)
 214  )
 215  
 216  // --- Service ---
 217  
 218  type ServiceError struct {
 219  	Base
 220  	ServiceName []byte
 221  }
 222  
 223  func NewServiceError(code, service, message []byte, retryable bool) *ServiceError {
 224  	return &ServiceError{
 225  		Base:        Base{code: code, category: []byte("service"), message: message, retryable: retryable},
 226  		ServiceName: service,
 227  	}
 228  }
 229  
 230  var (
 231  	ErrServiceUnavailable = NewServiceError([]byte("SERVICE_UNAVAILABLE"), nil, []byte("service temporarily unavailable"), true)
 232  	ErrServiceTimeout     = NewServiceError([]byte("SERVICE_TIMEOUT"), nil, []byte("service request timed out"), true)
 233  	ErrServiceOverloaded  = NewServiceError([]byte("SERVICE_OVERLOADED"), nil, []byte("service is overloaded"), true)
 234  )
 235  
 236  // --- Helpers ---
 237  
 238  func Is(err error, target DomainError) bool {
 239  	if de, ok := err.(DomainError); ok {
 240  		return bytesEqual(de.Code(), target.Code())
 241  	}
 242  	return false
 243  }
 244  
 245  func Code(err error) []byte {
 246  	if de, ok := err.(DomainError); ok {
 247  		return de.Code()
 248  	}
 249  	return nil
 250  }
 251  
 252  func Category(err error) []byte {
 253  	if de, ok := err.(DomainError); ok {
 254  		return de.Category()
 255  	}
 256  	return []byte("unknown")
 257  }
 258  
 259  func IsRetryable(err error) bool {
 260  	if de, ok := err.(DomainError); ok {
 261  		return de.IsRetryable()
 262  	}
 263  	return false
 264  }
 265  
 266  func IsValidation(err error) bool     { _, ok := err.(*ValidationError); return ok }
 267  func IsAuthorization(err error) bool   { _, ok := err.(*AuthorizationError); return ok }
 268  func IsProcessing(err error) bool      { _, ok := err.(*ProcessingError); return ok }
 269  func IsPolicy(err error) bool          { _, ok := err.(*PolicyError); return ok }
 270  func IsStorage(err error) bool         { _, ok := err.(*StorageError); return ok }
 271  
 272  func NeedsAuth(err error) bool {
 273  	if ae, ok := err.(*AuthorizationError); ok {
 274  		return ae.NeedsAuth()
 275  	}
 276  	return false
 277  }
 278  
 279  func bytesEqual(a, b []byte) bool {
 280  	if len(a) != len(b) {
 281  		return false
 282  	}
 283  	for i := range a {
 284  		if a[i] != b[i] {
 285  			return false
 286  		}
 287  	}
 288  	return true
 289  }
 290