Custom Exception Hierarchy and Error Handling in the RomM Backend

RomM implements a flat, domain-specific exception hierarchy where custom exceptions inherit directly from Python's Exception class, with specialized modules for filesystem errors, API endpoints, authentication, and background tasks to ensure consistent logging and automatic HTTP status code mapping.

The RomM backend uses a strategic approach to error management through a custom exception hierarchy designed to separate domain concerns and streamline FastAPI HTTP responses. Located in the backend/exceptions/ directory, these classes handle everything from filesystem scan failures to database lookup errors, providing clear logging and user-friendly error messages while maintaining a deliberately flat inheritance structure.

Filesystem Exceptions in RomM

Filesystem errors reside in backend/exceptions/fs_exceptions.py and handle problems encountered while scanning the RomM folder structure or accessing platform and ROM data on disk. These exceptions provide human-readable messages that direct users to documentation or explain exactly what resource is missing.

The FolderStructureNotMatchException class demonstrates the pattern used for scan failures:


# backend/exceptions/fs_exceptions.py

class FolderStructureNotMatchException(Exception):
    def __init__(self):
        self.message = (
            f"Platforms not found. "
            "Check RomM folder structure here: https://docs.romm.app/latest/Getting-Started/Folder-Structure/ for more details"
        )
        super().__init__(self.message)

Additional filesystem exceptions include PlatformNotFoundException, RomsNotFoundException, and RomAlreadyExistsException. These are raised by helper utilities like fs_platform_handler.get_platforms() and caught in socket endpoints to emit error messages via WebSocket:


# backend/endpoints/sockets/scan.py

try:
    fs_platforms: list[str] = await fs_platform_handler.get_platforms()
except FolderStructureNotMatchException as e:
    log.error(e)
    await socket_manager.emit("scan:done_ko", e.message)
    return scan_stats

Endpoint Exceptions and Automatic HTTP Response Mapping

API-level exceptions in backend/exceptions/endpoint_exceptions.py embed HTTPException instantiation directly within their constructors. This design ensures that raising the custom exception automatically triggers the appropriate FastAPI HTTP response without requiring explicit exception handling in route functions.

The PlatformNotFoundInDatabaseException illustrates this pattern:


# backend/exceptions/endpoint_exceptions.py

class PlatformNotFoundInDatabaseException(Exception):
    def __init__(self, id):
        self.message = f"Platform with id '{id}' not found"
        super().__init__(self.message)
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=self.message)

When a database lookup fails, the endpoint code simply raises the exception, and FastAPI automatically returns a JSON payload:

{
  "detail": "Platform with id '42' not found"
}

Other endpoint exceptions follow this same scheme, including RomNotFoundInDatabaseException and CollectionPermissionError. For critical errors such as duplicate collection creation, the exception logs a critical message before raising the HTTP exception:

log.critical(self.message)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=self.message)

Authentication and Pre-Instantiated Exceptions

Authentication errors in backend/exceptions/auth_exceptions.py take a different approach by defining pre-instantiated HTTPException objects rather than custom classes. This pattern allows for immediate raising without instantiation overhead.


# backend/exceptions/auth_exceptions.py

AuthCredentialsException = HTTPException(
    status_code=status.HTTP_401_UNAUTHORIZED,
    detail="Incorrect username or password",
)

Routes use these directly when validation fails:

if not valid:
    raise AuthCredentialsException

Similarly, OAuthCredentialsException provides specific error details for OAuth-related failures.

Task, Configuration, and Validation Errors

Background job errors are handled by SchedulerException in backend/exceptions/task_exceptions.py, which is raised by Redis-RQ workers when tasks encounter problems. These exceptions are caught by the task runner, logged, and marked as failed without generating HTTP responses since they occur outside the request cycle.

Configuration errors use ConfigNotWritableException in backend/exceptions/config_exceptions.py when the application cannot persist settings to the .env file due to permission issues.

Input validation errors are defined in backend/utils/validation.py as ValidationError, which is caught by FastAPI dependencies and converted into 422 Unprocessable Entity responses.

Service-Specific Error Handling

Third-party service integrations define their own exception classes within their respective adapter modules. For example, the RetroAchievements adapter in backend/adapters/services/rahasher.py raises RAHasherError, while the IGDB integration uses IGDBInvalidCredentialsException. These encapsulate third-party API failures and credential issues separately from core RomM logic.

Error Handling Flow and Integration

The RomM backend follows a consistent error propagation pattern across all layers:

  1. Internal utilities raise domain-specific exceptions when encountering filesystem or data model issues.
  2. Socket endpoints catch filesystem exceptions and emit WebSocket error events while logging via the central logger.
  3. API endpoints allow endpoint exceptions to propagate, letting FastAPI automatically convert them to HTTP responses.
  4. Authentication uses pre-built HTTPException objects for immediate client feedback.
  5. Background tasks log scheduler exceptions and mark jobs as failed without HTTP interaction.
  6. Generic fallback handlers catch Exception to log unexpected failures without masking specific error types.

This architecture ensures that every error is logged for observability while clients receive appropriate HTTP status codes and clear error messages.

Summary

  • RomM uses a flat exception hierarchy where all custom exceptions inherit directly from Python's built-in Exception class.
  • Filesystem exceptions in backend/exceptions/fs_exceptions.py provide descriptive messages and are caught in socket endpoints to emit WebSocket notifications.
  • Endpoint exceptions automatically raise HTTPException inside their constructors, causing FastAPI to return proper JSON error responses with correct status codes.
  • Authentication errors are pre-instantiated HTTPException objects raised directly without custom class instantiation.
  • Background tasks use SchedulerException for Redis-RQ job failures, logging errors without HTTP response generation.
  • Validation errors are converted to 422 Unprocessable Entity responses by FastAPI dependencies.

Frequently Asked Questions

How does RomM map custom exceptions to HTTP status codes?

RomM maps exceptions to HTTP status codes through the constructor pattern in backend/exceptions/endpoint_exceptions.py. Each endpoint exception class raises HTTPException with a specific status code (such as 404 for not found or 500 for server errors) inside its __init__ method. When the exception is raised anywhere in the codebase, FastAPI intercepts the embedded HTTPException and returns the corresponding JSON response to the client.

What is the difference between filesystem and endpoint exceptions in RomM?

Filesystem exceptions in backend/exceptions/fs_exceptions.py are standard Python exceptions that carry descriptive messages for scan and folder structure errors. They are typically caught in socket endpoints and converted to WebSocket emissions or log entries. Endpoint exceptions in backend/exceptions/endpoint_exceptions.py are designed specifically for API routes and automatically raise FastAPI HTTPException objects to generate immediate HTTP responses without explicit try-catch blocks in the route handlers.

How are authentication errors handled differently from other exceptions?

According to the RomM source code, authentication errors use pre-instantiated HTTPException objects rather than custom exception classes. Defined in backend/exceptions/auth_exceptions.py, objects like AuthCredentialsException are raised directly using raise AuthCredentialsException, eliminating the need for instantiation and ensuring consistent 401 Unauthorized responses across all authentication endpoints.

Where are validation errors defined and how do they integrate with FastAPI?

Validation errors are defined in backend/utils/validation.py as the ValidationError class. These exceptions are raised by request-body validators when payload validation fails. FastAPI dependencies catch these exceptions and automatically convert them into 422 Unprocessable Entity HTTP responses, providing detailed feedback about which fields failed validation constraints.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →