| # Copyright 2018 The LUCI Authors. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Generic value validators.""" |
| |
| load('@stdlib//internal/lucicfg.star', 'lucicfg') |
| load('@stdlib//internal/re.star', 're') |
| load('@stdlib//internal/time.star', 'time') |
| |
| |
| def _string(attr, val, *, regexp=None, allow_empty=False, default=None, required=True): |
| """Validates that the value is a string and returns it. |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| regexp: a regular expression to check 'val' against. |
| allow_empty: if True, accept empty string as valid. |
| default: a value to use if 'val' is None, ignored if required is True. |
| required: if False, allow 'val' to be None, return 'default' in this case. |
| |
| Returns: |
| The validated string or None if required is False and default is None. |
| """ |
| if val == None: |
| if required: |
| fail('missing required field %r' % attr) |
| if default == None: |
| return None |
| val = default |
| |
| if type(val) != 'string': |
| fail('bad %r: got %s, want string' % (attr, type(val))) |
| if not allow_empty and not val: |
| fail('bad %r: must not be empty' % (attr,)) |
| if regexp and not re.submatches(regexp, val): |
| fail('bad %r: %r should match %r' % (attr, val, regexp)) |
| |
| return val |
| |
| |
| def _int(attr, val, *, min=None, max=None, default=None, required=True): |
| """Validates that the value is an integer and returns it. |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| min: minimal allowed value (inclusive) or None for unbounded. |
| max: maximal allowed value (inclusive) or None for unbounded. |
| default: a value to use if 'val' is None, ignored if required is True. |
| required: if False, allow 'val' to be None, return 'default' in this case. |
| |
| Returns: |
| The validated int or None if required is False and default is None. |
| """ |
| if val == None: |
| if required: |
| fail('missing required field %r' % attr) |
| if default == None: |
| return None |
| val = default |
| |
| if type(val) != 'int': |
| fail('bad %r: got %s, want int' % (attr, type(val))) |
| |
| if min != None and val < min: |
| fail('bad %r: %s should be >= %s' % (attr, val, min)) |
| if max != None and val > max: |
| fail('bad %r: %s should be <= %s' % (attr, val, max)) |
| |
| return val |
| |
| |
| def _float(attr, val, *, min=None, max=None, default=None, required=True): |
| """Validates that the value is a float or integer and returns it as float. |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| min: minimal allowed value (inclusive) or None for unbounded. |
| max: maximal allowed value (inclusive) or None for unbounded. |
| default: a value to use if 'val' is None, ignored if required is True. |
| required: if False, allow 'val' to be None, return 'default' in this case. |
| |
| Returns: |
| The validated float or None if required is False and default is None. |
| """ |
| if val == None: |
| if required: |
| fail('missing required field %r' % attr) |
| if default == None: |
| return None |
| val = default |
| |
| if type(val) == 'int': |
| val = float(val) |
| elif type(val) != 'float': |
| fail('bad %r: got %s, want float or int' % (attr, type(val))) |
| |
| if min != None and val < min: |
| fail('bad %r: %s should be >= %s' % (attr, val, min)) |
| if max != None and val > max: |
| fail('bad %r: %s should be <= %s' % (attr, val, max)) |
| |
| return val |
| |
| |
| def _bool(attr, val, *, default=None, required=True): |
| """Validates that the value can be converted to a boolean. |
| |
| Zero values other than None (0, "", [], etc) are treated as False. None |
| indicates "use default". If required is False and val is None, returns None |
| (indicating no value was passed). |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| default: a value to use if 'val' is None, ignored if required is True. |
| required: if False, allow 'val' to be None, return 'default' in this case. |
| |
| Returns: |
| The boolean or None if required is False and default is None. |
| """ |
| if val == None: |
| if required: |
| fail('missing required field %r' % attr) |
| if default == None: |
| return None |
| val = default |
| return bool(val) |
| |
| |
| def _duration(attr, val, *, precision=time.second, min=time.zero, max=None, default=None, required=True): |
| """Validates that the value is a duration specified at the given precision. |
| |
| For example, if 'precision' is time.second, will validate that the given |
| duration has a whole number of seconds. Fails if truncating the duration to |
| the requested precision loses information. |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| precision: a time unit to divide 'val' by to get the output. |
| min: minimal allowed duration (inclusive) or None for unbounded. |
| max: maximal allowed duration (inclusive) or None for unbounded. |
| default: a value to use if 'val' is None, ignored if required is True. |
| required: if False, allow 'val' to be None, return 'default' in this case. |
| |
| Returns: |
| The validated duration or None if required is False and default is None. |
| """ |
| if val == None: |
| if required: |
| fail('missing required field %r' % attr) |
| if default == None: |
| return None |
| val = default |
| |
| if type(val) != 'duration': |
| fail('bad %r: got %s, want duration' % (attr, type(val))) |
| |
| if min != None and val < min: |
| fail('bad %r: %s should be >= %s' % (attr, val, min)) |
| if max != None and val > max: |
| fail('bad %r: %s should be <= %s' % (attr, val, max)) |
| |
| if time.truncate(val, precision) != val: |
| fail(( |
| 'bad %r: losing precision when truncating %s to %s units, ' + |
| 'use time.truncate(...) to acknowledge') % (attr, val, precision)) |
| |
| return val |
| |
| |
| def _list(attr, val, *, required=False): |
| """Validates that the value is a list and returns it. |
| |
| None is treated as an empty list. |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| required: if False, allow 'val' to be None or empty, return empty list in |
| this case. |
| |
| Returns: |
| The validated list. |
| """ |
| if val == None: |
| val = [] |
| |
| if type(val) != 'list': |
| fail('bad %r: got %s, want list' % (attr, type(val))) |
| |
| if required and not val: |
| fail('missing required field %r' % attr) |
| |
| return val |
| |
| |
| def _str_dict(attr, val, *, required=False): |
| """Validates that the value is a dict with non-empty string keys. |
| |
| None is treated as an empty dict. |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| required: if False, allow 'val' to be None or empty, return empty dict in |
| this case. |
| |
| Returns: |
| The validated dict. |
| """ |
| if val == None: |
| val = {} |
| |
| if type(val) != 'dict': |
| fail('bad %r: got %s, want dict' % (attr, type(val))) |
| |
| if required and not val: |
| fail('missing required field %r' % attr) |
| |
| for k in val: |
| if type(k) != 'string': |
| fail('bad %r: got %s key, want string' % (attr, type(k))) |
| if not k: |
| fail('bad %r: got empty key' % attr) |
| |
| return val |
| |
| |
| def _struct(attr, val, sym, *, default=None, required=True): |
| """Validates that the value is a struct of the given flavor and returns it. |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| sym: a name of the constructor that produced the struct. |
| default: a value to use if 'val' is None, ignored if required is True. |
| required: if False, allow 'val' to be None, return 'default' in this case. |
| |
| Returns: |
| The validated struct or None if required is False and default is None. |
| """ |
| if val == None: |
| if required: |
| fail('missing required field %r' % attr) |
| if default == None: |
| return None |
| val = default |
| |
| tp = __native__.ctor(val) or type(val) # ctor(...) return None for non-structs |
| if tp != sym: |
| fail('bad %r: got %s, want %s' % (attr, tp, sym)) |
| |
| return val |
| |
| |
| def _type(attr, val, prototype, *, default=None, required=True): |
| """Validates that the value is either None or has the same type as `prototype` |
| value. |
| |
| Useful when checking types of protobuf messages. |
| |
| Args: |
| attr: field name with this value, for error messages. |
| val: a value to validate. |
| prototype: a prototype value to compare val's type against. |
| default: a value to use if `val` is None, ignored if required is True. |
| required: if False, allow `val` to be None, return `default` in this case. |
| |
| Returns: |
| `val` on success or None if required is False and default is None. |
| """ |
| if val == None: |
| if required: |
| fail('missing required field %r' % attr) |
| if default == None: |
| return None |
| val = default |
| |
| if type(val) != type(prototype): |
| fail('bad %r: got %s, want %s' % (attr, type(val), type(prototype))) |
| |
| return val |
| |
| |
| def _repo_url(attr, val, *, required=True): |
| """Validates that the value is `https://...` repository URL and returns it. |
| |
| Additionally verifies that `val` doesn't end with `.git`. |
| |
| Args: |
| attr: name of the var for error messages. Required. |
| val: a value to validate. Required. |
| required: if False, allow `val` to be None, return None in this case. |
| |
| Returns: |
| Validate `val` or None if it is None and `required` is False. |
| """ |
| val = validate.string(attr, val, regexp=r'https://.+', required=required) |
| if val and val.endswith('.git'): |
| fail('bad %r: %r should not end with ".git"' % (attr, val)) |
| return val |
| |
| |
| def _relative_path(attr, val, *, allow_dots=False, base=None, required=True, default=None): |
| """Validates that the value is a string with relative path. |
| |
| Optionally adds it to some base path and returns the cleaned resulting path. |
| |
| Args: |
| attr: name of the var for error messages. Required. |
| var: a value to validate. Required. |
| allow_dots: if True, allow `../` as a prefix in the resulting path. Default |
| is False. |
| base: if given, apply the relative path to this base path and returns the |
| result. |
| default: a value to use if 'val' is None, ignored if required is True. |
| required: if False, allow 'val' to be None, return `default` in this case. |
| |
| Returns: |
| Validated, cleaned and (if `base` is given) rebased path. |
| """ |
| val = validate.string(attr, val, required=required, default=default) |
| if val == None: |
| return None |
| base = validate.string('base', base, required=False) |
| clean, err = __native__.clean_relative_path(base or '', val, allow_dots) |
| if err: |
| fail('bad %r: %s' % (attr, err)) |
| return clean |
| |
| |
| def _var_with_validator(attr, validator, **kwargs): |
| """Returns a lucicfg.var that validates the value via a validator callback. |
| |
| Args: |
| attr: name of the var for error messages. Required. |
| validator: a callback(attr, value, **kwargs), e.g. `validate.string`. |
| Required. |
| |
| Returns: |
| lucicfg.var(...). |
| """ |
| return lucicfg.var(validator=lambda value: validator(attr, value, **kwargs)) |
| |
| |
| def _vars_with_validators(vars): |
| """Accepts dict `{attr -> validator}`, returns dict `{attr -> lucicfg.var}`. |
| |
| Basically applies validate.var_with_validator(...) to each item of the dict. |
| |
| Args: |
| vars: a dict with string keys and callable values, matching the signature of |
| `validator` in validate.var_with_validator(...). Required. |
| |
| Returns: |
| Dict with string keys and lucicfg.var(...) values. |
| """ |
| return {attr: _var_with_validator(attr, validator) for attr, validator in vars.items()} |
| |
| |
| validate = struct( |
| string = _string, |
| int = _int, |
| float = _float, |
| bool = _bool, |
| duration = _duration, |
| list = _list, |
| str_dict = _str_dict, |
| struct = _struct, |
| type = _type, |
| repo_url = _repo_url, |
| relative_path = _relative_path, |
| |
| var_with_validator = _var_with_validator, |
| vars_with_validators = _vars_with_validators, |
| ) |