refactor: Remove duplicate router-level exception helpers

All routers now let domain exceptions propagate to the global handlers in main.py
instead of catching and converting them to HTTPException. This eliminates:

- Duplicate exception-to-HTTP-status mappings across 8 routers
- Duplicate helper functions (_bad_gateway, _not_found, _conflict, etc.)
- Inconsistent error response formats

Changes:
- Removed all try/except blocks from routers that catch domain exceptions
- Removed duplicate helper functions from all routers
- Added missing exception handlers to main.py for:
  * ActionNameError
  * FilterNameError
  * JailNameError
  * JailNotFoundInConfigError
  * FilterInvalidRegexError
- Removed unused imports from affected routers

All domain exceptions now propagate to the single authoritative mapping in
main.py, ensuring consistent error codes, messages, and logging across the API.

Affected routers:
- action_config.py: Removed _action_not_found, _bad_request, _not_found helpers
- bans.py: Removed try/except in ban/unban endpoints
- config_misc.py: Removed try/except blocks
- file_config.py: Removed 6 try/except blocks and _service_unavailable helper
- filter_config.py: Removed try/except blocks
- geo.py: Removed try/except in lookup_ip endpoint
- jail_config.py: Removed try/except blocks
- jails.py: Removed try/except blocks
- server.py: Removed try/except blocks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-23 16:00:37 +02:00
parent b634ce876a
commit 5480dce221
12 changed files with 229 additions and 977 deletions

View File

