Repository: django-json-api/django-rest-framework-json-api

Issue:
Missing "errors" field in error response
The 400-level errors are responding with a JSON array containing error objects, but the JSON API spec [requires](http://jsonapi.org/format/#document-top-level) a top-level `error` field.
##### To reproduce
1. Use `serializer.is_valid(raise_exception=True)` in an API view
2. Send invalid JSON from the client to _any_ API endpoint
   
   Example:
   
   ``` js
   ajax({
     url: '/my/api',
     contentType: 'application/vnd.api+json',
     type: 'POST',
     data: 'not JSON'
   });
   ```
   
   Expected response:
   
   ``` json
   {
     "errors": [
       {
         "status": "400",
         "source": {"pointer": "/data"},
         "detail": "JSON parse error - No JSON object could be decoded"
       }
     ]
   }
   ```
   
   Actual response:    
   
   ``` json
     [
       {
         "status": "400",
         "source": {"pointer": "/data"},
         "detail": "JSON parse error - No JSON object could be decoded"
       }
     ]
   ```

It seems like the exception handler is [properly setting the resource name](https://github.com/django-json-api/django-rest-framework-json-api/blob/v2.0.0-beta.2/rest_framework_json_api/exceptions.py#L72), which _should_ be used [here](https://github.com/django-json-api/django-rest-framework-json-api/blob/v2.0.0-beta.2/rest_framework_json_api/renderers.py#L376-L405). The former gets called, but the latter does not. DRF seems to have taken over, responding with this project's `exception_handler` as-is.
##### My requirements

```
...
Django==1.9.1
djangorestframework==3.3.3
djangorestframework-jsonapi==2.0.0-beta.2
...
```
##### My config

``` py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework_json_api.renderers.JSONRenderer',
    ),
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework_json_api.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
    ),
    'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
}
```



Discussion:
@joeyespo thanks for the detailed bug report. Did you end up finding a fix and would you like to open a PR? If not I'll try to get to it myself. I'm guessing that the issue has something to do with calling `serializer.is_valid(raise_exception=True)` manually rather than by the view itself.

@jerel Thanks for the reply! I did. I got something working locally for both this and #214. I'll open a PR within the next couple days.

@joeyespo good to hear! I did just merge a pull request (#222) that moves the error formatting to utils.py. Not much changed with the implementation but you'll want to pull that in before opening your PR


Resolving patch:
diff --git a/rest_framework_json_api/exceptions.py b/rest_framework_json_api/exceptions.py
--- a/rest_framework_json_api/exceptions.py
+++ b/rest_framework_json_api/exceptions.py
@@ -1,7 +1,16 @@
+from django.conf import settings
 from django.utils.translation import ugettext_lazy as _
 from rest_framework import status, exceptions
 
 from rest_framework_json_api import utils
+from rest_framework_json_api import renderers
+
+
+def rendered_with_json_api(view):
+    for renderer_class in getattr(view, 'renderer_classes', []):
+        if issubclass(renderer_class, renderers.JSONRenderer):
+            return True
+    return False
 
 
 def exception_handler(exc, context):
@@ -12,11 +21,26 @@ def exception_handler(exc, context):
     #
     # Also see: https://github.com/django-json-api/django-rest-framework-json-api/issues/158
     from rest_framework.views import exception_handler as drf_exception_handler
-    response = drf_exception_handler(exc, context)
 
+    # Render exception with DRF
+    response = drf_exception_handler(exc, context)
     if not response:
         return response
-    return utils.format_drf_errors(response, context, exc)
+
+    # Use regular DRF format if not rendered by DRF JSON API and not uniform
+    is_json_api_view = rendered_with_json_api(context['view'])
+    is_uniform = getattr(settings, 'JSON_API_UNIFORM_EXCEPTIONS', False)
+    if not is_json_api_view and not is_uniform:
+        return response
+
+    # Convert to DRF JSON API error format
+    response = utils.format_drf_errors(response, context, exc)
+
+    # Add top-level 'errors' object when not rendered by DRF JSON API
+    if not is_json_api_view:
+        response.data = utils.format_errors(response.data)
+
+    return response
 
 
 class Conflict(exceptions.APIException):
diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py
--- a/rest_framework_json_api/renderers.py
+++ b/rest_framework_json_api/renderers.py
@@ -381,11 +381,8 @@ def render_relationship_view(self, data, accepted_media_type=None, renderer_cont
         )
 
     def render_errors(self, data, accepted_media_type=None, renderer_context=None):
-        # Get the resource name.
-        if len(data) > 1 and isinstance(data, list):
-            data.sort(key=lambda x: x.get('source', {}).get('pointer', ''))
         return super(JSONRenderer, self).render(
-            {'errors': data}, accepted_media_type, renderer_context
+            utils.format_errors(data), accepted_media_type, renderer_context
         )
 
     def render(self, data, accepted_media_type=None, renderer_context=None):
diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py
--- a/rest_framework_json_api/utils.py
+++ b/rest_framework_json_api/utils.py
@@ -315,4 +315,11 @@ def format_drf_errors(response, context, exc):
 
     context['view'].resource_name = 'errors'
     response.data = errors
+
     return response
+
+
+def format_errors(data):
+    if len(data) > 1 and isinstance(data, list):
+        data.sort(key=lambda x: x.get('source', {}).get('pointer', ''))
+    return {'errors': data}


Test patch: