Skip to content

Commit

Permalink
Makes the error message serializable via json
Browse files Browse the repository at this point in the history
If the exception is a BadRequest and the message is a list, return the
message with list. If the list elements are dictionaries, convert
non-serializable keys and values to string. If it is not a BadRequest,
returns the exception as a string.
  • Loading branch information
wesleybl committed Feb 23, 2024
1 parent 71391f2 commit e2b8877
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 4 deletions.
40 changes: 36 additions & 4 deletions src/plone/rest/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,41 @@
except ImportError:
HAS_WSGI = False

BASE_TYPES = (str, float, int)


def convert_to_serializable(item):
if isinstance(item, dict):
new_dict = {}
for key, value in item.items():
if not isinstance(key, BASE_TYPES):
new_key = repr(key)
else:
new_key = key
if not isinstance(value, BASE_TYPES):
new_value = repr(value)
else:
new_value = value
new_dict[new_key] = new_value
return new_dict
return repr(item)


def extract_error_message(exception):
"""If the exception is a BadRequest and the message is a list,
return the message with list. If the list elements are dictionaries,
convert non-serializable keys and values to string.
If it is not a BadRequest, returns the exception as a string.
"""
# If exception is a BadRequest
if isinstance(exception, BadRequest):
message = exception.args[0]
# And de message is a list
if isinstance(message, list):
# Convert list items to serializable objects.
return [convert_to_serializable(item) for item in message]
return str(exception)


@adapter(Exception, IAPIRequest)
class ErrorHandling(BrowserView):
Expand Down Expand Up @@ -58,10 +93,7 @@ def __call__(self):

def render_exception(self, exception):
name = type(exception).__name__
if isinstance(exception, BadRequest):
message = exception.args[0]
else:
message = str(exception)
message = extract_error_message(exception)
result = {"type": name, "message": message}

policy = queryMultiAdapter((self.context, self.request), ICORSPolicy)
Expand Down
9 changes: 9 additions & 0 deletions src/plone/rest/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ def __call__(self):
errors = [{"error": "ValidationError", "message": "message error"}]

raise BadRequest(errors)


class BadRequestNoSerializableService(Service):
def __call__(self):
from zExceptions import BadRequest

errors = [{"error": Exception("error"), "message": "message error"}]

raise BadRequest(errors)
8 changes: 8 additions & 0 deletions src/plone/rest/testing.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,12 @@
name="bad-request-error"
/>

<plone:service
method="GET"
factory=".testing.BadRequestNoSerializableService"
for="*"
permission="zope2.View"
name="bad-request-no-serializable"
/>

</configure>
11 changes: 11 additions & 0 deletions src/plone/rest/tests/test_error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,14 @@ def test_bad_request_error_message(self):
response.json()["message"],
[{"error": "ValidationError", "message": "message error"}],
)

def test_bad_request_no_serializable_message(self):
response = requests.get(
f"{self.portal_url}/bad-request-no-serializable",
headers={"Accept": "application/json"},
auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
)
self.assertEqual(
response.json()["message"],
[{"error": "Exception('error')", "message": "message error"}],
)

0 comments on commit e2b8877

Please sign in to comment.