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