From 7df71f31ae70f0d6e4e6c7511f5ba35c2977af5e Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 25 Jun 2014 17:52:06 -0400 Subject: Serialize WebOb errors as JSON if the client requests it via an Accept header. Change-Id: I32040eff4259daf7a0e58b81ce861758d1d14bd9 Fixes bug 1324134 --- pecan/core.py | 21 ++++++++++++++++++--- pecan/tests/test_base.py | 37 +++++++++++++++++++++++++++++++++++++ 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): -- cgit v1.2.1