TorbatYar/backend/services/experience/app/validators/foundation.py
Mortezakoohjani 203671a7bf feat(experience): ship Experience Platform phases 11.0-11.10
Add the experience service with sites through analytics/AI hooks, migrations through 0011, and phase docs/manifests marking the track complete.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 11:43:10 +03:30

56 lines
1.7 KiB
Python

"""Reusable validators for Experience foundation."""
from __future__ import annotations
import re
from typing import Any
from shared.exceptions import AppError
CODE_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,49}$")
class ValidationError(AppError):
def __init__(self, message: str, details: dict[str, Any] | None = None):
super().__init__(
message=message,
error_code="validation_error",
status_code=422,
details=details or {},
)
def validate_non_empty(value: str | None, field: str) -> str:
if value is None or not str(value).strip():
raise ValidationError(f"{field} الزامی است", {"field": field})
return str(value).strip()
def validate_code(code: str) -> str:
code = validate_non_empty(code, "code")
if not CODE_RE.match(code):
raise ValidationError(
"کد نامعتبر است",
{"field": "code", "pattern": CODE_RE.pattern},
)
return code
def validate_currency_code(code: str) -> str:
code = validate_non_empty(code, "currency_code").upper()
if len(code) != 3:
raise ValidationError("currency_code باید ۳ حرفی باشد", {"field": "currency_code"})
return code
def ensure_optimistic_version(entity: Any, expected: int) -> None:
current = getattr(entity, "version", None)
if current is None:
return
if current != expected:
raise AppError(
"نسخه موجودیت هم‌خوان نیست",
error_code="version_conflict",
status_code=409,
details={"expected": expected, "actual": current},
)