def _val_string(value: Any, min_len: Optional[int] = None, max_len: Optional[int] = None, ends_with: Optional[str] = None, starts_with: Optional[str] = None, strip_html: bool = False) -> Result: if value is None: return Success(None) if not isinstance(value, str): return Failure( ValErrorEntry(_ERR_STR_NOT_STRING, f'Value not a string')) if min_len and len(value) < min_len: return Failure( ValErrorEntry(_ERR_STR_TOO_SHORT, f'Value is too short. Min length {min_len}')) if max_len and len(value) > max_len: return Failure( ValErrorEntry(_ERR_STR_TOO_LONG, f'Value is too long. Max length {max_len}')) if ends_with and not value.endswith(ends_with): return Failure( ValErrorEntry(_ERR_STR_NOT_ENDS_WITH, f'Incorrect value. Value not ends with {ends_with}')) if starts_with and not value.startswith(starts_with): return Failure( ValErrorEntry( _ERR_STR_NOT_STARTS_WITH, f'Incorrect value. Value not starts with {starts_with}')) if strip_html: value = remove_tags(value) return Success(value)
def _val_list_of_items(value: Any, parser: Callable[[str], Optional[Union[float, int]]], min_length: int = None, max_length: int = None) -> Result: if value is None: return Success(None) if not isinstance(value, (list, tuple)): return Failure(ValErrorEntry(_ERR_EXPECTED_LIST, 'Expected list')) elif min_length and min_length > len(value): return Failure( ValErrorEntry( _ERR_LIST_TOO_SHORT, f'List too short. Required at least {min_length} elements')) else: out = [] for i, e in enumerate(value): e = parser(e, None) if e is None: return Failure( ValErrorEntry(_ERR_LIST_VALUE_ERROR, f'Invalid value at position {i}')) else: out.append(e) if max_length and len(out) > max_length: return Success( ValErrorEntry( _ERR_LIST_TOO_BIG, f'List too long. Expected maximum {max_length} elements')) return Success(out)
def val_gif(contents: bytes) -> Result: if not contents.startswith(_GIF_HEADER): return Failure(ValErrorEntry(_ERR_IMG_GIF, 'Invalid GIF file')) if not contents.endswith(_GIF_TRAILER): return Failure(ValErrorEntry(_ERR_IMG_GIF, 'Invalid GIF file')) if contents[4] not in (0x37, 0x39) or contents[5] != 0x61: return Failure(ValErrorEntry(_ERR_IMG_GIF, 'Invalid GIF file'))
def val_jpg(contents: bytes) -> Result: if not contents.startswith(_JPEG_HEADER): return Failure(ValErrorEntry(_ERR_IMG_JPEG, 'Invalid JPEG file')) if not contents.endswith(_JPEG_TRAILER): return Failure(ValErrorEntry(_ERR_IMG_JPEG, 'Invalid JPEG file')) if 0xE0 <= contents[3] <= 0xE8: return Success(None) else: return Failure(ValErrorEntry(_ERR_IMG_JPEG, 'Invalid JPEG file'))
def _val_email(value: Any, domain: Optional[str] = None) -> Result: if value is None: return Success(None) result = _EMAIL_RE.match(value) value = result.string if result else None if value: if domain and not value.endswith(domain): return Failure(ValErrorEntry(_ERR_EMAIL_DOMAIN, 'Not valid domain')) return Success(value) else: return Failure( ValErrorEntry(_ERR_EMAIL_NOT_VALID, 'Not valid email address'))
def _validate_field(cls, value: Any, validators: Union[List, Tuple]) -> Result: check_status = True errors = [] for i, v in enumerate(validators): if isinstance(v, _FieldValidator): result = v.call(value) elif callable(v): result = v(value) else: raise ValueError( f'Validator at position {i} of "{v}" is unknown') if isinstance(result, Result): if not result.ok: check_status = False if isinstance(result.result, ValErrorEntry): errors.append(_val_err_dict(result.result)) else: errors.append(result.result) else: value = result.result else: raise ValueError( f'Validator at position {i} of "{v}" has returned unexpected result' ) if check_status: return Success(value) else: return Failure(errors)
def _val_fn(value: Any, fn: Callable[[str], Optional[Any]]) -> Result: if value is None: return Success(None) v = fn(value) if v is None: return Failure(ValErrorEntry(_ERR_INVALID_VALUE, 'Incorrect value')) else: return Success(v)
def _val_phone(value: Any, country: Optional[str] = None) -> Result: if value is None: return Success(None) if parse_phone_number(value, country == 'POL') is None: return Failure(ValErrorEntry(_ERR_PHONE_FORMAT, 'Invalid phone format')) else: return Success(value)
def _val_value_in(value: Any, available: Optional[Union[List, Tuple]] = None) -> Result: if value is None or available is None: return Success(None) if value in available: return Success(value) else: s = ', '.join(available) return Failure( ValErrorEntry(_ERR_VALUE_IN, f'Incorrect value. Expected {s}'))
def _val_numeric(value: Any, parser: Callable[[str, Optional[Union[float, int]]], Optional[Union[float, int]]], min_val: Optional[Union[int, float]] = None, max_val: Optional[Union[int, float]] = None) -> Result: if value is None: return Success(None) v = parser(value, None) if v is None: return Failure( ValErrorEntry(_ERR_NUMBER_FORMAT, "Invalid number format")) else: if min_val is not None and v < min_val: return Failure( ValErrorEntry(_ERR_NUMBER_TOO_SMALL, f'Cannot be smaller than {min_val}')) if max_val is not None and v > max_val: return Failure( ValErrorEntry(_ERR_NUMBER_TOO_BIG, f'Cannot be bigger than {max_val}')) return Success(v)
def _val_address(value: Any, required: int = ADDR_REQ_CITY & ADDR_REQ_COUNTRY) -> Result: if value is None or required == 0: return Success(None) if not isinstance(value, dict): return Failure(ValErrorEntry(_ERR_ADDR_FORMAT, 'Invalid format')) err = {} if required & ADDR_REQ_CITY and value.get('city') is None: err['city'] = { 'code': _ERR_ADDR_MISSING_CITY, 'message': 'Missing required value' } if required & ADDR_REQ_COUNTRY and value.get('country') is None: err['country'] = { 'code': _ERR_ADDR_MISSING_COUNTRY, 'message': 'Missing required value' } if value.get('country') and value.get( 'country') not in countries_by_alpha3: err['country'] = { 'code': _ERR_ADDR_COUNTRY, 'message': 'Incorrect value' } if required & ADDR_REQ_STREET and value.get('street') is None: err['street'] = { 'code': _ERR_ADDR_MISSING_STREET, 'message': 'Missing required value' } if required & ADDR_REQ_DISTRICT and value.get('district') is None: err['district'] = { 'code': _ERR_ADDR_MISSING_DISTRICT, 'message': 'Missing required value' } if any(err): return Failure(err) else: return Success(value)
def validate(cls, contents: Union[bytes, str], types: int = PNG + JPEG + GIF) -> Result: if not contents: return Success(None) if isinstance(contents, str): contents = bytes(contents, 'ascii') header = bytes(memoryview(contents)[0:36]) if len(header) != 36: return Failure( ValErrorEntry(_ERR_IMG_CONTENT_TOO_SHORT, 'Not valid data contents. Content too short')) if not header.startswith(b'data:image/'): return Failure( ValErrorEntry(_ERR_IMG_MISSING_HEADER, 'Not valid data contents. Expected image data')) img = header.split(b';', 1) if len(img) != 2: return Failure( ValErrorEntry(_ERR_IMG_MISSING_HEADER, 'Not valid data contents. Expected image data')) image_type = img[0][11:] val = cls._IMG_VAL.get(image_type) it = cls._IMG_TYPES.get(image_type) if not it or it & types == 0 or not val: formats = [] if types & cls.PNG: formats.append('PNG') if types & cls.JPEG: formats.append('JPEG') if types & cls.GIF: formats.append('GIF') formats = ', '.join(formats) return Failure( ValErrorEntry(_ERR_IMG_TYPE, f'Unknown image type. Expected {formats}')) if not img[1].startswith(b'base64,'): return Failure( ValErrorEntry(_ERR_IMG_CONTENT, 'Unknown image type. Expected base64 contents')) content_start = len(img[0]) + 8 try: image_bytes = base64_decode(contents[content_start:])[0] except (TypeError, binascii.Error) as ex: return Failure( ValErrorEntry(_ERR_IMG_CONTENT, f'Invalid image contents. {ex}')) result = val(image_bytes) if result.ok: return Success( cls.Image(img_type=image_type.decode('ascii'), img_contents=image_bytes)) else: return result
def _val_date(value: Any, remove_offset: bool = True, before_date: Optional[datetime] = None, after_date: Optional[datetime] = None) -> Result: if value is None: return Success(None) v = parse_date(value) if v is None: return Failure(ValErrorEntry(_ERR_DATE_FORMAT, "Not valid date format")) else: if remove_offset: v = v.replace(tzinfo=None) if before_date and before_date < v: return Failure( ValErrorEntry(_ERR_DATE_BEFORE, f'Date has to be before {before_date}')) if after_date and after_date > v: return Failure( ValErrorEntry(_ERR_DATE_AFTER, f'Date has to be after {after_date}')) return Success(v)
def _is_required(value: Any) -> Result: if value in (None, ''): return Failure(ValErrorEntry(_ERR_REQUIRED, 'Missing required value')) else: return Success(value)
def _just_fail(value: Any) -> Result: return Failure(ValErrorEntry(0, "Fail"))
def val_png(contents: bytes) -> Result: if not contents.startswith(_PNG_HEADER): return Failure(ValErrorEntry(_ERR_IMG_PNG, 'Invalid PNG file')) if not contents.endswith(_PNG_TRAILER): return Failure(ValErrorEntry(_ERR_IMG_PNG, 'Invalid PNG file')) return Success(None)