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