awesome-errors 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. awesome_errors/__init__.py +232 -0
  2. awesome_errors/analysis/__init__.py +8 -0
  3. awesome_errors/analysis/decorators.py +251 -0
  4. awesome_errors/analysis/error_analyzer.py +463 -0
  5. awesome_errors/client/__init__.py +7 -0
  6. awesome_errors/client/exceptions.py +97 -0
  7. awesome_errors/client/response_parser.py +175 -0
  8. awesome_errors/converters/__init__.py +31 -0
  9. awesome_errors/converters/generic.py +23 -0
  10. awesome_errors/converters/pydantic_converter.py +75 -0
  11. awesome_errors/converters/python_converter.py +80 -0
  12. awesome_errors/converters/sql_converter.py +258 -0
  13. awesome_errors/converters/universal_converter.py +123 -0
  14. awesome_errors/core/__init__.py +0 -0
  15. awesome_errors/core/error_codes.py +104 -0
  16. awesome_errors/core/error_response.py +61 -0
  17. awesome_errors/core/exceptions.py +613 -0
  18. awesome_errors/core/renderers.py +115 -0
  19. awesome_errors/i18n/__init__.py +0 -0
  20. awesome_errors/i18n/locales/en/errors.json +32 -0
  21. awesome_errors/i18n/locales/test/errors.json +4 -0
  22. awesome_errors/i18n/locales/uk/errors.json +26 -0
  23. awesome_errors/i18n/translator.py +150 -0
  24. awesome_errors/integrations/__init__.py +11 -0
  25. awesome_errors/integrations/fastapi_auto_docs.py +220 -0
  26. awesome_errors/litestar_utils.py +55 -0
  27. awesome_errors/middleware/__init__.py +0 -0
  28. awesome_errors/middleware/fastapi.py +247 -0
  29. awesome_errors/middleware/litestar.py +333 -0
  30. awesome_errors/py.typed +1 -0
  31. awesome_errors/websocket/__init__.py +35 -0
  32. awesome_errors/websocket/error_handler.py +231 -0
  33. awesome_errors/websocket/exceptions.py +286 -0
  34. awesome_errors-0.3.0.dist-info/METADATA +239 -0
  35. awesome_errors-0.3.0.dist-info/RECORD +37 -0
  36. awesome_errors-0.3.0.dist-info/WHEEL +4 -0
  37. awesome_errors-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,613 @@
