Add custom error code to ClientSideError

Added custom error status code for ClientSideError
exception instead of hardcoded value 400.
Fixed case when user exception with client error code
formatted as server error.
Pecan extension fixed.

Change-Id: I2663db0aa88538b722eb2783d130585b0fc2335b
This commit is contained in:
Yuriy Zveryanskyy 2013-10-09 11:26:29 +03:00
parent a84d744794
commit 9546c10250
14 changed files with 83 additions and 58 deletions

View File

@ -94,6 +94,9 @@ class AuthorsController(RestController):
if id == 997: if id == 997:
raise NonHttpException(id) raise NonHttpException(id)
if id == 996:
raise wsme.exc.ClientSideError('Disabled ID', status_code=403)
if id == 911: if id == 911:
return wsme.api.Response(Author(), return wsme.api.Response(Author(),
status_code=401) status_code=401)

View File

@ -5,7 +5,7 @@ import pecan
import six import six
used_status_codes = [400, 401, 404, 500] used_status_codes = [400, 401, 403, 404, 500]
http_response_messages = {} http_response_messages = {}
for code in used_status_codes: for code in used_status_codes:
http_response_messages[code] = '%s %s' % (code, http_client.responses[code]) http_response_messages[code] = '%s %s' % (code, http_client.responses[code])
@ -96,14 +96,14 @@ class TestWS(FunctionalTest):
) )
self.assertEqual(res.status, expected_status) self.assertEqual(res.status, expected_status)
a = json.loads(res.body.decode('utf-8')) a = json.loads(res.body.decode('utf-8'))
assert a['faultcode'] == 'Server' assert a['faultcode'] == 'Client'
res = self.app.get( res = self.app.get(
'/authors/998.xml', '/authors/998.xml',
expect_errors=True expect_errors=True
) )
self.assertEqual(res.status, expected_status) self.assertEqual(res.status, expected_status)
assert '<faultcode>Server</faultcode>' in res.body.decode('utf-8') assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8')
def test_custom_non_http_clientside_error(self): def test_custom_non_http_clientside_error(self):
expected_status_code = 500 expected_status_code = 500
@ -123,6 +123,24 @@ class TestWS(FunctionalTest):
self.assertEqual(res.status, expected_status) self.assertEqual(res.status, expected_status)
assert '<faultcode>Server</faultcode>' in res.body.decode('utf-8') assert '<faultcode>Server</faultcode>' in res.body.decode('utf-8')
def test_clientsideerror_status_code(self):
expected_status_code = 403
expected_status = http_response_messages[expected_status_code]
res = self.app.get(
'/authors/996.json',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
a = json.loads(res.body.decode('utf-8'))
assert a['faultcode'] == 'Client'
res = self.app.get(
'/authors/996.xml',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8')
def test_non_default_response(self): def test_non_default_response(self):
expected_status_code = 401 expected_status_code = 401
expected_status = http_response_messages[expected_status_code] expected_status = http_response_messages[expected_status_code]

View File

@ -181,5 +181,5 @@ class WSMECorniceTestCase(unittest.TestCase):
expect_errors=True expect_errors=True
) )
print resp.body print resp.body
self.assertEquals(resp.json['faultcode'], 'Server') self.assertEquals(resp.json['faultcode'], 'Client')
self.assertEquals(resp.status_code, 401) self.assertEquals(resp.status_code, 401)

View File

@ -138,7 +138,7 @@ class FlaskrTestCase(unittest.TestCase):
headers={'Accept': 'application/xml'} headers={'Accept': 'application/xml'}
) )
assert r.status_code == 403, r.status_code assert r.status_code == 403, r.status_code
assert r.data == ('<error><faultcode>Server</faultcode>' assert r.data == ('<error><faultcode>Client</faultcode>'
'<faultstring>403: Forbidden</faultstring>' '<faultstring>403: Forbidden</faultstring>'
'<debuginfo /></error>') '<debuginfo /></error>')
@ -155,7 +155,7 @@ class FlaskrTestCase(unittest.TestCase):
headers={'Accept': 'application/xml'} headers={'Accept': 'application/xml'}
) )
assert r.status_code == 412, r.status_code assert r.status_code == 412, r.status_code
assert r.data == ('<error><faultcode>Server</faultcode>' assert r.data == ('<error><faultcode>Client</faultcode>'
'<faultstring>FOO!</faultstring>' '<faultstring>FOO!</faultstring>'
'<debuginfo /></error>') '<debuginfo /></error>')

