2013-12-12 23:47:12 +01:00
|
|
|
'''
|
|
|
|
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:
|
|
|
|
|
2016-05-31 15:58:35 +02:00
|
|
|
check_list(check_string)('my_list', ['a', 'b', 'c']) is None
|
2013-12-12 23:47:12 +01:00
|
|
|
|
|
|
|
To extend this concept, it's simply a matter of writing your own validator
|
|
|
|
for any particular type of object.
|
|
|
|
'''
|
2016-05-25 15:02:02 +02:00
|
|
|
from django.utils.translation import ugettext as _
|
2017-04-10 08:06:10 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2017-06-11 09:11:59 +02:00
|
|
|
from django.core.validators import validate_email, URLValidator
|
2017-11-06 08:59:43 +01:00
|
|
|
from typing import Callable, Iterable, Optional, Tuple, TypeVar, Text
|
2017-04-10 08:06:10 +02:00
|
|
|
|
|
|
|
from zerver.lib.request import JsonableError
|
2016-06-03 20:21:57 +02:00
|
|
|
|
2017-11-06 08:59:43 +01:00
|
|
|
Validator = Callable[[str, object], Optional[str]]
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2017-11-06 08:59:43 +01:00
|
|
|
def check_string(var_name: str, val: object) -> Optional[str]:
|
2017-09-27 10:06:17 +02:00
|
|
|
if not isinstance(val, str):
|
2016-05-25 15:02:02 +02:00
|
|
|
return _('%s is not a string') % (var_name,)
|
2013-12-12 23:47:12 +01:00
|
|
|
return None
|
|
|
|
|
2017-11-06 08:59:43 +01:00
|
|
|
def check_short_string(var_name: str, val: object) -> Optional[str]:
|
2017-11-11 01:37:43 +01:00
|
|
|
if not isinstance(val, str):
|
|
|
|
return _('%s is not a string') % (var_name,)
|
2017-03-17 10:07:22 +01:00
|
|
|
max_length = 200
|
|
|
|
if len(val) >= max_length:
|
|
|
|
return _("{var_name} is longer than {max_length}.".format(
|
|
|
|
var_name=var_name, max_length=max_length))
|
2017-11-11 01:37:43 +01:00
|
|
|
return None
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2017-11-06 08:59:43 +01:00
|
|
|
def check_int(var_name: str, val: object) -> Optional[str]:
|
2013-12-12 23:47:12 +01:00
|
|
|
if not isinstance(val, int):
|
2016-05-25 15:02:02 +02:00
|
|
|
return _('%s is not an integer') % (var_name,)
|
2013-12-12 23:47:12 +01:00
|
|
|
return None
|
|
|
|
|
2017-11-06 08:59:43 +01:00
|
|
|
def check_float(var_name: str, val: object) -> Optional[str]:
|
2017-03-24 05:23:41 +01:00
|
|
|
if not isinstance(val, float):
|
|
|
|
return _('%s is not a float') % (var_name,)
|
|
|
|
return None
|
|
|
|
|
2017-11-06 08:59:43 +01:00
|
|
|
def check_bool(var_name: str, val: object) -> Optional[str]:
|
2013-12-12 23:47:12 +01:00
|
|
|
if not isinstance(val, bool):
|
2016-05-25 15:02:02 +02:00
|
|
|
return _('%s is not a boolean') % (var_name,)
|
2013-12-12 23:47:12 +01:00
|
|
|
return None
|
|
|
|
|
2017-11-06 08:37:15 +01:00
|
|
|
def check_none_or(sub_validator: Validator) -> Validator:
|
2017-11-06 08:59:43 +01:00
|
|
|
def f(var_name: str, val: object) -> Optional[str]:
|
2014-02-26 00:12:14 +01:00
|
|
|
if val is None:
|
2016-01-26 00:50:43 +01:00
|
|
|
return None
|
2014-02-26 00:12:14 +01:00
|
|
|
else:
|
|
|
|
return sub_validator(var_name, val)
|
|
|
|
return f
|
|
|
|
|
2017-11-06 08:37:15 +01:00
|
|
|
def check_list(sub_validator: Optional[Validator], length: Optional[int]=None) -> Validator:
|
2017-11-06 08:59:43 +01:00
|
|
|
def f(var_name: str, val: object) -> Optional[str]:
|
2013-12-12 23:47:12 +01:00
|
|
|
if not isinstance(val, list):
|
2016-05-25 15:02:02 +02:00
|
|
|
return _('%s is not a list') % (var_name,)
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2013-12-18 17:59:02 +01:00
|
|
|
if length is not None and length != len(val):
|
2016-05-25 15:02:02 +02:00
|
|
|
return (_('%(container)s should have exactly %(length)s items') %
|
|
|
|
{'container': var_name, 'length': length})
|
2013-12-18 18:47:32 +01:00
|
|
|
|
2014-02-14 16:25:31 +01:00
|
|
|
if sub_validator:
|
|
|
|
for i, item in enumerate(val):
|
|
|
|
vname = '%s[%d]' % (var_name, i)
|
|
|
|
error = sub_validator(vname, item)
|
|
|
|
if error:
|
|
|
|
return error
|
2013-12-12 23:47:12 +01:00
|
|
|
|
|
|
|
return None
|
|
|
|
return f
|
|
|
|
|
2018-01-07 18:55:39 +01:00
|
|
|
def check_dict(required_keys: Iterable[Tuple[str, Validator]]=[],
|
|
|
|
value_validator: Validator=None,
|
2017-11-06 08:59:43 +01:00
|
|
|
_allow_only_listed_keys: bool=False) -> Validator:
|
|
|
|
def f(var_name: str, val: object) -> Optional[str]:
|
2013-12-12 23:47:12 +01:00
|
|
|
if not isinstance(val, dict):
|
2016-05-25 15:02:02 +02:00
|
|
|
return _('%s is not a dict') % (var_name,)
|
2013-12-12 23:47:12 +01:00
|
|
|
|
|
|
|
for k, sub_validator in required_keys:
|
|
|
|
if k not in val:
|
2016-05-25 15:02:02 +02:00
|
|
|
return (_('%(key_name)s key is missing from %(var_name)s') %
|
|
|
|
{'key_name': k, 'var_name': var_name})
|
2013-12-12 23:47:12 +01:00
|
|
|
vname = '%s["%s"]' % (var_name, k)
|
|
|
|
error = sub_validator(vname, val[k])
|
|
|
|
if error:
|
|
|
|
return error
|
|
|
|
|
2018-01-07 18:55:39 +01:00
|
|
|
if value_validator:
|
|
|
|
for key in val:
|
|
|
|
vname = '%s contains a value that' % (var_name,)
|
|
|
|
error = value_validator(vname, val[key])
|
|
|
|
if error:
|
|
|
|
return error
|
|
|
|
|
2017-03-26 08:11:45 +02:00
|
|
|
if _allow_only_listed_keys:
|
|
|
|
delta_keys = set(val.keys()) - set(x[0] for x in required_keys)
|
|
|
|
if len(delta_keys) != 0:
|
|
|
|
return _("Unexpected arguments: %s" % (", ".join(list(delta_keys))))
|
|
|
|
|
2013-12-12 23:47:12 +01:00
|
|
|
return None
|
|
|
|
|
|
|
|
return f
|
2014-02-04 20:52:02 +01:00
|
|
|
|
2017-11-06 08:37:15 +01:00
|
|
|
def check_dict_only(required_keys: Iterable[Tuple[str, Validator]]) -> Validator:
|
2017-03-26 08:11:45 +02:00
|
|
|
return check_dict(required_keys, _allow_only_listed_keys=True)
|
|
|
|
|
2017-11-06 08:37:15 +01:00
|
|
|
def check_variable_type(allowed_type_funcs: Iterable[Validator]) -> Validator:
|
2014-02-14 19:55:35 +01:00
|
|
|
"""
|
|
|
|
Use this validator if an argument is of a variable type (e.g. processing
|
|
|
|
properties that might be strings or booleans).
|
|
|
|
|
|
|
|
`allowed_type_funcs`: the check_* validator functions for the possible data
|
|
|
|
types for this variable.
|
|
|
|
"""
|
2017-11-06 08:59:43 +01:00
|
|
|
def enumerated_type_check(var_name: str, val: object) -> Optional[str]:
|
2014-02-14 19:55:35 +01:00
|
|
|
for func in allowed_type_funcs:
|
|
|
|
if not func(var_name, val):
|
|
|
|
return None
|
2016-05-25 15:02:02 +02:00
|
|
|
return _('%s is not an allowed_type') % (var_name,)
|
2014-02-14 19:55:35 +01:00
|
|
|
return enumerated_type_check
|
|
|
|
|
2017-11-06 08:59:43 +01:00
|
|
|
def equals(expected_val: object) -> Validator:
|
|
|
|
def f(var_name: str, val: object) -> Optional[str]:
|
2014-02-04 20:52:02 +01:00
|
|
|
if val != expected_val:
|
2016-05-25 15:02:02 +02:00
|
|
|
return (_('%(variable)s != %(expected_value)s (%(value)s is wrong)') %
|
|
|
|
{'variable': var_name,
|
|
|
|
'expected_value': expected_val,
|
|
|
|
'value': val})
|
2014-02-04 20:52:02 +01:00
|
|
|
return None
|
|
|
|
return f
|
2017-04-10 08:06:10 +02:00
|
|
|
|
2017-11-06 08:37:15 +01:00
|
|
|
def validate_login_email(email: Text) -> None:
|
2017-04-10 08:06:10 +02:00
|
|
|
try:
|
|
|
|
validate_email(email)
|
|
|
|
except ValidationError as err:
|
|
|
|
raise JsonableError(str(err.message))
|
2017-06-11 09:11:59 +02:00
|
|
|
|
2017-11-06 08:37:15 +01:00
|
|
|
def check_url(var_name: str, val: Text) -> None:
|
2017-06-11 09:11:59 +02:00
|
|
|
validate = URLValidator()
|
|
|
|
try:
|
|
|
|
validate(val)
|
|
|
|
except ValidationError as err:
|
|
|
|
raise JsonableError(str(err.message))
|