@@ -46,58 +46,8 @@ from app.services import jail_service
router: APIRouter = APIRouter(prefix="/api/jails", tags=["Jails"])
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
_NamePath = Annotated[str, Path(description="Jail name as configured in fail2ban.")]
def _not_found(name: str) -> HTTPException:
"""Return a 404 response for an unknown jail.
Args:
name: Jail name that was not found.
Returns:
:class:`fastapi.HTTPException` with status 404.
"""
return HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Jail not found: {name!r}",
)
def _bad_gateway(exc: Exception) -> HTTPException:
"""Return a 502 response when fail2ban is unreachable.
Args:
exc: The underlying connection error.
Returns:
:class:`fastapi.HTTPException` with status 502.
"""
return HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"Cannot reach fail2ban: {exc}",
)
def _conflict(message: str) -> HTTPException:
"""Return a 409 response for invalid jail state transitions.
Args:
message: Human-readable description of the conflict.
Returns:
:class:`fastapi.HTTPException` with status 409.
"""
return HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=message,
)
# ---------------------------------------------------------------------------
# Jail listing & detail
# ---------------------------------------------------------------------------
@@ -124,10 +74,7 @@ async def get_jails(
Returns:
:class:`~app.models.jail.JailListResponse` with all active jails.
"""
try:
return await jail_service.list_jails(socket_path)
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
return await jail_service.list_jails(socket_path)
@router.get(
@@ -157,12 +104,7 @@ async def get_jail(
HTTPException: 404 when the jail does not exist.
HTTPException: 502 when fail2ban is unreachable.
"""
try:
return await jail_service.get_jail(socket_path, name)
except JailNotFoundError:
raise _not_found(name) from None
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
return await jail_service.get_jail(socket_path, name)
# ---------------------------------------------------------------------------
@@ -194,13 +136,8 @@ async def reload_all_jails(
HTTPException: 502 when fail2ban is unreachable.
HTTPException: 409 when fail2ban reports the operation failed.
"""
try:
await jail_service.reload_all(socket_path)
return JailCommandResponse(message="All jails reloaded successfully.", jail="*")
except JailOperationError as exc:
raise _conflict(str(exc)) from exc
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
await jail_service.reload_all(socket_path)
return JailCommandResponse(message="All jails reloaded successfully.", jail="*")
@router.post(
@@ -227,15 +164,8 @@ async def start_jail(
HTTPException: 409 when fail2ban reports the operation failed.
HTTPException: 502 when fail2ban is unreachable.
"""
try:
await jail_service.start_jail(socket_path, name)
return JailCommandResponse(message=f"Jail {name!r} started.", jail=name)
except JailNotFoundError:
raise _not_found(name) from None
except JailOperationError as exc:
raise _conflict(str(exc)) from exc
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
await jail_service.start_jail(socket_path, name)
return JailCommandResponse(message=f"Jail {name!r} started.", jail=name)
@router.post(
@@ -265,13 +195,8 @@ async def stop_jail(
HTTPException: 409 when fail2ban reports the operation failed.
HTTPException: 502 when fail2ban is unreachable.
"""
try:
await jail_service.stop_jail(socket_path, name)
return JailCommandResponse(message=f"Jail {name!r} stopped.", jail=name)
except JailOperationError as exc:
raise _conflict(str(exc)) from exc
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
await jail_service.stop_jail(socket_path, name)
return JailCommandResponse(message=f"Jail {name!r} stopped.", jail=name)
@router.post(
@@ -304,18 +229,11 @@ async def toggle_idle(
HTTPException: 502 when fail2ban is unreachable.
"""
state_str = "on" if on else "off"
try:
await jail_service.set_idle(socket_path, name, on=on)
return JailCommandResponse(
message=f"Jail {name!r} idle mode turned {state_str}.",
jail=name,
)
except JailNotFoundError:
raise _not_found(name) from None
except JailOperationError as exc:
raise _conflict(str(exc)) from exc
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
await jail_service.set_idle(socket_path, name, on=on)
return JailCommandResponse(
message=f"Jail {name!r} idle mode turned {state_str}.",
jail=name,
)
@router.post(
@@ -342,15 +260,8 @@ async def reload_jail(
HTTPException: 409 when fail2ban reports the operation failed.
HTTPException: 502 when fail2ban is unreachable.
"""
try:
await jail_service.reload_jail(socket_path, name)
return JailCommandResponse(message=f"Jail {name!r} reloaded.", jail=name)
except JailNotFoundError:
raise _not_found(name) from None
except JailOperationError as exc:
raise _conflict(str(exc)) from exc
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
await jail_service.reload_jail(socket_path, name)
return JailCommandResponse(message=f"Jail {name!r} reloaded.", jail=name)
# ---------------------------------------------------------------------------
@@ -389,12 +300,7 @@ async def get_ignore_list(
HTTPException: 404 when the jail does not exist.
HTTPException: 502 when fail2ban is unreachable.
"""
try:
return await jail_service.get_ignore_list(socket_path, name)
except JailNotFoundError:
raise _not_found(name) from None
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
return await jail_service.get_ignore_list(socket_path, name)
@router.post(
@@ -428,23 +334,11 @@ async def add_ignore_ip(
HTTPException: 409 when fail2ban reports the operation failed.
HTTPException: 502 when fail2ban is unreachable.
"""
try:
await jail_service.add_ignore_ip(socket_path, name, body.ip)
return JailCommandResponse(
message=f"IP {body.ip!r} added to ignore list of jail {name!r}.",
jail=name,
)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(exc),
) from exc
except JailNotFoundError:
raise _not_found(name) from None
except JailOperationError as exc:
raise _conflict(str(exc)) from exc
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
await jail_service.add_ignore_ip(socket_path, name, body.ip)
return JailCommandResponse(
message=f"IP {body.ip!r} added to ignore list of jail {name!r}.",
jail=name,
)
@router.delete(
@@ -473,18 +367,11 @@ async def del_ignore_ip(
HTTPException: 409 when fail2ban reports the operation failed.
HTTPException: 502 when fail2ban is unreachable.
"""
try:
await jail_service.del_ignore_ip(socket_path, name, body.ip)
return JailCommandResponse(
message=f"IP {body.ip!r} removed from ignore list of jail {name!r}.",
jail=name,
)
except JailNotFoundError:
raise _not_found(name) from None
except JailOperationError as exc:
raise _conflict(str(exc)) from exc
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
await jail_service.del_ignore_ip(socket_path, name, body.ip)
return JailCommandResponse(
message=f"IP {body.ip!r} removed from ignore list of jail {name!r}.",
jail=name,
)
@router.post(
@@ -517,18 +404,11 @@ async def toggle_ignore_self(
HTTPException: 502 when fail2ban is unreachable.
"""
state_str = "enabled" if on else "disabled"
try:
await jail_service.set_ignore_self(socket_path, name, on=on)
return JailCommandResponse(
message=f"ignoreself {state_str} for jail {name!r}.",
jail=name,
)
except JailNotFoundError:
raise _not_found(name) from None
except JailOperationError as exc:
raise _conflict(str(exc)) from exc
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
await jail_service.set_ignore_self(socket_path, name, on=on)
return JailCommandResponse(
message=f"ignoreself {state_str} for jail {name!r}.",
jail=name,
)
# ---------------------------------------------------------------------------
@@ -584,18 +464,13 @@ async def get_jail_banned_ips(
detail="page_size must be between 1 and 100.",
)
try:
return await jail_service.get_jail_banned_ips(
socket_path=socket_path,
jail_name=name,
page=page,
page_size=page_size,
search=search,
geo_batch_lookup=geo_batch_lookup,
http_session=http_session,
app_db=db,
)
except JailNotFoundError:
raise _not_found(name) from None
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
return await jail_service.get_jail_banned_ips(
socket_path=socket_path,
jail_name=name,
page=page,
page_size=page_size,
search=search,
geo_batch_lookup=geo_batch_lookup,
http_session=http_session,
app_db=db,
)