diff --git a/src/plone/rest/errors.py b/src/plone/rest/errors.py index 40adc12..5c2429f 100644 --- a/src/plone/rest/errors.py +++ b/src/plone/rest/errors.py @@ -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): @@ -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) diff --git a/src/plone/rest/testing.py b/src/plone/rest/testing.py index e1ce335..adc2102 100644 --- a/src/plone/rest/testing.py +++ b/src/plone/rest/testing.py @@ -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) diff --git a/src/plone/rest/testing.zcml b/src/plone/rest/testing.zcml index 38945a3..54495ec 100644 --- a/src/plone/rest/testing.zcml +++ b/src/plone/rest/testing.zcml @@ -175,4 +175,12 @@ name="bad-request-error" /> + + diff --git a/src/plone/rest/tests/test_error_handling.py b/src/plone/rest/tests/test_error_handling.py index 3fbef5e..d394787 100644 --- a/src/plone/rest/tests/test_error_handling.py +++ b/src/plone/rest/tests/test_error_handling.py @@ -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"}], + )