Coverage for src / lexigram / admin / validation / rules.py: 0%
34 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-11 02:25 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-11 02:25 +0800
1"""Admin-specific validation rules.
3Extends the core ``Rule`` ABC with admin domain constraints for email
4addresses, passwords, and usernames.
5"""
7from __future__ import annotations
9import re
10from typing import Any
12from lexigram.contracts.exceptions.domain import FieldError
13from lexigram.result import Err, Ok, Result
14from lexigram.validation.rules import AbstractRule
16_ADMIN_EMAIL_RE = re.compile(r"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$")
18_USERNAME_RE = re.compile(r"^[a-zA-Z0-9_]{3,64}$")
21class IsValidAdminEmail(AbstractRule):
22 """Validate that a value is a properly formatted admin email address.
24 Requires an ``@`` separator, a local part, and a domain with at least
25 one dot and a two-or-more character TLD.
26 """
28 def __call__(self, value: Any, field_name: str) -> Result[Any, FieldError]:
29 if value is not None and not _ADMIN_EMAIL_RE.match(str(value)):
30 return Err(
31 FieldError(
32 field=field_name,
33 message=f"{field_name} is not a valid email address",
34 code="invalid_admin_email",
35 )
36 )
37 return Ok(value)
40class StrongPassword(AbstractRule):
41 """Validate that a password meets admin strength requirements.
43 Rules:
44 - Minimum 8 characters
45 - At least one uppercase letter
46 - At least one lowercase letter
47 - At least one digit
48 - At least one special character (non-alphanumeric)
49 """
51 def __call__(self, value: Any, field_name: str) -> Result[Any, FieldError]:
52 if value is None:
53 return Ok(value)
54 pwd = str(value)
55 if len(pwd) < 8:
56 return Err(
57 FieldError(
58 field=field_name,
59 message=f"{field_name} must be at least 8 characters",
60 code="password_too_short",
61 )
62 )
63 if not re.search(r"[A-Z]", pwd):
64 return Err(
65 FieldError(
66 field=field_name,
67 message=f"{field_name} must contain at least one uppercase letter",
68 code="password_no_uppercase",
69 )
70 )
71 if not re.search(r"[a-z]", pwd):
72 return Err(
73 FieldError(
74 field=field_name,
75 message=f"{field_name} must contain at least one lowercase letter",
76 code="password_no_lowercase",
77 )
78 )
79 if not re.search(r"\d", pwd):
80 return Err(
81 FieldError(
82 field=field_name,
83 message=f"{field_name} must contain at least one digit",
84 code="password_no_digit",
85 )
86 )
87 if not re.search(r"[^a-zA-Z0-9]", pwd):
88 return Err(
89 FieldError(
90 field=field_name,
91 message=f"{field_name} must contain at least one special character",
92 code="password_no_special",
93 )
94 )
95 return Ok(value)
98class IsValidUsername(AbstractRule):
99 """Validate that a value is an acceptable admin username.
101 Accepts only alphanumeric characters and underscores, with a length
102 between 3 and 64 characters (inclusive).
103 """
105 def __call__(self, value: Any, field_name: str) -> Result[Any, FieldError]:
106 if value is not None and not _USERNAME_RE.match(str(value)):
107 return Err(
108 FieldError(
109 field=field_name,
110 message=(
111 f"{field_name} must be 3-64 alphanumeric characters "
112 "or underscores"
113 ),
114 code="invalid_username",
115 )
116 )
117 return Ok(value)