1
+ from typing import Any, ClassVar, Dict, Optional, Union, TYPE_CHECKING
2
+ from datetime import datetime, timezone
3
+ import uuid
4
+
5
+ from .error_codes import ErrorCode, get_http_status
6
+
7
+ if TYPE_CHECKING:
8
+ from litestar.openapi.datastructures import ResponseSpec
9
+
10
+
11
+ class AppError(Exception):
12
+ """
13
+ Base application error class for server-side error handling.
14
+
15
+ This is the core exception class used throughout the application.
16
+ It provides standardized error structure with:
17
+ - Error code (from ErrorCode enum or custom string)
18
+ - Human-readable message
19
+ - Additional details dictionary
20
+ - Timestamp and request ID for tracing
21
+
22
+ Usage:
23
+ raise AppError(ErrorCode.USER_NOT_FOUND, "User not found", {"user_id": 123})
24
+ """
25
+
26
+ code: ErrorCode
27
+ message: str
28
+ details: Dict[str, Any]
29
+ timestamp: datetime
30
+ request_id: str | None
31
+ status_code: int
32
+
33
+ def __init__(
34
+ self,
35
+ code: Union[ErrorCode, str],
36
+ message: str,
37
+ details: Optional[Dict[str, Any]] = None,
38
+ status_code: Optional[int] = None,
39
+ ):
40
+ super().__init__(message)
41
+ self.code = code if isinstance(code, ErrorCode) else ErrorCode(code)
42
+ self.message = message
43
+ self.details = details or {}
44
+ self.timestamp = datetime.now(timezone.utc)
45
+ self.request_id = str(uuid.uuid4())
46
+
47
+ # Use provided status code or get from mapping
48
+ self.status_code = status_code or get_http_status(self.code)
49
+
50
+ def to_dict(self) -> Dict[str, Any]:
51
+ """Convert error to dictionary for JSON serialization."""
52
+ return {
53
+ "error": {
54
+ "code": self.code.value,
55
+ "message": self.message,
56
+ "details": self.details,
57
+ "timestamp": self.timestamp.isoformat() + "Z",
58
+ "request_id": self.request_id,
59
+ }
60
+ }
61
+
62
+
63
+ class APIError(AppError):
64
+ """Base HTTP-facing error with OpenAPI metadata."""
65
+
66
+ error_code: ClassVar[Union[ErrorCode, str]] = ErrorCode.UNKNOWN_ERROR
67
+ http_status_code: ClassVar[int | None] = None
68
+ title: ClassVar[str] = "Internal error"
69
+ description: ClassVar[str] = "An internal error occurred"
70
+ response_media_type: ClassVar[str] = "application/problem+json"
71
+
72
+ def __init__(
73
+ self,
74
+ code: Union[ErrorCode, str] | None = None,
75
+ message: Optional[str] = None,
76
+ details: Optional[Dict[str, Any]] = None,
77
+ status_code: Optional[int] = None,
78
+ ):
79
+ effective_code = code or self.error_code
80
+ if status_code is not None:
81
+ effective_status = status_code
82
+ elif code is not None:
83
+ normalized = (
84
+ effective_code
85
+ if isinstance(effective_code, ErrorCode)
86
+ else ErrorCode(effective_code)
87
+ )
88
+ effective_status = get_http_status(normalized)
89
+ else:
90
+ normalized = (
91
+ effective_code
92
+ if isinstance(effective_code, ErrorCode)
93
+ else ErrorCode(effective_code)
94
+ )
95
+ effective_status = (
96
+ self.http_status_code
97
+ if self.http_status_code is not None
98
+ else get_http_status(normalized)
99
+ )
100
+ super().__init__(
101
+ effective_code,
102
+ message or self.title,
103
+ details,
104
+ effective_status,
105
+ )
106
+
107
+ @classmethod
108
+ def get_status_code(cls) -> int:
109
+ if cls.http_status_code is not None:
110
+ return cls.http_status_code
111
+ error_code = (
112
+ cls.error_code if isinstance(cls.error_code, ErrorCode) else ErrorCode(cls.error_code)
113
+ )
114
+ return get_http_status(error_code)
115
+
116
+ @classmethod
117
+ def openapi_response(cls) -> "ResponseSpec": # type: ignore[name-defined]
118
+ try:
119
+ from litestar.openapi.datastructures import ResponseSpec # type: ignore
120
+ except ImportError as exc: # pragma: no cover - optional dependency
121
+ msg = "Litestar is required to build OpenAPI response specs"
122
+ raise RuntimeError(msg) from exc
123
+
124
+ return ResponseSpec(
125
+ data_container=None,
126
+ description=cls.description,
127
+ media_type=cls.response_media_type,
128
+ )
129
+
130
+
131
+ class ValidationError(APIError):
132
+ """
133
+ Validation error (HTTP 400) for input validation failures.
134
+
135
+ Used when user input fails validation rules.
136
+ Can include field-specific information.
137
+
138
+ Usage:
139
+ raise ValidationError("Email is required", field="email")
140
+ """
141
+
142
+ error_code: ClassVar[ErrorCode] = ErrorCode.VALIDATION_FAILED
143
+ http_status_code: ClassVar[int] = 400
144
+ title: ClassVar[str] = "Request validation failed"
145
+ description: ClassVar[str] = "Request validation failed"
146
+
147
+ def __init__(
148
+ self,
149
+ message: Optional[str] = None,
150
+ field: Optional[str] = None,
151
+ value: Any = None,
152
+ code: ErrorCode = ErrorCode.VALIDATION_FAILED,
153
+ ):
154
+ details: Dict[str, Any] = {}
155
+ if field:
156
+ details["field"] = field
157
+ if value is not None:
158
+ details["value"] = value
159
+
160
+ super().__init__(code, message, details, 400)
161
+
162
+
163
+ class InvalidInputError(ValidationError):
164
+ """Invalid input error (HTTP 400)."""
165
+
166
+ error_code: ClassVar[ErrorCode] = ErrorCode.INVALID_INPUT
167
+ title: ClassVar[str] = "Invalid input"
168
+ description: ClassVar[str] = "Invalid input"
169
+
170
+ def __init__(self, message: Optional[str] = None, field: Optional[str] = None):
171
+ super().__init__(message or self.title, field=field, code=self.error_code)
172
+
173
+
174
+ class MissingRequiredFieldError(ValidationError):
175
+ """Missing required field error (HTTP 400)."""
176
+
177
+ error_code: ClassVar[ErrorCode] = ErrorCode.MISSING_REQUIRED_FIELD
178
+ title: ClassVar[str] = "Missing required field"
179
+ description: ClassVar[str] = "Missing required field"
180
+
181
+ def __init__(self, message: Optional[str] = None, field: Optional[str] = None):
182
+ super().__init__(message or self.title, field=field, code=self.error_code)
183
+
184
+
185
+ class InvalidFormatError(ValidationError):
186
+ """Invalid format error (HTTP 400)."""
187
+
188
+ error_code: ClassVar[ErrorCode] = ErrorCode.INVALID_FORMAT
189
+ title: ClassVar[str] = "Invalid format"
190
+ description: ClassVar[str] = "Invalid format"
191
+
192
+ def __init__(self, message: Optional[str] = None, field: Optional[str] = None):
193
+ super().__init__(message or self.title, field=field, code=self.error_code)
194
+
195
+
196
+ class AuthError(APIError):
197
+ """
198
+ Authentication/Authorization error (HTTP 401/403).
199
+
200
+ Used for authentication and authorization failures.
201
+ Can include required permission information.
202
+
203
+ Usage:
204
+ raise AuthError("Admin access required", required_permission="admin.users.read")
205
+ """
206
+
207
+ error_code: ClassVar[ErrorCode] = ErrorCode.AUTH_REQUIRED
208
+ http_status_code: ClassVar[int] = 401
209
+ title: ClassVar[str] = "Authentication required"
210
+ description: ClassVar[str] = "Authentication required"
211
+
212
+ def __init__(
213
+ self,
214
+ message: Optional[str] = None,
215
+ code: ErrorCode | None = None,
216
+ required_permission: Optional[str] = None,
217
+ status_code: Optional[int] = None,
218
+ ):
219
+ details: Dict[str, Any] = {}
220
+ if required_permission:
221
+ details["required_permission"] = required_permission
222
+
223
+ super().__init__(code, message, details, status_code)
224
+
225
+
226
+ class AuthRequiredError(AuthError):
227
+ """Authentication required error (HTTP 401)."""
228
+
229
+ error_code: ClassVar[ErrorCode] = ErrorCode.AUTH_REQUIRED
230
+ title: ClassVar[str] = "Authentication required"
231
+ description: ClassVar[str] = "Authentication required"
232
+
233
+ def __init__(
234
+ self, message: Optional[str] = None, required_permission: Optional[str] = None
235
+ ):
236
+ super().__init__(
237
+ message,
238
+ code=self.error_code,
239
+ required_permission=required_permission,
240
+ )
241
+
242
+
243
+ class AuthInvalidTokenError(AuthError):
244
+ """Invalid token error (HTTP 401)."""
245
+
246
+ error_code: ClassVar[ErrorCode] = ErrorCode.AUTH_INVALID_TOKEN
247
+ title: ClassVar[str] = "Invalid token"
248
+ description: ClassVar[str] = "Invalid token"
249
+
250
+ def __init__(self, message: Optional[str] = None):
251
+ super().__init__(message, code=self.error_code)
252
+
253
+
254
+ class AuthTokenExpiredError(AuthError):
255
+ """Token expired error (HTTP 401)."""
256
+
257
+ error_code: ClassVar[ErrorCode] = ErrorCode.AUTH_TOKEN_EXPIRED
258
+ title: ClassVar[str] = "Token expired"
259
+ description: ClassVar[str] = "Token expired"
260
+
261
+ def __init__(self, message: Optional[str] = None):
262
+ super().__init__(message, code=self.error_code)
263
+
264
+
265
+ class AuthPermissionDeniedError(AuthError):
266
+ """Permission denied error (HTTP 403)."""
267
+
268
+ error_code: ClassVar[ErrorCode] = ErrorCode.AUTH_PERMISSION_DENIED
269
+ http_status_code: ClassVar[int] = 403
270
+ title: ClassVar[str] = "Access denied"
271
+ description: ClassVar[str] = "Access denied"
272
+
273
+ def __init__(
274
+ self, message: Optional[str] = None, required_permission: Optional[str] = None
275
+ ):
276
+ super().__init__(
277
+ message,
278
+ code=self.error_code,
279
+ required_permission=required_permission,
280
+ status_code=self.http_status_code,
281
+ )
282
+
283
+
284
+ class AuthInsufficientPrivilegesError(AuthError):
285
+ """Insufficient privileges error (HTTP 403)."""
286
+
287
+ error_code: ClassVar[ErrorCode] = ErrorCode.AUTH_INSUFFICIENT_PRIVILEGES
288
+ http_status_code: ClassVar[int] = 403
289
+ title: ClassVar[str] = "Insufficient privileges"
290
+ description: ClassVar[str] = "Insufficient privileges"
291
+
292
+ def __init__(
293
+ self, message: Optional[str] = None, required_permission: Optional[str] = None
294
+ ):
295
+ super().__init__(
296
+ message,
297
+ code=self.error_code,
298
+ required_permission=required_permission,
299
+ status_code=self.http_status_code,
300
+ )
301
+
302
+
303
+ class SessionExpiredError(AuthError):
304
+ """Session expired error (HTTP 401)."""
305
+
306
+ error_code: ClassVar[ErrorCode] = ErrorCode.SESSION_EXPIRED
307
+ title: ClassVar[str] = "Session has expired"
308
+ description: ClassVar[str] = "Session has expired"
309
+
310
+ def __init__(self, message: Optional[str] = None):
311
+ super().__init__(message or self.title, code=self.error_code)
312
+
313
+
314
+ class RefreshTokenReuseDetectedError(AuthError):
315
+ """Refresh token reuse detected (HTTP 401)."""
316
+
317
+ error_code: ClassVar[ErrorCode] = ErrorCode.REFRESH_TOKEN_REUSE
318
+ title: ClassVar[str] = "Refresh token reuse detected"
319
+ description: ClassVar[str] = "Refresh token reuse detected"
320
+
321
+ def __init__(self, message: Optional[str] = None):
322
+ super().__init__(message or self.title, code=self.error_code)
323
+
324
+
325
+ class NotFoundError(APIError):
326
+ """
327
+ Resource not found error (HTTP 404).
328
+
329
+ Used when a requested resource doesn't exist.
330
+ Includes resource type and ID information.
331
+
332
+ Usage:
333
+ raise NotFoundError("user", user_id=123)
334
+ """
335
+
336
+ error_code: ClassVar[ErrorCode] = ErrorCode.RESOURCE_NOT_FOUND
337
+ http_status_code: ClassVar[int] = 404
338
+ title: ClassVar[str] = "Resource not found"
339
+ description: ClassVar[str] = "Resource not found"
340
+
341
+ def __init__(
342
+ self,
343
+ resource: str,
344
+ resource_id: Optional[Union[str, int]] = None,
345
+ code: ErrorCode = ErrorCode.RESOURCE_NOT_FOUND,
346
+ ):
347
+ details: Dict[str, Any] = {"resource": resource}
348
+ if resource_id is not None:
349
+ details["resource_id"] = resource_id
350
+
351
+ message = f"{resource} not found"
352
+ if resource_id:
353
+ message += f" with id: {resource_id}"
354
+
355
+ super().__init__(code, message, details, 404)
356
+
357
+
358
+ class ResourceNotFoundError(NotFoundError):
359
+ """Resource not found error (HTTP 404)."""
360
+
361
+ error_code: ClassVar[ErrorCode] = ErrorCode.RESOURCE_NOT_FOUND
362
+ title: ClassVar[str] = "Resource not found"
363
+ description: ClassVar[str] = "Resource not found"
364
+
365
+ def __init__(self, resource: str, resource_id: Optional[Union[str, int]] = None):
366
+ super().__init__(resource, resource_id=resource_id, code=self.error_code)
367
+
368
+
369
+ class UserNotFoundError(NotFoundError):
370
+ """User not found error (HTTP 404)."""
371
+
372
+ error_code: ClassVar[ErrorCode] = ErrorCode.USER_NOT_FOUND
373
+ title: ClassVar[str] = "User not found"
374
+ description: ClassVar[str] = "User not found"
375
+
376
+ def __init__(self, user_id: Optional[Union[str, int]] = None):
377
+ super().__init__("user", resource_id=user_id, code=self.error_code)
378
+
379
+
380
+ class EntityNotFoundError(NotFoundError):
381
+ """Entity not found error (HTTP 404)."""
382
+
383
+ error_code: ClassVar[ErrorCode] = ErrorCode.ENTITY_NOT_FOUND
384
+ title: ClassVar[str] = "Entity not found"
385
+ description: ClassVar[str] = "Entity not found"
386
+
387
+ def __init__(self, entity: str, entity_id: Optional[Union[str, int]] = None):
388
+ super().__init__(entity, resource_id=entity_id, code=self.error_code)
389
+
390
+
391
+ class OAuthProviderUnknownError(NotFoundError):
392
+ """OAuth provider not found error (HTTP 404)."""
393
+
394
+ error_code: ClassVar[ErrorCode] = ErrorCode.OAUTH_PROVIDER_UNKNOWN
395
+ title: ClassVar[str] = "OAuth provider not found"
396
+ description: ClassVar[str] = "OAuth provider not found"
397
+
398
+ def __init__(self, provider: str):
399
+ super().__init__("oauth_provider", resource_id=provider, code=self.error_code)
400
+
401
+
402
+ class DatabaseError(APIError):
403
+ """
404
+ Database error (HTTP 500/409/422).
405
+
406
+ Used for database-related errors like constraint violations,
407
+ connection issues, or query failures.
408
+
409
+ Usage:
410
+ raise DatabaseError("Duplicate email", table="users", sql_error="...")
411
+ """
412
+
413
+ error_code: ClassVar[ErrorCode] = ErrorCode.DB_QUERY_ERROR
414
+ title: ClassVar[str] = "Database query error"
415
+ description: ClassVar[str] = "Database query error"
416
+
417
+ def __init__(
418
+ self,
419
+ message: Optional[str] = None,
420
+ code: ErrorCode = ErrorCode.DB_QUERY_ERROR,
421
+ sql_error: Optional[str] = None,
422
+ table: Optional[str] = None,
423
+ ):
424
+ details: Dict[str, Any] = {}
425
+ if sql_error:
426
+ details["sql_error"] = sql_error
427
+ if table:
428
+ details["table"] = table
429
+
430
+ super().__init__(code, message, details)
431
+
432
+
433
+ class DatabaseConnectionError(DatabaseError):
434
+ """Database connection error (HTTP 500)."""
435
+
436
+ error_code: ClassVar[ErrorCode] = ErrorCode.DB_CONNECTION_ERROR
437
+ title: ClassVar[str] = "Database connection error"
438
+ description: ClassVar[str] = "Database connection error"
439
+
440
+ def __init__(self, message: Optional[str] = None, sql_error: Optional[str] = None):
441
+ super().__init__(message or self.title, code=self.error_code, sql_error=sql_error)
442
+
443
+
444
+ class DatabaseQueryError(DatabaseError):
445
+ """Database query error (HTTP 500)."""
446
+
447
+ error_code: ClassVar[ErrorCode] = ErrorCode.DB_QUERY_ERROR
448
+ title: ClassVar[str] = "Database query error"
449
+ description: ClassVar[str] = "Database query error"
450
+
451
+ def __init__(self, message: Optional[str] = None, sql_error: Optional[str] = None):
452
+ super().__init__(message or self.title, code=self.error_code, sql_error=sql_error)
453
+
454
+
455
+ class DatabaseTransactionError(DatabaseError):
456
+ """Database transaction error (HTTP 500)."""
457
+
458
+ error_code: ClassVar[ErrorCode] = ErrorCode.DB_TRANSACTION_ERROR
459
+ title: ClassVar[str] = "Database transaction error"
460
+ description: ClassVar[str] = "Database transaction error"
461
+
462
+ def __init__(self, message: Optional[str] = None, sql_error: Optional[str] = None):
463
+ super().__init__(message or self.title, code=self.error_code, sql_error=sql_error)
464
+
465
+
466
+ class DatabaseConstraintViolationError(DatabaseError):
467
+ """Database constraint violation (HTTP 409)."""
468
+
469
+ error_code: ClassVar[ErrorCode] = ErrorCode.DB_CONSTRAINT_VIOLATION
470
+ title: ClassVar[str] = "Database constraint violation"
471
+ description: ClassVar[str] = "Database constraint violation"
472
+
473
+ def __init__(
474
+ self,
475
+ message: Optional[str] = None,
476
+ sql_error: Optional[str] = None,
477
+ table: Optional[str] = None,
478
+ ):
479
+ super().__init__(
480
+ message or self.title,
481
+ code=self.error_code,
482
+ sql_error=sql_error,
483
+ table=table,
484
+ )
485
+
486
+
487
+ class DatabaseDuplicateEntryError(DatabaseError):
488
+ """Database duplicate entry (HTTP 409)."""
489
+
490
+ error_code: ClassVar[ErrorCode] = ErrorCode.DB_DUPLICATE_ENTRY
491
+ title: ClassVar[str] = "Database duplicate entry"
492
+ description: ClassVar[str] = "Database duplicate entry"
493
+
494
+ def __init__(
495
+ self,
496
+ message: Optional[str] = None,
497
+ sql_error: Optional[str] = None,
498
+ table: Optional[str] = None,
499
+ ):
500
+ super().__init__(
501
+ message or self.title,
502
+ code=self.error_code,
503
+ sql_error=sql_error,
504
+ table=table,
505
+ )
506
+
507
+
508
+ class DatabaseInvalidReferenceError(DatabaseError):
509
+ """Database invalid reference (HTTP 422)."""
510
+
511
+ error_code: ClassVar[ErrorCode] = ErrorCode.DB_INVALID_REFERENCE
512
+ title: ClassVar[str] = "Database invalid reference"
513
+ description: ClassVar[str] = "Database invalid reference"
514
+
515
+ def __init__(
516
+ self,
517
+ message: Optional[str] = None,
518
+ sql_error: Optional[str] = None,
519
+ table: Optional[str] = None,
520
+ ):
521
+ super().__init__(
522
+ message or self.title,
523
+ code=self.error_code,
524
+ sql_error=sql_error,
525
+ table=table,
526
+ )
527
+
528
+
529
+ class DatabaseMissingRequiredError(DatabaseError):
530
+ """Database missing required field (HTTP 422)."""
531
+
532
+ error_code: ClassVar[ErrorCode] = ErrorCode.DB_MISSING_REQUIRED
533
+ title: ClassVar[str] = "Database missing required field"
534
+ description: ClassVar[str] = "Database missing required field"
535
+
536
+ def __init__(
537
+ self,
538
+ message: Optional[str] = None,
539
+ sql_error: Optional[str] = None,
540
+ table: Optional[str] = None,
541
+ ):
542
+ super().__init__(
543
+ message or self.title,
544
+ code=self.error_code,
545
+ sql_error=sql_error,
546
+ table=table,
547
+ )
548
+
549
+
550
+ class BusinessLogicError(APIError):
551
+ """
552
+ Business logic error (HTTP 422).
553
+
554
+ Used for business rule violations and domain-specific errors.
555
+ Can include rule name and context information.
556
+
557
+ Usage:
558
+ raise BusinessLogicError("Insufficient balance", rule="min_balance", context={"current": 50})
559
+ """
560
+
561
+ error_code: ClassVar[ErrorCode] = ErrorCode.BUSINESS_RULE_VIOLATION
562
+ http_status_code: ClassVar[int] = 422
563
+ title: ClassVar[str] = "Business rule violation"
564
+ description: ClassVar[str] = "Business rule violation"
565
+
566
+ def __init__(
567
+ self,
568
+ message: Optional[str] = None,
569
+ code: ErrorCode = ErrorCode.BUSINESS_RULE_VIOLATION,
570
+ rule: Optional[str] = None,
571
+ context: Optional[Dict[str, Any]] = None,
572
+ ):
573
+ details: Dict[str, Any] = {}
574
+ if rule:
575
+ details["rule"] = rule
576
+ if context:
577
+ details["context"] = context
578
+
579
+ super().__init__(code, message, details, 422)
580
+
581
+
582
+ class InsufficientBalanceError(BusinessLogicError):
583
+ """Insufficient balance error (HTTP 422)."""
584
+
585
+ error_code: ClassVar[ErrorCode] = ErrorCode.INSUFFICIENT_BALANCE
586
+ title: ClassVar[str] = "Insufficient balance"
587
+ description: ClassVar[str] = "Insufficient balance"
588
+
589
+ def __init__(
590
+ self, message: Optional[str] = None, context: Optional[Dict[str, Any]] = None
591
+ ):
592
+ super().__init__(
593
+ message or self.title,
594
+ code=self.error_code,
595
+ context=context,
596
+ )
597
+
598
+
599
+ class OperationNotAllowedError(BusinessLogicError):
600
+ """Operation not allowed error (HTTP 422)."""
601
+
602
+ error_code: ClassVar[ErrorCode] = ErrorCode.OPERATION_NOT_ALLOWED
603
+ title: ClassVar[str] = "Operation not allowed"
604
+ description: ClassVar[str] = "Operation not allowed"
605
+
606
+ def __init__(
607
+ self, message: Optional[str] = None, context: Optional[Dict[str, Any]] = None
608
+ ):
609
+ super().__init__(
610
+ message or self.title,
611
+ code=self.error_code,
612
+ context=context,
613
+ )