summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2014-06-25 17:52:06 -0400
committerRyan Petrello <lists@ryanpetrello.com>2014-06-25 18:12:30 -0400
commit7df71f31ae70f0d6e4e6c7511f5ba35c2977af5e (patch)
tree6b400a7685e13353414bd4808f1f32ba0ba8cc9d
parent3ab38a675086426aca086dbd66bfde667ecb6ade (diff)
downloadpecan-7df71f31ae70f0d6e4e6c7511f5ba35c2977af5e.tar.gz
Serialize WebOb errors as JSON if the client requests it via an Accept header.
Change-Id: I32040eff4259daf7a0e58b81ce861758d1d14bd9 Fixes bug 1324134
-rw-r--r--pecan/core.py21
-rw-r--r--pecan/tests/test_base.py37
2 files changed, 55 insertions, 3 deletions
diff --git a/pecan/core.py b/pecan/core.py
index eb3fe69..0cfb3eb 100644
--- a/pecan/core.py
+++ b/pecan/core.py
@@ -1,7 +1,7 @@
try:
- from simplejson import loads
-except ImportError: # pragma: no cover
- from json import loads # noqa
+ from simplejson import dumps, loads
+except ImportError: # pragma: no cover
+ from json import dumps, loads # noqa
from itertools import chain
from mimetypes import guess_type, add_type
from os.path import splitext
@@ -585,7 +585,22 @@ class PecanBase(object):
except Exception as e:
# if this is an HTTP Exception, set it as the response
if isinstance(e, exc.HTTPException):
+ # if the client asked for JSON, do our best to provide it
+ best_match = acceptparse.MIMEAccept(
+ getattr(req.accept, 'header_value', '*/*')
+ ).best_match(('text/plain', 'text/html', 'application/json'))
state.response = e
+ if best_match == 'application/json':
+ json_body = dumps({
+ 'code': e.status_int,
+ 'title': e.title,
+ 'description': e.detail
+ })
+ if isinstance(json_body, six.text_type):
+ e.text = json_body
+ else:
+ e.body = json_body
+ state.response.content_type = best_match
environ['pecan.original_exception'] = e
# if this is not an internal redirect, run error hooks
diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py
index c076139..b69f824 100644
--- a/pecan/tests/test_base.py
+++ b/pecan/tests/test_base.py
@@ -8,6 +8,7 @@ else:
import unittest # pragma: nocover
import webob
+from webob.exc import HTTPNotFound
from webtest import TestApp
import six
from six import b as b_
@@ -760,6 +761,42 @@ class TestControllerArguments(PecanTestCase):
assert r.body == b_('eater: 10, dummy, day=12, month=1')
+class TestDefaultErrorRendering(PecanTestCase):
+
+ def test_plain_error(self):
+ class RootController(object):
+ pass
+
+ app = TestApp(Pecan(RootController()))
+ r = app.get('/', status=404)
+ assert r.status_int == 404
+ assert r.content_type == 'text/plain'
+ assert r.body == b_(HTTPNotFound().plain_body({}))
+
+ def test_html_error(self):
+ class RootController(object):
+ pass
+
+ app = TestApp(Pecan(RootController()))
+ r = app.get('/', headers={'Accept': 'text/html'}, status=404)
+ assert r.status_int == 404
+ assert r.content_type == 'text/html'
+ assert r.body == b_(HTTPNotFound().html_body({}))
+
+ def test_json_error(self):
+ class RootController(object):
+ pass
+
+ app = TestApp(Pecan(RootController()))
+ r = app.get('/', headers={'Accept': 'application/json'}, status=404)
+ assert r.status_int == 404
+ json_resp = json.loads(r.body.decode())
+ assert json_resp['code'] == 404
+ assert json_resp['description'] is None
+ assert json_resp['title'] == 'Not Found'
+ assert r.content_type == 'application/json'
+
+
class TestAbort(PecanTestCase):
def test_abort(self):