Skip to content

Avoid overwriting of pointer when formatting error object #1074

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 5, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 16 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,29 @@ 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).

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,
Expand Down
9 changes: 4 additions & 5 deletions rest_framework_json_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
43 changes: 43 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)