View File

@ -109,7 +109,7 @@ class TestController(testutil.TGTest):
assert response.status_int == 400 assert response.status_int == 400
assert simplejson.loads(response.body) == { assert simplejson.loads(response.body) == {
"debuginfo": None, "debuginfo": None,
"faultcode": "Server", "faultcode": "Client",
"faultstring": "(400, 'Cannot divide by zero!')" "faultstring": "(400, 'Cannot divide by zero!')"
} }
@ -120,7 +120,7 @@ class TestController(testutil.TGTest):
expect_errors=True expect_errors=True
) )
assert response.status_int == 400 assert response.status_int == 400
assert response.body == ("<error><faultcode>Server</faultcode>" assert response.body == ("<error><faultcode>Client</faultcode>"
"<faultstring>(400, 'Cannot divide by zero!')" "<faultstring>(400, 'Cannot divide by zero!')"
"</faultstring><debuginfo /></error>") "</faultstring><debuginfo /></error>")

View File

@ -6,6 +6,8 @@ import logging
import wsme.exc import wsme.exc
import wsme.types import wsme.types
from wsme import utils
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -200,9 +202,12 @@ class Response(object):
def format_exception(excinfo, debug=False): def format_exception(excinfo, debug=False):
"""Extract informations that can be sent to the client.""" """Extract informations that can be sent to the client."""
error = excinfo[1] error = excinfo[1]
if isinstance(error, wsme.exc.ClientSideError): code = getattr(error, 'code', None)
if code and utils.is_valid_code(code) and utils.is_client_error(code):
faultstring = error.faultstring if hasattr(error, 'faultstring') \
else str(error)
r = dict(faultcode="Client", r = dict(faultcode="Client",
faultstring=error.faultstring) faultstring=faultstring)
log.warning("Client-side error: %s" % r['faultstring']) log.warning("Client-side error: %s" % r['faultstring'])
r['debuginfo'] = None r['debuginfo'] = None
return r return r

View File

@ -4,8 +4,9 @@ from wsme.utils import _
class ClientSideError(RuntimeError): class ClientSideError(RuntimeError):
def __init__(self, msg=None): def __init__(self, msg=None, status_code=400):
self.msg = msg self.msg = msg
self.code = status_code
super(ClientSideError, self).__init__(self.faultstring) super(ClientSideError, self).__init__(self.faultstring)
@property @property

View File

@ -1,7 +1,7 @@
import datetime import datetime
import unittest import unittest
from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime from wsme import utils
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):
@ -18,9 +18,9 @@ class TestUtils(unittest.TestCase):
'2012-02-30', '2012-02-30',
] ]
for s, d in good_dates: for s, d in good_dates:
assert parse_isodate(s) == d assert utils.parse_isodate(s) == d
for s in ill_formatted_dates + out_of_range_dates: for s in ill_formatted_dates + out_of_range_dates:
self.assertRaises(ValueError, parse_isodate, s) self.assertRaises(ValueError, utils.parse_isodate, s)
def test_parse_isotime(self): def test_parse_isotime(self):
good_times = [ good_times = [
@ -35,9 +35,9 @@ class TestUtils(unittest.TestCase):
'00:54:60', '00:54:60',
] ]
for s, t in good_times: for s, t in good_times:
assert parse_isotime(s) == t assert utils.parse_isotime(s) == t
for s in ill_formatted_times + out_of_range_times: for s in ill_formatted_times + out_of_range_times:
self.assertRaises(ValueError, parse_isotime, s) self.assertRaises(ValueError, utils.parse_isotime, s)
def test_parse_isodatetime(self): def test_parse_isodatetime(self):
good_datetimes = [ good_datetimes = [
@ -54,6 +54,27 @@ class TestUtils(unittest.TestCase):
'2012-13-12T00:54:60', '2012-13-12T00:54:60',
] ]
for s, t in good_datetimes: for s, t in good_datetimes:
assert parse_isodatetime(s) == t assert utils.parse_isodatetime(s) == t
for s in ill_formatted_datetimes + out_of_range_datetimes: for s in ill_formatted_datetimes + out_of_range_datetimes:
self.assertRaises(ValueError, parse_isodatetime, s) self.assertRaises(ValueError, utils.parse_isodatetime, s)
def test_validator_with_valid_code(self):
valid_code = 404
assert (
utils.is_valid_code(valid_code),
"Valid status code not detected"
)
def test_validator_with_invalid_int_code(self):
invalid_int_code = 648
assert (
not utils.is_valid_code(invalid_int_code),
"Invalid status code not detected"
)
def test_validator_with_invalid_str_code(self):
invalid_str_code = '404'
assert (
not utils.is_valid_code(invalid_str_code),
"Invalid status code not detected"
)

