Convert built-in types when passed as strings
If on a service exposed with some arguments with a built-in type among int, long, bool or float a request is made passing a JSON with string values instead of the intended type, the called function would have strings as parameters instead of the expected types. This also means that invalid strings would still be passed without error, leading to unexpected failures. This patch tries to convert the string to the intended type before failing with an InvalidInput exception if the string can't be converted. This is to try and be as nice as possible with whatever input is thrown at wsme. Closes-Bug: 1450544 Change-Id: I705c183bb68457d539074b78ce81339b9464e1e0
This commit is contained in:
parent
eb37037f54
commit
9a0d3c1461
@ -27,6 +27,8 @@ accept_content_types = [
|
||||
'text/javascript',
|
||||
'application/javascript'
|
||||
]
|
||||
ENUM_TRUE = ('true', 't', 'yes', 'y', 'on', '1')
|
||||
ENUM_FALSE = ('false', 'f', 'no', 'n', 'off', '0')
|
||||
|
||||
|
||||
@generic
|
||||
@ -182,6 +184,28 @@ def text_fromjson(datatype, value):
|
||||
return value
|
||||
|
||||
|
||||
@fromjson.when_object(*six.integer_types + (float,))
|
||||
def numeric_fromjson(datatype, value):
|
||||
"""Convert string object to built-in types int, long or float."""
|
||||
if value is None:
|
||||
return None
|
||||
return datatype(value)
|
||||
|
||||
|
||||
@fromjson.when_object(bool)
|
||||
def bool_fromjson(datatype, value):
|
||||
"""Convert to bool, restricting strings to just unambiguous values."""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, six.integer_types + (bool,)):
|
||||
return bool(value)
|
||||
if value in ENUM_TRUE:
|
||||
return True
|
||||
if value in ENUM_FALSE:
|
||||
return False
|
||||
raise ValueError("Value not an unambiguous boolean: %s" % value)
|
||||
|
||||
|
||||
@fromjson.when_object(decimal.Decimal)
|
||||
def decimal_fromjson(datatype, value):
|
||||
if value is None:
|
||||
|
@ -65,6 +65,11 @@ class NamedAttrsObject(object):
|
||||
attr_2 = wsme.types.wsattr(int, name='attr.2')
|
||||
|
||||
|
||||
class CustomObject(object):
|
||||
aint = int
|
||||
name = wsme.types.text
|
||||
|
||||
|
||||
class NestedInnerApi(object):
|
||||
@expose(bool)
|
||||
def deepfunction(self):
|
||||
@ -166,6 +171,10 @@ class ArgTypes(object):
|
||||
if not (a == b):
|
||||
raise AssertionError('%s != %s' % (a, b))
|
||||
|
||||
def assertIsInstance(self, value, v_type):
|
||||
assert isinstance(value, v_type), ("%s is not instance of type %s" %
|
||||
(value, v_type))
|
||||
|
||||
@expose(wsme.types.bytes)
|
||||
@validate(wsme.types.bytes)
|
||||
def setbytes(self, value):
|
||||
@ -307,6 +316,14 @@ class ArgTypes(object):
|
||||
self.assertEquals(value.attr_2, 20)
|
||||
return value
|
||||
|
||||
@expose(CustomObject)
|
||||
@validate(CustomObject)
|
||||
def setcustomobject(self, value):
|
||||
self.assertIsInstance(value, CustomObject)
|
||||
self.assertIsInstance(value.name, wsme.types.text)
|
||||
self.assertIsInstance(value.aint, int)
|
||||
return value
|
||||
|
||||
|
||||
class BodyTypes(object):
|
||||
def assertEquals(self, a, b):
|
||||
|
@ -13,7 +13,7 @@ from wsme.rest.json import fromjson, tojson, parse
|
||||
from wsme.utils import parse_isodatetime, parse_isotime, parse_isodate
|
||||
from wsme.types import isarray, isdict, isusertype, register_type
|
||||
from wsme.rest import expose, validate
|
||||
from wsme.exc import InvalidInput
|
||||
from wsme.exc import ClientSideError, InvalidInput
|
||||
|
||||
|
||||
import six
|
||||
@ -270,6 +270,15 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
"Unknown argument:"
|
||||
)
|
||||
|
||||
def test_set_custom_object(self):
|
||||
r = self.app.post(
|
||||
'/argtypes/setcustomobject',
|
||||
'{"value": {"aint": 2, "name": "test"}}',
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
self.assertEqual(r.status_int, 200)
|
||||
self.assertEqual(r.json, {'aint': 2, 'name': 'test'})
|
||||
|
||||
def test_unset_attrs(self):
|
||||
class AType(object):
|
||||
attr = int
|
||||
@ -328,6 +337,107 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert e.value == jdate
|
||||
assert e.msg == "'%s' is not a legal date value" % jdate
|
||||
|
||||
def test_valid_str_to_builtin_fromjson(self):
|
||||
types = six.integer_types + (bool, float)
|
||||
value = '2'
|
||||
for t in types:
|
||||
for ba in True, False:
|
||||
jd = '%s' if ba else '{"a": %s}'
|
||||
i = parse(jd % value, {'a': t}, ba)
|
||||
self.assertEqual(
|
||||
i, {'a': t(value)},
|
||||
"Parsed value does not correspond for %s: "
|
||||
"%s != {'a': %s}" % (
|
||||
t, repr(i), repr(t(value))
|
||||
)
|
||||
)
|
||||
self.assertIsInstance(i['a'], t)
|
||||
|
||||
def test_valid_int_fromjson(self):
|
||||
value = 2
|
||||
for ba in True, False:
|
||||
jd = '%d' if ba else '{"a": %d}'
|
||||
i = parse(jd % value, {'a': int}, ba)
|
||||
self.assertEqual(i, {'a': 2})
|
||||
self.assertIsInstance(i['a'], int)
|
||||
|
||||
def test_valid_num_to_float_fromjson(self):
|
||||
values = 2, 2.3
|
||||
for v in values:
|
||||
for ba in True, False:
|
||||
jd = '%f' if ba else '{"a": %f}'
|
||||
i = parse(jd % v, {'a': float}, ba)
|
||||
self.assertEqual(i, {'a': float(v)})
|
||||
self.assertIsInstance(i['a'], float)
|
||||
|
||||
def test_invalid_str_to_buitin_fromjson(self):
|
||||
types = six.integer_types + (float, bool)
|
||||
value = '2a'
|
||||
for t in types:
|
||||
for ba in True, False:
|
||||
jd = '"%s"' if ba else '{"a": "%s"}'
|
||||
try:
|
||||
parse(jd % value, {'a': t}, ba)
|
||||
assert False, (
|
||||
"Value '%s' should not parse correctly for %s." %
|
||||
(value, t)
|
||||
)
|
||||
except ClientSideError as e:
|
||||
self.assertIsInstance(e, InvalidInput)
|
||||
self.assertEqual(e.fieldname, 'a')
|
||||
self.assertEqual(e.value, value)
|
||||
|
||||
def test_ambiguous_to_bool(self):
|
||||
amb_values = ('', 'randomstring', '2', '-32', 'not true')
|
||||
for value in amb_values:
|
||||
for ba in True, False:
|
||||
jd = '"%s"' if ba else '{"a": "%s"}'
|
||||
try:
|
||||
parse(jd % value, {'a': bool}, ba)
|
||||
assert False, (
|
||||
"Value '%s' should not parse correctly for %s." %
|
||||
(value, bool)
|
||||
)
|
||||
except ClientSideError as e:
|
||||
self.assertIsInstance(e, InvalidInput)
|
||||
self.assertEqual(e.fieldname, 'a')
|
||||
self.assertEqual(e.value, value)
|
||||
|
||||
def test_true_strings_to_bool(self):
|
||||
true_values = ('true', 't', 'yes', 'y', 'on', '1')
|
||||
for value in true_values:
|
||||
for ba in True, False:
|
||||
jd = '"%s"' if ba else '{"a": "%s"}'
|
||||
i = parse(jd % value, {'a': bool}, ba)
|
||||
self.assertIsInstance(i['a'], bool)
|
||||
self.assertTrue(i['a'])
|
||||
|
||||
def test_false_strings_to_bool(self):
|
||||
false_values = ('false', 'f', 'no', 'n', 'off', '0')
|
||||
for value in false_values:
|
||||
for ba in True, False:
|
||||
jd = '"%s"' if ba else '{"a": "%s"}'
|
||||
i = parse(jd % value, {'a': bool}, ba)
|
||||
self.assertIsInstance(i['a'], bool)
|
||||
self.assertFalse(i['a'])
|
||||
|
||||
def test_true_ints_to_bool(self):
|
||||
true_values = (1, 5, -3)
|
||||
for value in true_values:
|
||||
for ba in True, False:
|
||||
jd = '%d' if ba else '{"a": %d}'
|
||||
i = parse(jd % value, {'a': bool}, ba)
|
||||
self.assertIsInstance(i['a'], bool)
|
||||
self.assertTrue(i['a'])
|
||||
|
||||
def test_false_ints_to_bool(self):
|
||||
value = 0
|
||||
for ba in True, False:
|
||||
jd = '%d' if ba else '{"a": %d}'
|
||||
i = parse(jd % value, {'a': bool}, ba)
|
||||
self.assertIsInstance(i['a'], bool)
|
||||
self.assertFalse(i['a'])
|
||||
|
||||
def test_nest_result(self):
|
||||
self.root.protocols[0].nest_result = True
|
||||
r = self.app.get('/returntypes/getint.json')
|
||||
|
@ -18,7 +18,7 @@ class TestSpore(unittest.TestCase):
|
||||
|
||||
spore = json.loads(spore)
|
||||
|
||||
assert len(spore['methods']) == 49, str(len(spore['methods']))
|
||||
assert len(spore['methods']) == 50, str(len(spore['methods']))
|
||||
|
||||
m = spore['methods']['argtypes_setbytesarray']
|
||||
assert m['path'] == 'argtypes/setbytesarray', m['path']
|
||||
|
@ -397,7 +397,7 @@ class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
|
||||
|
||||
assert len(sd.ports) == 1
|
||||
port, methods = sd.ports[0]
|
||||
self.assertEquals(len(methods), 49)
|
||||
self.assertEquals(len(methods), 50)
|
||||
|
||||
methods = dict(methods)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user