Repository: miLibris/flask-rest-jsonapi

Issue:
[Need help] Exceptions not formatted as JSON in permission_manager
> the exceptions module: you can import lot of exceptions from this module that helps you to raise exceptions that will be well formatted according to JSONAPI 1.0 specification

```python
def permission_manager(view, view_args, view_kwargs, *args, **kwargs):
    raise JsonApiException('Test', "You don't have the ability to be here")
```

It raises a 500 error as HTML.

My API init is (bootstrap.py) :
```python
from flask import current_app as app, Blueprint
from flask_rest_jsonapi import Api
from myapp.api.helpers.permission_manager import permission_manager

api_v1 = Blueprint('v1', __name__, url_prefix='/v1')
api = Api(app, api_v1)
api.permission_manager(permission_manager)
```

My app init is :
```python
app = Flask('myapp')

with app.app_context():
    from myapp.api.bootstrap import api_v1
    app.register_blueprint(api_v1)
```

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 15f8627..99c8a3f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,7 @@ python:
 install:
   - pip install -r requirements.txt
   - pip install coveralls coverage
+  - pip install -U setuptools
   - pip install pytest --upgrade
 script:
   - python setup.py install
diff --git a/flask_rest_jsonapi/api.py b/flask_rest_jsonapi/api.py
index 7b1189b..ffd5d94 100644
--- a/flask_rest_jsonapi/api.py
+++ b/flask_rest_jsonapi/api.py
@@ -8,6 +8,7 @@ import inspect
 from functools import wraps
 
 from flask_rest_jsonapi.resource import ResourceList, ResourceRelationship
+from flask_rest_jsonapi.decorators import jsonapi_exception_formatter
 
 
 class Api(object):
@@ -147,6 +148,7 @@ class Api(object):
                 return view
 
             @wraps(view)
+            @jsonapi_exception_formatter
             def decorated(*view_args, **view_kwargs):
                 self.check_permissions(view, view_args, view_kwargs, *args, **kwargs)
                 return view(*view_args, **view_kwargs)
diff --git a/flask_rest_jsonapi/decorators.py b/flask_rest_jsonapi/decorators.py
index 9bcb383..39a457f 100644
--- a/flask_rest_jsonapi/decorators.py
+++ b/flask_rest_jsonapi/decorators.py
@@ -4,9 +4,10 @@
 
 from functools import wraps
 
-from flask import request, make_response, jsonify
+from flask import request, make_response, jsonify, current_app
 
 from flask_rest_jsonapi.errors import jsonapi_errors
+from flask_rest_jsonapi.exceptions import JsonApiException
 
 
 def check_headers(func):
@@ -63,3 +64,33 @@ def check_method_requirements(func):
 
         return func(*args, **kwargs)
     return wrapper
+
+
+def jsonapi_exception_formatter(func):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        headers = {'Content-Type': 'application/vnd.api+json'}
+        try:
+            return func(*args, **kwargs)
+        except JsonApiException as e:
+            return make_response(jsonify(jsonapi_errors([e.to_dict()])),
+                                 e.status,
+                                 headers)
+        except Exception as e:
+            if current_app.config['DEBUG'] is True:
+                raise e
+
+            exc = JsonApiException(getattr(e,
+                                           'detail',
+                                           current_app.config.get('GLOBAL_ERROR_MESSAGE') or str(e)),
+                                   source=getattr(e, 'source', ''),
+                                   title=getattr(e, 'title', None),
+                                   status=getattr(e, 'status', None),
+                                   code=getattr(e, 'code', None),
+                                   id_=getattr(e, 'id', None),
+                                   links=getattr(e, 'links', None),
+                                   meta=getattr(e, 'meta', None))
+            return make_response(jsonify(jsonapi_errors([exc.to_dict()])),
+                                 exc.status,
+                                 headers)
+    return wrapper
diff --git a/flask_rest_jsonapi/resource.py b/flask_rest_jsonapi/resource.py
index 0504c33..6e8f1fd 100644
--- a/flask_rest_jsonapi/resource.py
+++ b/flask_rest_jsonapi/resource.py
@@ -15,7 +15,7 @@ from flask_rest_jsonapi.errors import jsonapi_errors
 from flask_rest_jsonapi.querystring import QueryStringManager as QSManager
 from flask_rest_jsonapi.pagination import add_pagination_links
 from flask_rest_jsonapi.exceptions import InvalidType, BadRequest, JsonApiException, RelationNotFound
-from flask_rest_jsonapi.decorators import check_headers, check_method_requirements
+from flask_rest_jsonapi.decorators import check_headers, check_method_requirements, jsonapi_exception_formatter
 from flask_rest_jsonapi.schema import compute_schema, get_relationships, get_model_field
 from flask_rest_jsonapi.data_layers.base import BaseDataLayer
 from flask_rest_jsonapi.data_layers.alchemy import SqlalchemyDataLayer
@@ -57,6 +57,7 @@ class Resource(MethodView):
 
         return super(Resource, cls).__new__(cls)
 
+    @jsonapi_exception_formatter
     def dispatch_request(self, *args, **kwargs):
         """Logic of how to handle a request"""
         method = getattr(self, request.method.lower(), None)
@@ -66,32 +67,7 @@ class Resource(MethodView):
 
         headers = {'Content-Type': 'application/vnd.api+json'}
 
-        try:
-            response = method(*args, **kwargs)
-        except JsonApiException as e:
-            return make_response(jsonify(jsonapi_errors([e.to_dict()])),
-                                 e.status,
-                                 headers)
-        except Exception as e:
-            if current_app.config['DEBUG'] is True:
-                raise e
-
-            if 'sentry' in current_app.extensions:
-                current_app.extensions['sentry'].captureException()
-
-            exc = JsonApiException(getattr(e,
-                                           'detail',
-                                           current_app.config.get('GLOBAL_ERROR_MESSAGE') or str(e)),
-                                   source=getattr(e, 'source', ''),
-                                   title=getattr(e, 'title', None),
-                                   status=getattr(e, 'status', None),
-                                   code=getattr(e, 'code', None),
-                                   id_=getattr(e, 'id', None),
-                                   links=getattr(e, 'links', None),
-                                   meta=getattr(e, 'meta', None))
-            return make_response(jsonify(jsonapi_errors([exc.to_dict()])),
-                                 exc.status,
-                                 headers)
+        response = method(*args, **kwargs)
 
         if isinstance(response, Response):
             response.headers.add('Content-Type', 'application/vnd.api+json')


Test patch:
diff --git a/tests/test_sqlalchemy_data_layer.py b/tests/test_sqlalchemy_data_layer.py
index d5f62a3..397c5e1 100644
--- a/tests/test_sqlalchemy_data_layer.py
+++ b/tests/test_sqlalchemy_data_layer.py
@@ -415,6 +415,7 @@ def test_resource(app, person_model, person_schema, session, monkeypatch):
         rd.schema = person_schema
         monkeypatch.setattr(flask_rest_jsonapi.resource, 'request', request)
         monkeypatch.setattr(flask_rest_jsonapi.resource, 'current_app', app)
+        monkeypatch.setattr(flask_rest_jsonapi.decorators, 'current_app', app)
         monkeypatch.setattr(flask_rest_jsonapi.decorators, 'request', request)
         monkeypatch.setattr(rl.schema, 'load', schema_load_mock)
         r = super(flask_rest_jsonapi.resource.Resource, ResourceList)\