View File

@ -1,7 +1,7 @@
import decimal import decimal
import datetime import datetime
import re import re
from six.moves import builtins from six.moves import builtins, http_client
try: try:
import dateutil.parser import dateutil.parser
@ -84,6 +84,18 @@ def parse_isodatetime(value):
raise ValueError("'%s' is a out-of-range datetime" % (value)) raise ValueError("'%s' is a out-of-range datetime" % (value))
def is_valid_code(code_value):
"""
This function checks if incoming value in http response codes range.
"""
return code_value in http_client.responses
def is_client_error(code):
""" Checks client error code (RFC 2616)."""
return 400 <= code < 500
try: try:
from collections import OrderedDict from collections import OrderedDict
except ImportError: except ImportError:

View File

@ -9,7 +9,7 @@ import wsme.api
import wsme.rest.json import wsme.rest.json
import wsme.rest.xml import wsme.rest.xml
import wsme.rest.args import wsme.rest.args
from wsmeext.utils import is_valid_code from wsme.utils import is_valid_code
import flask import flask

View File

@ -11,7 +11,7 @@ import wsme.rest.xml
import pecan import pecan
from wsmeext.utils import is_valid_code from wsme.utils import is_valid_code
class JSonRenderer(object): class JSonRenderer(object):
@ -94,9 +94,7 @@ def wsexpose(*args, **kwargs):
finally: finally:
del exception_info del exception_info
if data['faultcode'] == 'Client': if orig_code and is_valid_code(orig_code):
pecan.response.status = 400
elif orig_code and is_valid_code(orig_code):
pecan.response.status = orig_code pecan.response.status = orig_code
else: else:
pecan.response.status = 500 pecan.response.status = 500

View File

@ -1,22 +0,0 @@
from wsmeext.utils import is_valid_code
class TestUtils():
def test_validator_with_valid_code(self):
valid_code = 404
assert is_valid_code(valid_code), "Valid status code not detected"
def test_validator_with_invalid_int_code(self):
invalid_int_code = 648
assert (
not is_valid_code(invalid_int_code),
"Invalid status code not detected"
)
def test_validator_with_invalid_str_code(self):
invalid_str_code = '404'
assert (
not is_valid_code(invalid_str_code),
"Invalid status code not detected"
)

View File

@ -14,7 +14,7 @@ from wsme.rest import validate as wsvalidate
import wsme.api import wsme.api
import wsme.rest.args import wsme.rest.args
import wsme.rest.json import wsme.rest.json
from wsmeext.utils import is_valid_code from wsme.utils import is_valid_code
import inspect import inspect

View File

@ -1,11 +0,0 @@
"""
This File consists of utils functions used in wsmeext module.
"""
from six.moves import http_client
def is_valid_code(code_value):
"""
This function checks if incoming value in http response codes range.
"""
return code_value in http_client.responses