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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Adam Ziolkowski <adam@adsized.com>
Alan Crosswell <alan@columbia.edu>
Alex Seidmann <alex@leanpnt.de>
Anton Shutik <shutikanton@gmail.com>
Arttu Perälä <arttu@perala.me>
Ashley Loewen <github@ashleycodes.tech>
Asif Saif Uddin <auvipy@gmail.com>
Beni Keller <beni@matraxi.ch>
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ any parts of the framework not mentioned in the documentation should generally b
### Fixed

* Refactored handling of the `sort` query parameter to fix duplicate declaration in the generated schema definition
* Non-field serializer errors are given a source.pointer value of "/data".

## [6.0.0] - 2022-09-24

Expand Down
7 changes: 7 additions & 0 deletions example/api/serializers/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ def validate_last_name(self, data):
)
return data

def validate(self, data):
if data["first_name"] == data["last_name"]:
raise serializers.ValidationError(
"First name cannot be the same as last name!"
)
return data

class Meta:
model = auth_models.User
fields = (
Expand Down
32 changes: 32 additions & 0 deletions example/tests/test_generic_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,35 @@ def test_custom_validation_exceptions(self):
)

assert expected == response.json()

def test_nonfield_validation_exceptions(self):
"""
Non-field errors should be attributed to /data source.pointer.
"""
expected = {
"errors": [
{
"status": "400",
"source": {
"pointer": "/data",
},
"detail": "First name cannot be the same as last name!",
"code": "invalid",
},
]
}
response = self.client.post(
"/identities",
{
"data": {
"type": "users",
"attributes": {
"email": "miles@example.com",
"first_name": "Miles",
"last_name": "Miles",
},
}
},
)

assert expected == response.json()
7 changes: 6 additions & 1 deletion rest_framework_json_api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,12 @@ def validate_path(serializer_class, field_path, path):
class ReservedFieldNamesMixin:
"""Ensures that reserved field names are not used and an error raised instead."""

_reserved_field_names = {"meta", "results", "type"}
_reserved_field_names = {
"meta",
"results",
"type",
api_settings.NON_FIELD_ERRORS_KEY,
}

def get_fields(self):
fields = super().get_fields()
Expand Down
9 changes: 7 additions & 2 deletions rest_framework_json_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import exceptions, relations
from rest_framework.exceptions import APIException
from rest_framework.settings import api_settings

from .settings import json_api_settings

Expand Down Expand Up @@ -381,10 +382,14 @@ def format_drf_errors(response, context, exc):
]

for field, error in response.data.items():
non_field_error = field == api_settings.NON_FIELD_ERRORS_KEY
field = format_field_name(field)
pointer = None
# pointer can be determined only if there's a serializer.
if has_serializer:
if non_field_error:
# Serializer error does not refer to a specific field.
pointer = "/data"
elif has_serializer:
# pointer can be determined only if there's a serializer.
rel = "relationships" if field in relationship_fields else "attributes"
pointer = f"/data/{rel}/{field}"
if isinstance(exc, Http404) and isinstance(error, str):
Expand Down