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