errors.go raw

   1  // Package errors provides domain-specific error types for the ORLY relay.
   2  // These typed errors enable structured error handling, machine-readable error codes,
   3  // and proper error categorization throughout the codebase.
   4  package errors
   5  
   6  import (
   7  	"fmt"
   8  )
   9  
  10  // DomainError is the base interface for all domain errors.
  11  // It extends the standard error interface with structured metadata.
  12  type DomainError interface {
  13  	error
  14  	Code() string      // Machine-readable error code (e.g., "INVALID_ID")
  15  	Category() string  // Error category for grouping (e.g., "validation")
  16  	IsRetryable() bool // Whether the operation can be retried
  17  }
  18  
  19  // Base provides common implementation for all domain errors.
  20  type Base struct {
  21  	code      string
  22  	category  string
  23  	message   string
  24  	retryable bool
  25  	cause     error
  26  }
  27  
  28  func (e *Base) Error() string {
  29  	if e.cause != nil {
  30  		return fmt.Sprintf("%s: %v", e.message, e.cause)
  31  	}
  32  	return e.message
  33  }
  34  
  35  func (e *Base) Code() string       { return e.code }
  36  func (e *Base) Category() string   { return e.category }
  37  func (e *Base) IsRetryable() bool  { return e.retryable }
  38  func (e *Base) Unwrap() error      { return e.cause }
  39  
  40  // WithCause returns a copy of the error with the given cause.
  41  func (e *Base) WithCause(cause error) *Base {
  42  	return &Base{
  43  		code:      e.code,
  44  		category:  e.category,
  45  		message:   e.message,
  46  		retryable: e.retryable,
  47  		cause:     cause,
  48  	}
  49  }
  50  
  51  // WithMessage returns a copy of the error with the given message.
  52  func (e *Base) WithMessage(msg string) *Base {
  53  	return &Base{
  54  		code:      e.code,
  55  		category:  e.category,
  56  		message:   msg,
  57  		retryable: e.retryable,
  58  		cause:     e.cause,
  59  	}
  60  }
  61  
  62  // =============================================================================
  63  // Validation Errors
  64  // =============================================================================
  65  
  66  // ValidationError represents an error in event validation.
  67  type ValidationError struct {
  68  	Base
  69  	Field string // The field that failed validation
  70  }
  71  
  72  // NewValidationError creates a new validation error.
  73  func NewValidationError(code, field, message string) *ValidationError {
  74  	return &ValidationError{
  75  		Base: Base{
  76  			code:     code,
  77  			category: "validation",
  78  			message:  message,
  79  		},
  80  		Field: field,
  81  	}
  82  }
  83  
  84  // WithField returns a copy with the specified field.
  85  func (e *ValidationError) WithField(field string) *ValidationError {
  86  	return &ValidationError{
  87  		Base:  e.Base,
  88  		Field: field,
  89  	}
  90  }
  91  
  92  // Validation error constants
  93  var (
  94  	ErrInvalidEventID = NewValidationError(
  95  		"INVALID_ID",
  96  		"id",
  97  		"event ID does not match computed hash",
  98  	)
  99  	ErrInvalidSignature = NewValidationError(
 100  		"INVALID_SIG",
 101  		"sig",
 102  		"signature verification failed",
 103  	)
 104  	ErrFutureTimestamp = NewValidationError(
 105  		"FUTURE_TS",
 106  		"created_at",
 107  		"timestamp too far in future",
 108  	)
 109  	ErrPastTimestamp = NewValidationError(
 110  		"PAST_TS",
 111  		"created_at",
 112  		"timestamp too far in past",
 113  	)
 114  	ErrUppercaseHex = NewValidationError(
 115  		"UPPERCASE_HEX",
 116  		"id/pubkey",
 117  		"hex values must be lowercase",
 118  	)
 119  	ErrProtectedTagMismatch = NewValidationError(
 120  		"PROTECTED_TAG",
 121  		"tags",
 122  		"protected event can only be modified by author",
 123  	)
 124  	ErrInvalidJSON = NewValidationError(
 125  		"INVALID_JSON",
 126  		"",
 127  		"malformed JSON",
 128  	)
 129  	ErrEventTooLarge = NewValidationError(
 130  		"EVENT_TOO_LARGE",
 131  		"content",
 132  		"event exceeds size limit",
 133  	)
 134  	ErrInvalidKind = NewValidationError(
 135  		"INVALID_KIND",
 136  		"kind",
 137  		"event kind not allowed",
 138  	)
 139  	ErrMissingTag = NewValidationError(
 140  		"MISSING_TAG",
 141  		"tags",
 142  		"required tag missing",
 143  	)
 144  	ErrInvalidTagValue = NewValidationError(
 145  		"INVALID_TAG",
 146  		"tags",
 147  		"tag value validation failed",
 148  	)
 149  )
 150  
 151  // =============================================================================
 152  // Authorization Errors
 153  // =============================================================================
 154  
 155  // AuthorizationError represents an authorization failure.
 156  type AuthorizationError struct {
 157  	Base
 158  	Pubkey      []byte // The pubkey that was denied
 159  	AccessLevel string // The access level that was required
 160  	RequireAuth bool   // Whether authentication might resolve this
 161  }
 162  
 163  // NeedsAuth returns true if authentication might resolve this error.
 164  func (e *AuthorizationError) NeedsAuth() bool { return e.RequireAuth }
 165  
 166  // NewAuthRequired creates an error indicating authentication is required.
 167  func NewAuthRequired(reason string) *AuthorizationError {
 168  	return &AuthorizationError{
 169  		Base: Base{
 170  			code:     "AUTH_REQUIRED",
 171  			category: "authorization",
 172  			message:  reason,
 173  		},
 174  		RequireAuth: true,
 175  	}
 176  }
 177  
 178  // NewAccessDenied creates an error indicating access was denied.
 179  func NewAccessDenied(level, reason string) *AuthorizationError {
 180  	return &AuthorizationError{
 181  		Base: Base{
 182  			code:     "ACCESS_DENIED",
 183  			category: "authorization",
 184  			message:  reason,
 185  		},
 186  		AccessLevel: level,
 187  	}
 188  }
 189  
 190  // WithPubkey returns a copy with the specified pubkey.
 191  func (e *AuthorizationError) WithPubkey(pubkey []byte) *AuthorizationError {
 192  	return &AuthorizationError{
 193  		Base:        e.Base,
 194  		Pubkey:      pubkey,
 195  		AccessLevel: e.AccessLevel,
 196  		RequireAuth: e.RequireAuth,
 197  	}
 198  }
 199  
 200  // Authorization error constants
 201  var (
 202  	ErrAuthRequired = NewAuthRequired("authentication required")
 203  	ErrBanned       = &AuthorizationError{
 204  		Base: Base{
 205  			code:     "BANNED",
 206  			category: "authorization",
 207  			message:  "pubkey banned",
 208  		},
 209  	}
 210  	ErrIPBlocked = &AuthorizationError{
 211  		Base: Base{
 212  			code:     "IP_BLOCKED",
 213  			category: "authorization",
 214  			message:  "IP address blocked",
 215  		},
 216  	}
 217  	ErrNotFollowed = &AuthorizationError{
 218  		Base: Base{
 219  			code:     "NOT_FOLLOWED",
 220  			category: "authorization",
 221  			message:  "write access requires being followed by admin",
 222  		},
 223  	}
 224  	ErrNotMember = &AuthorizationError{
 225  		Base: Base{
 226  			code:     "NOT_MEMBER",
 227  			category: "authorization",
 228  			message:  "membership required",
 229  		},
 230  	}
 231  	ErrInsufficientAccess = &AuthorizationError{
 232  		Base: Base{
 233  			code:     "INSUFFICIENT_ACCESS",
 234  			category: "authorization",
 235  			message:  "insufficient access level",
 236  		},
 237  	}
 238  )
 239  
 240  // =============================================================================
 241  // Processing Errors
 242  // =============================================================================
 243  
 244  // ProcessingError represents an error during event processing.
 245  type ProcessingError struct {
 246  	Base
 247  	EventID []byte // The event ID (if known)
 248  	Kind    uint16 // The event kind (if known)
 249  }
 250  
 251  // NewProcessingError creates a new processing error.
 252  func NewProcessingError(code string, message string, retryable bool) *ProcessingError {
 253  	return &ProcessingError{
 254  		Base: Base{
 255  			code:      code,
 256  			category:  "processing",
 257  			message:   message,
 258  			retryable: retryable,
 259  		},
 260  	}
 261  }
 262  
 263  // WithEventID returns a copy with the specified event ID.
 264  func (e *ProcessingError) WithEventID(id []byte) *ProcessingError {
 265  	return &ProcessingError{
 266  		Base:    e.Base,
 267  		EventID: id,
 268  		Kind:    e.Kind,
 269  	}
 270  }
 271  
 272  // WithKind returns a copy with the specified kind.
 273  func (e *ProcessingError) WithKind(kind uint16) *ProcessingError {
 274  	return &ProcessingError{
 275  		Base:    e.Base,
 276  		EventID: e.EventID,
 277  		Kind:    kind,
 278  	}
 279  }
 280  
 281  // Processing error constants
 282  var (
 283  	ErrDuplicate = NewProcessingError(
 284  		"DUPLICATE",
 285  		"event already exists",
 286  		false,
 287  	)
 288  	ErrReplaceNotAllowed = NewProcessingError(
 289  		"REPLACE_DENIED",
 290  		"cannot replace event from different author",
 291  		false,
 292  	)
 293  	ErrDeletedEvent = NewProcessingError(
 294  		"DELETED",
 295  		"event has been deleted",
 296  		false,
 297  	)
 298  	ErrEphemeralNotStored = NewProcessingError(
 299  		"EPHEMERAL",
 300  		"ephemeral events are not stored",
 301  		false,
 302  	)
 303  	ErrRateLimited = NewProcessingError(
 304  		"RATE_LIMITED",
 305  		"rate limit exceeded",
 306  		true,
 307  	)
 308  	ErrSprocketRejected = NewProcessingError(
 309  		"SPROCKET_REJECTED",
 310  		"rejected by sprocket",
 311  		false,
 312  	)
 313  )
 314  
 315  // =============================================================================
 316  // Policy Errors
 317  // =============================================================================
 318  
 319  // PolicyError represents a policy violation.
 320  type PolicyError struct {
 321  	Base
 322  	RuleName string // The rule that was violated
 323  	Action   string // The action taken (block, reject)
 324  }
 325  
 326  // NewPolicyBlocked creates an error for a blocked event.
 327  func NewPolicyBlocked(ruleName, reason string) *PolicyError {
 328  	return &PolicyError{
 329  		Base: Base{
 330  			code:     "POLICY_BLOCKED",
 331  			category: "policy",
 332  			message:  reason,
 333  		},
 334  		RuleName: ruleName,
 335  		Action:   "block",
 336  	}
 337  }
 338  
 339  // NewPolicyRejected creates an error for a rejected event.
 340  func NewPolicyRejected(ruleName, reason string) *PolicyError {
 341  	return &PolicyError{
 342  		Base: Base{
 343  			code:     "POLICY_REJECTED",
 344  			category: "policy",
 345  			message:  reason,
 346  		},
 347  		RuleName: ruleName,
 348  		Action:   "reject",
 349  	}
 350  }
 351  
 352  // Policy error constants
 353  var (
 354  	ErrKindBlocked = &PolicyError{
 355  		Base: Base{
 356  			code:     "KIND_BLOCKED",
 357  			category: "policy",
 358  			message:  "event kind not allowed by policy",
 359  		},
 360  		Action: "block",
 361  	}
 362  	ErrPubkeyBlocked = &PolicyError{
 363  		Base: Base{
 364  			code:     "PUBKEY_BLOCKED",
 365  			category: "policy",
 366  			message:  "pubkey blocked by policy",
 367  		},
 368  		Action: "block",
 369  	}
 370  	ErrContentBlocked = &PolicyError{
 371  		Base: Base{
 372  			code:     "CONTENT_BLOCKED",
 373  			category: "policy",
 374  			message:  "content blocked by policy",
 375  		},
 376  		Action: "block",
 377  	}
 378  	ErrScriptRejected = &PolicyError{
 379  		Base: Base{
 380  			code:     "SCRIPT_REJECTED",
 381  			category: "policy",
 382  			message:  "rejected by policy script",
 383  		},
 384  		Action: "reject",
 385  	}
 386  )
 387  
 388  // =============================================================================
 389  // Storage Errors
 390  // =============================================================================
 391  
 392  // StorageError represents a storage-layer error.
 393  type StorageError struct {
 394  	Base
 395  }
 396  
 397  // NewStorageError creates a new storage error.
 398  func NewStorageError(code, message string, cause error, retryable bool) *StorageError {
 399  	return &StorageError{
 400  		Base: Base{
 401  			code:      code,
 402  			category:  "storage",
 403  			message:   message,
 404  			cause:     cause,
 405  			retryable: retryable,
 406  		},
 407  	}
 408  }
 409  
 410  // Storage error constants
 411  var (
 412  	ErrDatabaseUnavailable = NewStorageError(
 413  		"DB_UNAVAILABLE",
 414  		"database not available",
 415  		nil,
 416  		true,
 417  	)
 418  	ErrWriteTimeout = NewStorageError(
 419  		"WRITE_TIMEOUT",
 420  		"write operation timed out",
 421  		nil,
 422  		true,
 423  	)
 424  	ErrReadTimeout = NewStorageError(
 425  		"READ_TIMEOUT",
 426  		"read operation timed out",
 427  		nil,
 428  		true,
 429  	)
 430  	ErrStorageFull = NewStorageError(
 431  		"STORAGE_FULL",
 432  		"storage capacity exceeded",
 433  		nil,
 434  		false,
 435  	)
 436  	ErrCorruptedData = NewStorageError(
 437  		"CORRUPTED_DATA",
 438  		"data corruption detected",
 439  		nil,
 440  		false,
 441  	)
 442  )
 443  
 444  // =============================================================================
 445  // Service Errors
 446  // =============================================================================
 447  
 448  // ServiceError represents a service-level error.
 449  type ServiceError struct {
 450  	Base
 451  	ServiceName string
 452  }
 453  
 454  // NewServiceError creates a new service error.
 455  func NewServiceError(code, service, message string, retryable bool) *ServiceError {
 456  	return &ServiceError{
 457  		Base: Base{
 458  			code:      code,
 459  			category:  "service",
 460  			message:   message,
 461  			retryable: retryable,
 462  		},
 463  		ServiceName: service,
 464  	}
 465  }
 466  
 467  // Service error constants
 468  var (
 469  	ErrServiceUnavailable = NewServiceError(
 470  		"SERVICE_UNAVAILABLE",
 471  		"",
 472  		"service temporarily unavailable",
 473  		true,
 474  	)
 475  	ErrServiceTimeout = NewServiceError(
 476  		"SERVICE_TIMEOUT",
 477  		"",
 478  		"service request timed out",
 479  		true,
 480  	)
 481  	ErrServiceOverloaded = NewServiceError(
 482  		"SERVICE_OVERLOADED",
 483  		"",
 484  		"service is overloaded",
 485  		true,
 486  	)
 487  )
 488  
 489  // =============================================================================
 490  // Helper Functions
 491  // =============================================================================
 492  
 493  // Is checks if an error matches a target domain error by code.
 494  func Is(err error, target DomainError) bool {
 495  	if de, ok := err.(DomainError); ok {
 496  		return de.Code() == target.Code()
 497  	}
 498  	return false
 499  }
 500  
 501  // Code returns the error code if err is a DomainError, empty string otherwise.
 502  func Code(err error) string {
 503  	if de, ok := err.(DomainError); ok {
 504  		return de.Code()
 505  	}
 506  	return ""
 507  }
 508  
 509  // Category returns the category of a domain error, or "unknown" for other errors.
 510  func Category(err error) string {
 511  	if de, ok := err.(DomainError); ok {
 512  		return de.Category()
 513  	}
 514  	return "unknown"
 515  }
 516  
 517  // IsRetryable checks if an error indicates the operation can be retried.
 518  func IsRetryable(err error) bool {
 519  	if de, ok := err.(DomainError); ok {
 520  		return de.IsRetryable()
 521  	}
 522  	return false
 523  }
 524  
 525  // IsValidation checks if an error is a validation error.
 526  func IsValidation(err error) bool {
 527  	_, ok := err.(*ValidationError)
 528  	return ok
 529  }
 530  
 531  // IsAuthorization checks if an error is an authorization error.
 532  func IsAuthorization(err error) bool {
 533  	_, ok := err.(*AuthorizationError)
 534  	return ok
 535  }
 536  
 537  // IsProcessing checks if an error is a processing error.
 538  func IsProcessing(err error) bool {
 539  	_, ok := err.(*ProcessingError)
 540  	return ok
 541  }
 542  
 543  // IsPolicy checks if an error is a policy error.
 544  func IsPolicy(err error) bool {
 545  	_, ok := err.(*PolicyError)
 546  	return ok
 547  }
 548  
 549  // IsStorage checks if an error is a storage error.
 550  func IsStorage(err error) bool {
 551  	_, ok := err.(*StorageError)
 552  	return ok
 553  }
 554  
 555  // NeedsAuth checks if an authorization error requires authentication.
 556  func NeedsAuth(err error) bool {
 557  	if ae, ok := err.(*AuthorizationError); ok {
 558  		return ae.NeedsAuth()
 559  	}
 560  	return false
 561  }
 562