refactor(backend): clean up jail service, add error handling service
- Extract jail status/processing to helper functions - Add error_handling.py service for centralized error handling - Update config.py with validation and defaults - Update .env.example with all config options - Remove obsolete Tasks.md, add Service-Development.md - Minor fixes across routers and services Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1672,18 +1672,50 @@ async def get_jail(...) -> JailDetailResponse:
|
||||
- Map domain exceptions to HTTP status codes via FastAPI **exception handlers** registered on the app.
|
||||
- Always log errors with context before raising.
|
||||
|
||||
```python
|
||||
class JailNotFoundError(Exception):
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name: str = name
|
||||
super().__init__(f"Jail '{name}' not found")
|
||||
### Service Error Contracts
|
||||
|
||||
# In main.py
|
||||
@app.exception_handler(JailNotFoundError)
|
||||
async def jail_not_found_handler(request: Request, exc: JailNotFoundError) -> JSONResponse:
|
||||
return JSONResponse(status_code=404, content={"detail": f"Jail '{exc.name}' not found"})
|
||||
Each service method must document which error handling pattern it follows. This
|
||||
lets callers know what to expect without reading the implementation. See
|
||||
`Docs/Service-Development.md` for the full guide.
|
||||
|
||||
**ABORT_ON_ERROR** — Raise an exception, let the router handle it. Used for:
|
||||
auth, writes, state changes, any operation where partial success is meaningless.
|
||||
|
||||
**RETURN_DEFAULT** — Return empty result and log warning. Never raises. Used for:
|
||||
informational reads where infrastructure unavailability should not block the UI.
|
||||
|
||||
**PARTIAL_RESULT** — Return a result that contains both successful items and a
|
||||
list of errors. Caller decides what to do with each.
|
||||
|
||||
```python
|
||||
async def get_settings(socket_path: str) -> DomainServerSettingsResult:
|
||||
"""Return current fail2ban server-level settings.
|
||||
|
||||
Error contract: RETURN_DEFAULT. Returns DomainServerSettingsResult with
|
||||
default values if socket is unreachable. Never raises.
|
||||
"""
|
||||
...
|
||||
|
||||
async def start_jail(socket_path: str, name: str) -> None:
|
||||
"""Start a stopped fail2ban jail.
|
||||
|
||||
Error contract: ABORT_ON_ERROR. Raises JailNotFoundError (404),
|
||||
JailOperationError (409), Fail2BanConnectionError (503).
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
```python
|
||||
class ServiceErrorContract:
|
||||
"""
|
||||
ABORT_ON_ERROR: Raise exception, let router handle
|
||||
RETURN_DEFAULT: Return empty result, log warning
|
||||
PARTIAL_RESULT: Return partial success with error list
|
||||
"""
|
||||
```
|
||||
|
||||
The error contract enum and helper are in `app.services.error_handling`.
|
||||
|
||||
### Routers and Exception Propagation
|
||||
|
||||
- **Routers must NOT construct `HTTPException` for domain errors** — let domain exceptions propagate.
|
||||
|
||||
Reference in New Issue
Block a user