File size: 1,974 Bytes
e197abb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import re
from typing import Any, Callable

EMAIL_RE = re.compile(r'^[\w.+-]+@[\w-]+\.[\w.-]+$')
PHONE_RE = re.compile(r'^\+?[\d\s\-().]{7,20}$')

Rule = Callable[[str, Any], str | None]


def required(field: str, value: Any) -> str | None:
    if value is None or (isinstance(value, str) and not value.strip()):
        return f"{field} is required"
    return None


def min_len(n: int) -> Rule:
    def check(field: str, value: Any) -> str | None:
        if value is not None and len(str(value)) < n:
            return f"{field} must be at least {n} characters"
        return None
    return check


def max_len(n: int) -> Rule:
    def check(field: str, value: Any) -> str | None:
        if value is not None and len(str(value)) > n:
            return f"{field} must be at most {n} characters"
        return None
    return check


def is_email(field: str, value: Any) -> str | None:
    if value and not EMAIL_RE.match(str(value)):
        return f"{field} is not a valid email"
    return None


def is_phone(field: str, value: Any) -> str | None:
    if value and not PHONE_RE.match(str(value)):
        return f"{field} is not a valid phone number"
    return None


def is_positive(field: str, value: Any) -> str | None:
    try:
        if float(value) <= 0:
            return f"{field} must be positive"
    except (TypeError, ValueError):
        return f"{field} must be a number"
    return None


def one_of(*choices: Any) -> Rule:
    def check(field: str, value: Any) -> str | None:
        if value not in choices:
            opts = ", ".join(map(str, choices))
            return f"{field} must be one of: {opts}"
        return None
    return check


def validate(data: dict[str, Any], rules: dict[str, list[Rule]]) -> list[str]:
    errors: list[str] = []
    for field, checks in rules.items():
        for rule in checks:
            err = rule(field, data.get(field))
            if err:
                errors.append(err)
    return errors