refactor(ban_service): extract _bans_by_country_load_data helper
Break up long function into focused helper. Load data logic separate from aggregation.
This commit is contained in:
@@ -39,6 +39,11 @@ See Backend-Development.md for the complete exception contract.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.models.response import ErrorMetadata
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Exception Base Classes (Categories)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -46,7 +51,7 @@ from __future__ import annotations
|
||||
|
||||
class DomainError(Exception):
|
||||
"""Base class for all domain exceptions.
|
||||
|
||||
|
||||
All domain exceptions must:
|
||||
1. Define an `error_code` class attribute (machine-readable error code)
|
||||
2. Implement `get_error_metadata()` to return structured error context
|
||||
@@ -54,11 +59,11 @@ class DomainError(Exception):
|
||||
|
||||
error_code: str = "internal_error"
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
"""Return structured metadata for the API error response.
|
||||
|
||||
|
||||
Subclasses should override to expose only safe, relevant metadata.
|
||||
|
||||
|
||||
Returns:
|
||||
A dictionary of metadata key-value pairs safe for client consumption.
|
||||
"""
|
||||
@@ -116,7 +121,7 @@ class RateLimitError(DomainError):
|
||||
self.retry_after_seconds: float = retry_after_seconds
|
||||
super().__init__(message)
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"retry_after_seconds": self.retry_after_seconds}
|
||||
|
||||
|
||||
@@ -134,7 +139,7 @@ class JailNotFoundError(NotFoundError):
|
||||
self.name = name
|
||||
super().__init__(f"Jail not found: {name!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"jail_name": self.name}
|
||||
|
||||
|
||||
@@ -176,7 +181,7 @@ class ConfigFileNotFoundError(NotFoundError):
|
||||
self.filename = filename
|
||||
super().__init__(f"Config file not found: {filename!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"filename": self.filename}
|
||||
|
||||
|
||||
@@ -194,7 +199,7 @@ class ConfigFileExistsError(ConflictError):
|
||||
self.filename = filename
|
||||
super().__init__(f"Config file already exists: {filename!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"filename": self.filename}
|
||||
|
||||
|
||||
@@ -231,7 +236,7 @@ class Fail2BanConnectionError(ServiceUnavailableError):
|
||||
self.socket_path: str = socket_path
|
||||
super().__init__(f"{message} (socket: {socket_path})")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"socket_path": self.socket_path}
|
||||
|
||||
|
||||
@@ -252,7 +257,7 @@ class FilterInvalidRegexError(BadRequestError):
|
||||
self.error = error
|
||||
super().__init__(f"Invalid regex {pattern!r}: {error}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"pattern": self.pattern, "error": self.error}
|
||||
|
||||
|
||||
@@ -276,7 +281,7 @@ class FilterRegexTooLongError(BadRequestError):
|
||||
f"{self.actual_length} provided"
|
||||
)
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {
|
||||
"pattern_length": self.actual_length,
|
||||
"max_length": self.max_length,
|
||||
@@ -302,7 +307,7 @@ class FilterRegexTimeoutError(BadRequestError):
|
||||
f"(possible ReDoS attack). Pattern is too complex or causes catastrophic backtracking."
|
||||
)
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"timeout_seconds": self.timeout_seconds}
|
||||
|
||||
|
||||
@@ -315,7 +320,7 @@ class JailNotFoundInConfigError(NotFoundError):
|
||||
self.name = name
|
||||
super().__init__(f"Jail not found in config: {name!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"jail_name": self.name}
|
||||
|
||||
|
||||
@@ -328,7 +333,7 @@ class ConfigWriteError(OperationError):
|
||||
self.message = message
|
||||
super().__init__(message)
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"message": self.message}
|
||||
|
||||
|
||||
@@ -347,7 +352,7 @@ class JailAlreadyActiveError(ConflictError):
|
||||
self.name = name
|
||||
super().__init__(f"Jail is already active: {name!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"jail_name": self.name}
|
||||
|
||||
|
||||
@@ -360,7 +365,7 @@ class JailAlreadyInactiveError(ConflictError):
|
||||
self.name = name
|
||||
super().__init__(f"Jail is already inactive: {name!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"jail_name": self.name}
|
||||
|
||||
|
||||
@@ -373,7 +378,7 @@ class FilterNotFoundError(NotFoundError):
|
||||
self.name = name
|
||||
super().__init__(f"Filter not found: {name!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"filter_name": self.name}
|
||||
|
||||
|
||||
@@ -386,7 +391,7 @@ class FilterAlreadyExistsError(ConflictError):
|
||||
self.name = name
|
||||
super().__init__(f"Filter already exists: {name!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"filter_name": self.name}
|
||||
|
||||
|
||||
@@ -407,7 +412,7 @@ class FilterReadonlyError(ConflictError):
|
||||
f"Filter {name!r} is a shipped default (.conf only); only user-created .local files can be deleted."
|
||||
)
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"filter_name": self.name}
|
||||
|
||||
|
||||
@@ -420,7 +425,7 @@ class ActionNotFoundError(NotFoundError):
|
||||
self.name = name
|
||||
super().__init__(f"Action not found: {name!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"action_name": self.name}
|
||||
|
||||
|
||||
@@ -433,7 +438,7 @@ class ActionAlreadyExistsError(ConflictError):
|
||||
self.name = name
|
||||
super().__init__(f"Action already exists: {name!r}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"action_name": self.name}
|
||||
|
||||
|
||||
@@ -454,7 +459,7 @@ class ActionReadonlyError(ConflictError):
|
||||
f"Action {name!r} is a shipped default (.conf only); only user-created .local files can be deleted."
|
||||
)
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"action_name": self.name}
|
||||
|
||||
|
||||
@@ -478,7 +483,7 @@ class BlocklistSourceNotFoundError(NotFoundError):
|
||||
self.source_id = source_id
|
||||
super().__init__(f"Blocklist source not found: {source_id}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"source_id": self.source_id}
|
||||
|
||||
|
||||
@@ -494,7 +499,7 @@ class BlocklistSourceHasLogsError(ConflictError):
|
||||
"Delete the import logs first."
|
||||
)
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"source_id": self.source_id}
|
||||
|
||||
|
||||
@@ -507,5 +512,5 @@ class HistoryNotFoundError(NotFoundError):
|
||||
self.ip = ip
|
||||
super().__init__(f"No history found for IP: {ip}")
|
||||
|
||||
def get_error_metadata(self) -> dict[str, str | int | float | bool | None]:
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"ip": self.ip}
|
||||
|
||||
Reference in New Issue
Block a user