diff --git a/wsme/rest/json.py b/wsme/rest/json.py index 07388bb..abe3d4a 100644 --- a/wsme/rest/json.py +++ b/wsme/rest/json.py @@ -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: diff --git a/wsme/tests/protocol.py b/wsme/tests/protocol.py index 35a744d..b70f02d 100644 --- a/wsme/tests/protocol.py +++ b/wsme/tests/protocol.py @@ -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): diff --git a/wsme/tests/test_restjson.py b/wsme/tests/test_restjson.py index 6297b59..08af1c0 100644 --- a/wsme/tests/test_restjson.py +++ b/wsme/tests/test_restjson.py @@ -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') diff --git a/wsme/tests/test_spore.py b/wsme/tests/test_spore.py index 03b0228..60afdc9 100644 --- a/wsme/tests/test_spore.py +++ b/wsme/tests/test_spore.py @@ -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'] diff --git a/wsmeext/tests/test_soap.py b/wsmeext/tests/test_soap.py index 3e070a7..9fdfebc 100644 --- a/wsmeext/tests/test_soap.py +++ b/wsmeext/tests/test_soap.py @@ -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)