mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	Add validators.py (with tests).
This sets up a scheme to validate complex data structures and give specific error messages for improperly typed parameters. (imported from commit 33b2f070d993da4ee929119dd41503bd0128c8eb)
This commit is contained in:
		
							
								
								
									
										76
									
								
								zerver/lib/validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								zerver/lib/validator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
'''
 | 
			
		||||
This module sets up a scheme for validating that arbitrary Python
 | 
			
		||||
objects are correctly typed.  It is totally decoupled from Django,
 | 
			
		||||
composable, easily wrapped, and easily extended.
 | 
			
		||||
 | 
			
		||||
A validator takes two parameters--var_name and val--and returns an
 | 
			
		||||
error if val is not the correct type.  The var_name parameter is used
 | 
			
		||||
to format error messages.  Validators return None when there are no errors.
 | 
			
		||||
 | 
			
		||||
Example primitive validators are check_string, check_int, and check_bool.
 | 
			
		||||
 | 
			
		||||
Compound validators are created by check_list and check_dict.  Note that
 | 
			
		||||
those functions aren't directly called for validation; instead, those
 | 
			
		||||
functions are called to return other functions that adhere to the validator
 | 
			
		||||
contract.  This is similar to how Python decorators are often parameterized.
 | 
			
		||||
 | 
			
		||||
The contract for check_list and check_dict is that they get passed in other
 | 
			
		||||
validators to apply to their items.  This allows you to build up validators
 | 
			
		||||
for arbitrarily complex validators.  See ValidatorTestCase for example usage.
 | 
			
		||||
 | 
			
		||||
A simple example of composition is this:
 | 
			
		||||
 | 
			
		||||
   check_list(check_string)('my_list', ['a', 'b', 'c']) == None
 | 
			
		||||
 | 
			
		||||
To extend this concept, it's simply a matter of writing your own validator
 | 
			
		||||
for any particular type of object.
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
def check_string(var_name, val):
 | 
			
		||||
    if not isinstance(val, basestring):
 | 
			
		||||
        return '%s is not a string' % (var_name,)
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
def check_int(var_name, val):
 | 
			
		||||
    if not isinstance(val, int):
 | 
			
		||||
        return '%s is not an integer' % (var_name,)
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
def check_bool(var_name, val):
 | 
			
		||||
    if not isinstance(val, bool):
 | 
			
		||||
        return '%s is not a boolean' % (var_name,)
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
def check_list(sub_validator):
 | 
			
		||||
    def f(var_name, val):
 | 
			
		||||
        if not isinstance(val, list):
 | 
			
		||||
            return '%s is not a list' % (var_name,)
 | 
			
		||||
 | 
			
		||||
        for i, item in enumerate(val):
 | 
			
		||||
            vname = '%s[%d]' % (var_name, i)
 | 
			
		||||
            error = sub_validator(vname, item)
 | 
			
		||||
            if error:
 | 
			
		||||
                return error
 | 
			
		||||
 | 
			
		||||
        return None
 | 
			
		||||
    return f
 | 
			
		||||
 | 
			
		||||
def check_dict(required_keys):
 | 
			
		||||
    # required_keys is a list of tuples of
 | 
			
		||||
    # key_name/validator
 | 
			
		||||
 | 
			
		||||
    def f(var_name, val):
 | 
			
		||||
        if not isinstance(val, dict):
 | 
			
		||||
            return '%s is not a dict' % (var_name,)
 | 
			
		||||
 | 
			
		||||
        for k, sub_validator in required_keys:
 | 
			
		||||
            if k not in val:
 | 
			
		||||
                return '%s key is missing from %s' % (k, var_name)
 | 
			
		||||
            vname = '%s["%s"]' % (var_name, k)
 | 
			
		||||
            error = sub_validator(vname, val[k])
 | 
			
		||||
            if error:
 | 
			
		||||
                return error
 | 
			
		||||
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    return f
 | 
			
		||||
@@ -31,6 +31,8 @@ from zerver.lib.alert_words import alert_words_in_realm, user_alert_words, \
 | 
			
		||||
    add_user_alert_words, remove_user_alert_words
 | 
			
		||||
from zerver.lib.digest import send_digest_email
 | 
			
		||||
from zerver.forms import not_mit_mailing_list
 | 
			
		||||
from zerver.lib.validator import check_string, check_list, check_dict, \
 | 
			
		||||
    check_bool, check_int
 | 
			
		||||
 | 
			
		||||
from zerver.worker import queue_processors
 | 
			
		||||
 | 
			
		||||
@@ -198,6 +200,93 @@ def is_known_slow_test(test_method):
 | 
			
		||||
 | 
			
		||||
API_KEYS = {}
 | 
			
		||||
 | 
			
		||||
class ValidatorTestCase(TestCase):
 | 
			
		||||
    def test_check_string(self):
 | 
			
		||||
        x = "hello"
 | 
			
		||||
        self.assertEqual(check_string('x', x), None)
 | 
			
		||||
 | 
			
		||||
        x = 4
 | 
			
		||||
        self.assertEqual(check_string('x', x), 'x is not a string')
 | 
			
		||||
 | 
			
		||||
    def test_check_bool(self):
 | 
			
		||||
        x = True
 | 
			
		||||
        self.assertEqual(check_bool('x', x), None)
 | 
			
		||||
 | 
			
		||||
        x = 4
 | 
			
		||||
        self.assertEqual(check_bool('x', x), 'x is not a boolean')
 | 
			
		||||
 | 
			
		||||
    def test_check_int(self):
 | 
			
		||||
        x = 5
 | 
			
		||||
        self.assertEqual(check_int('x', x), None)
 | 
			
		||||
 | 
			
		||||
        x = [{}]
 | 
			
		||||
        self.assertEqual(check_int('x', x), 'x is not an integer')
 | 
			
		||||
 | 
			
		||||
    def test_check_list(self):
 | 
			
		||||
        x = 999
 | 
			
		||||
        error = check_list(check_string)('x', x)
 | 
			
		||||
        self.assertEqual(error, 'x is not a list')
 | 
			
		||||
 | 
			
		||||
        x = ["hello", 5]
 | 
			
		||||
        error = check_list(check_string)('x', x)
 | 
			
		||||
        self.assertEqual(error, 'x[1] is not a string')
 | 
			
		||||
 | 
			
		||||
        x = [["yo"], ["hello", "goodbye", 5]]
 | 
			
		||||
        error = check_list(check_list(check_string))('x', x)
 | 
			
		||||
        self.assertEqual(error, 'x[1][2] is not a string')
 | 
			
		||||
 | 
			
		||||
    def test_check_dict(self):
 | 
			
		||||
        keys = [
 | 
			
		||||
            ('names', check_list(check_string)),
 | 
			
		||||
            ('city', check_string),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        x = {
 | 
			
		||||
            'names': ['alice', 'bob'],
 | 
			
		||||
            'city': 'Boston',
 | 
			
		||||
        }
 | 
			
		||||
        error = check_dict(keys)('x', x)
 | 
			
		||||
        self.assertEqual(error, None)
 | 
			
		||||
 | 
			
		||||
        x = 999
 | 
			
		||||
        error = check_dict(keys)('x', x)
 | 
			
		||||
        self.assertEqual(error, 'x is not a dict')
 | 
			
		||||
 | 
			
		||||
        x = {}
 | 
			
		||||
        error = check_dict(keys)('x', x)
 | 
			
		||||
        self.assertEqual(error, 'names key is missing from x')
 | 
			
		||||
 | 
			
		||||
        x = {
 | 
			
		||||
            'names': ['alice', 'bob', {}]
 | 
			
		||||
        }
 | 
			
		||||
        error = check_dict(keys)('x', x)
 | 
			
		||||
        self.assertEqual(error, 'x["names"][2] is not a string')
 | 
			
		||||
 | 
			
		||||
        x = {
 | 
			
		||||
            'names': ['alice', 'bob'],
 | 
			
		||||
            'city': 5
 | 
			
		||||
        }
 | 
			
		||||
        error = check_dict(keys)('x', x)
 | 
			
		||||
        self.assertEqual(error, 'x["city"] is not a string')
 | 
			
		||||
 | 
			
		||||
    def test_encapsulation(self):
 | 
			
		||||
        # There might be situations where we want deep
 | 
			
		||||
        # validation, but the error message should be customized.
 | 
			
		||||
        # This is an example.
 | 
			
		||||
        def check_person(val):
 | 
			
		||||
            error = check_dict([
 | 
			
		||||
                ['name', check_string],
 | 
			
		||||
                ['age', check_int],
 | 
			
		||||
            ])('_', val)
 | 
			
		||||
            if error:
 | 
			
		||||
                return 'This is not a valid person'
 | 
			
		||||
 | 
			
		||||
        person = {'name': 'King Lear', 'age': 42}
 | 
			
		||||
        self.assertEqual(check_person(person), None)
 | 
			
		||||
 | 
			
		||||
        person = 'misconfigured data'
 | 
			
		||||
        self.assertEqual(check_person(person), 'This is not a valid person')
 | 
			
		||||
 | 
			
		||||
class AuthedTestCase(TestCase):
 | 
			
		||||
    # Helper because self.client.patch annoying requires you to urlencode
 | 
			
		||||
    def client_patch(self, url, info={}, **kwargs):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user