Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/objects/tests/v2/test_filters.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from datetime import date, timedelta
from unittest.mock import patch

Expand Down Expand Up @@ -1021,6 +1022,7 @@ def test_filter_with_nesting(self):
f"http://testserver{reverse('object-detail', args=[record.object.uuid])}",
)

@patch.dict(os.environ, {"DEBUG": "false"})
@patch(
"objects.core.query.ObjectRecordQuerySet._fetch_all",
side_effect=ProgrammingError("'jsonpath' is not found"),
Expand All @@ -1032,7 +1034,10 @@ def test_filter_db_error(self, mock_query):
self.assertEqual(
response.json(),
{
"detail": "This search operation is not supported by the underlying data store."
"code": "error",
"title": "Internal Server Error",
"status": 500,
"detail": "This search operation is not supported by the underlying data store.",
},
)

Expand Down
41 changes: 37 additions & 4 deletions src/objects/utils/views.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
from django import http
from django.db.utils import DatabaseError
from django.template import TemplateDoesNotExist, loader
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import requires_csrf_token
from django.views.defaults import ERROR_500_TEMPLATE_NAME

import structlog
from open_api_framework.conf.utils import config
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler

logger = structlog.stdlib.get_logger(__name__)

DEFAULT_CODE = "invalid"
DEFAULT_DETAIL = _("Invalid input.")


@requires_csrf_token
def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
Expand All @@ -34,14 +39,42 @@ def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):


def exception_handler(exc, context):
"""
Transform 5xx errors into DSO-compliant shape.
"""
response = drf_exception_handler(exc, context)
if not response:
if config("DEBUG", default=False):
return None

# provide user-friendly response if data_icontains was used but DB couldn't process it
if not response and isinstance(exc, DatabaseError) and "jsonpath" in exc.args[0]:
data = {
"detail": "This search operation is not supported by the underlying data store."
"code": "error",
"title": "Internal Server Error",
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
"detail": _("A server error has occurred."),
}
event = "api.uncaught_exception"

if isinstance(exc, DatabaseError) and "jsonpath" in exc.args[0]:
# provide user-friendly response if data_icontains was used but DB couldn't process it
data["detail"] = (
"This search operation is not supported by the underlying data store."
)
event = "api.database_exception"

response = Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data=data)
logger.exception("search_failed_for_datastore", exc_info=exc)
logger.exception(event, exc_info=exc)

return response

# exception logger event
logger.exception(
"api.handled_exception",
title=getattr(exc, "default_detail", DEFAULT_DETAIL).strip("'"),
code=getattr(exc, "default_code", DEFAULT_CODE),
status=getattr(response, "status_code", status.HTTP_400_BAD_REQUEST),
data=getattr(response, "data", {}),
exc_info=False,
)

return response