diff --git a/CHANGELOG.md b/CHANGELOG.md index 91faffde..91551dca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ any parts of the framework not mentioned in the documentation should generally b * Fixed invalid relationship pointer in error objects when field naming formatting is used. * Properly resolved related resource type when nested source field is defined. +* Prevented overwriting of pointer in custom error object ### Added diff --git a/docs/usage.md b/docs/usage.md index 939e4e6a..952fe80c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -237,7 +237,7 @@ class MyViewset(ModelViewSet): ``` -### Exception handling +### Error objects / Exception handling For the `exception_handler` class, if the optional `JSON_API_UNIFORM_EXCEPTIONS` is set to True, all exceptions will respond with the JSON:API [error format](https://jsonapi.org/format/#error-objects). @@ -245,6 +245,21 @@ all exceptions will respond with the JSON:API [error format](https://jsonapi.org When `JSON_API_UNIFORM_EXCEPTIONS` is False (the default), non-JSON:API views will respond with the normal DRF error format. +In case you need a custom error object you can simply raise an `rest_framework.serializers.ValidationError` like the following: + +```python +raise serializers.ValidationError( + { + "id": "your-id", + "detail": "your detail message", + "source": { + "pointer": "/data/attributes/your-pointer", + } + + } +) +``` + ### Performance Testing If you are trying to see if your viewsets are configured properly to optimize performance, diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 8317c10f..2e86e762 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -417,21 +417,20 @@ def format_error_object(message, pointer, response): if isinstance(message, dict): # as there is no required field in error object we check that all fields are string - # except links and source which might be a dict + # except links, source or meta which might be a dict is_custom_error = all( [ isinstance(value, str) for key, value in message.items() - if key not in ["links", "source"] + if key not in ["links", "source", "meta"] ] ) if is_custom_error: if "source" not in message: message["source"] = {} - message["source"] = { - "pointer": pointer, - } + if "pointer" not in message["source"]: + message["source"]["pointer"] = pointer errors.append(message) else: for k, v in message.items(): diff --git a/tests/test_utils.py b/tests/test_utils.py index 9b47e9ff..038e8ce9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -7,6 +7,7 @@ from rest_framework_json_api import serializers from rest_framework_json_api.utils import ( + format_error_object, format_field_name, format_field_names, format_link_segment, @@ -389,3 +390,45 @@ class SerializerWithoutResourceName(serializers.Serializer): "can not detect 'resource_name' on serializer " "'SerializerWithoutResourceName' in module 'tests.test_utils'" ) + + +@pytest.mark.parametrize( + "message,pointer,response,result", + [ + # Test that pointer does not get overridden in custom error + ( + { + "status": "400", + "source": { + "pointer": "/data/custom-pointer", + }, + "meta": {"key": "value"}, + }, + "/data/default-pointer", + Response(status.HTTP_400_BAD_REQUEST), + [ + { + "status": "400", + "source": {"pointer": "/data/custom-pointer"}, + "meta": {"key": "value"}, + } + ], + ), + # Test that pointer gets added to custom error + ( + { + "detail": "custom message", + }, + "/data/default-pointer", + Response(status.HTTP_400_BAD_REQUEST), + [ + { + "detail": "custom message", + "source": {"pointer": "/data/default-pointer"}, + } + ], + ), + ], +) +def test_format_error_object(message, pointer, response, result): + assert result == format_error_object(message, pointer, response)