Fix exception handler overlap issue - add DomainError catch-all handler
**Problem:** Broad exception handlers created fragility where adding a new DomainError subclass without explicit registration would silently fall through to the generic exception handler, losing the specific error_code and metadata. **Solution:** 1. Import DomainError in main.py for explicit handler registration 2. Fix type hints in exception handlers from 'Exception' to specific types - NotFoundError handler now typed as 'NotFoundError' - BadRequestError handler now typed as 'BadRequestError' - ConflictError handler now typed as 'ConflictError' - DomainError handler now typed as 'DomainError' - ServiceUnavailableError handler now typed as 'ServiceUnavailableError' 3. Add DomainError as an explicit catch-all handler in the registration chain - Positioned after specific handlers, before HTTPException - Any unregistered DomainError subclass now gets correct error_code + metadata 4. Document the exception handler hierarchy with detailed comments 5. Update Backend-Development.md with handler hierarchy documentation 6. Update Architekture.md section 2.2 with exception handler details 7. Fix test expectations in test_main.py to verify ErrorResponse format **Impact:** Any new DomainError subclass now automatically gets correct HTTP 500 status, error_code, and metadata - even if developer forgets explicit handler. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -555,9 +555,23 @@ The FastAPI app factory. Responsibilities:
|
||||
- Creates the `FastAPI` instance with metadata (title, version, docs URL)
|
||||
- Registers the **lifespan** context manager (startup: open DB, create aiohttp session, start scheduler; shutdown: close all)
|
||||
- Mounts all routers
|
||||
- Registers global exception handlers that map domain exceptions to HTTP status codes
|
||||
- Registers global exception handlers that map domain exceptions to HTTP status codes with a hierarchical fallback chain
|
||||
- Applies the setup-redirect middleware (returns `423 Locked` for all API requests when no configuration exists, except for `/api/setup` and `/api/health`)
|
||||
|
||||
**Exception Handler Hierarchy:**
|
||||
|
||||
Exception handlers are registered in order of specificity to ensure each exception type is caught by the most appropriate handler:
|
||||
|
||||
1. **Specific network errors** (Fail2BanConnectionError, Fail2BanProtocolError) → HTTP 502 Bad Gateway
|
||||
2. **Specific auth/rate errors** (AuthenticationError, RateLimitError) → HTTP 401 Unauthorized / 429 Too Many Requests
|
||||
3. **Category handlers** (NotFoundError, BadRequestError, ConflictError, OperationError, ServiceUnavailableError) → HTTP 404/400/409/500/503
|
||||
4. **DomainError catch-all** → HTTP 500 (catches any unregistered DomainError subclass, ensuring proper error_code and metadata are returned)
|
||||
5. **HTTPException** → HTTP status from exception (FastAPI built-in validation and routing errors)
|
||||
6. **ValueError** → HTTP 400 Bad Request (Pydantic validation errors)
|
||||
7. **Exception catch-all** → HTTP 500 Internal Server Error (absolute fallback for unexpected errors)
|
||||
|
||||
The DomainError catch-all handler (step 4) is critical: it ensures that any new DomainError subclass automatically gets the correct HTTP status (500), error_code, and metadata through its inherited `error_code` attribute and `get_error_metadata()` method, even if the developer forgot to create an explicit handler for it. This prevents silent failures where an unhandled exception would return a generic "internal_error" code instead of the specific error code defined by the exception class.
|
||||
|
||||
### 2.3 Dependency Wiring and Service Composition
|
||||
|
||||
BanGUI uses a **lightweight dependency injection (DI) pattern** based on FastAPI's `Depends()` framework. There is no heavy container library — the composition root is implicit and managed through simple provider functions in `app/dependencies.py`.
|
||||
|
||||
Reference in New Issue
Block a user