This repository has been archived on 2022-02-09. You can view files and clone it, but cannot push or open issues or pull requests.
disclosemypay.org/section7/router/error_handler.py

123 lines
4.1 KiB
Python

"""Custom error handler and error page rendering endpoint"""
import enum
import logging
import uuid
from dataclasses import dataclass
from dataclasses import field
from typing import Tuple
import flask
import werkzeug
class ErrorColor(enum.Enum):
"""Enum of possible colors for the error page to be rendered with
Colors can be used to indicate the severity of the problem to a user. Generally, red is reserved
for fatal errors (errors that the user cannot fix themselves), orange for user input or
temporary errors (errors that the user can potentially fix themselves), and blue for
informational errors that don't necessarily indicate a problem, but still needs to block user
access.
Names are the intended usage of the color while values are the CSS classes that the page
uses for the corresponding color.
"""
WARNING = "warning"
ERROR = "error"
INFORMATIONAL = "informational"
class ErrorIcon(enum.Enum):
"""Enum of possible icons for the error page
Names are the intended usage of the icon while values are the Font Awesome CSS class structure
for the corresponding icon.
"""
EXCLAMATION = "fas fa-exclamation-triangle"
INFORMATION = "fas fa-exclamation-circle"
@dataclass
class ErrorDetails:
"""Container of details for an error that can be used to render an error page
:param code: The HTTP response code for the error
:param title: Page title/short description of the error
:param description: Long description of the error. Ideally should include an action the user
can take to debug or fix the problem.
:param icon: The icon that should be added to the page
:param color: The color of the page highlights
"""
code: int = 500
title: str = "Internal Server Error"
description: str = "The application encountered an unhandled error while trying to fulfill the request."
icon: ErrorIcon = ErrorIcon.EXCLAMATION
color: ErrorColor = ErrorColor.ERROR
event_id: str = field(default_factory=lambda: uuid.uuid4().hex)
def handle_error(error: Exception) -> Tuple[str, int]:
"""Handle an exception raised elsewhere in the application
:returns: Rendered template of an error page for the raised exception
"""
logger = logging.getLogger(__name__)
# If the application is running in debug mode then we re-raise the exception so that the
# developer gets the helpful browser-rendered stack trace werkzeug provides
if flask.current_app.config["DEBUG"]:
logger.warning(
"Application is running in debug mode, stack trace will be returned to client"
)
raise error
details = ErrorDetails()
if isinstance(error, NotImplementedError):
details.title = "Not Implemented"
details.description = "The functionality for this resource has not yet been implemented. The resource name is reserved for for future use"
details.color = ErrorColor.INFORMATIONAL
details.icon = ErrorIcon.INFORMATION
elif isinstance(error, werkzeug.exceptions.HTTPException):
details.code = error.code
details.title = error.name
details.description = error.description
if 400 <= error.code < 500:
details.color = ErrorColor.WARNING
details.icon = ErrorIcon.INFORMATION
else:
logger.exception(
f"Event ID {details.event_id}: unhandled application error at '{flask.request.full_path}'"
)
logger.error(
f"Event ID {details.event_id}: Error {details.code} {details.description}"
)
def _render_template(
title: str,
error: ErrorDetails,
) -> str:
"""Stub function to type, enumerate, and document the template parameters
:param title: Value of the page title header
:param error: Container of error details about the error to render on the page
"""
return flask.render_template(
"error.html.j2",
title=title,
error=error,
)
return (
_render_template(
title=f"{details.title} ({details.code})",
error=details,
),
details.code,
)