"""Template validators — Phase 11.4.""" from __future__ import annotations from typing import Any from app.models.types import ( PageType, TemplateStatus, TemplateVersionStatus, ) from app.validators.foundation import ValidationError, validate_non_empty from shared.exceptions import AppError TEMPLATE_STATUS_TRANSITIONS: dict[TemplateStatus, frozenset[TemplateStatus]] = { TemplateStatus.DRAFT: frozenset({TemplateStatus.ACTIVE, TemplateStatus.ARCHIVED}), TemplateStatus.ACTIVE: frozenset( {TemplateStatus.DEPRECATED, TemplateStatus.ARCHIVED} ), TemplateStatus.DEPRECATED: frozenset( {TemplateStatus.ACTIVE, TemplateStatus.ARCHIVED} ), TemplateStatus.ARCHIVED: frozenset(), } TEMPLATE_VERSION_STATUS_TRANSITIONS: dict[ TemplateVersionStatus, frozenset[TemplateVersionStatus] ] = { TemplateVersionStatus.DRAFT: frozenset({TemplateVersionStatus.PUBLISHED}), TemplateVersionStatus.PUBLISHED: frozenset({TemplateVersionStatus.RETIRED}), TemplateVersionStatus.RETIRED: frozenset(), } _ALLOWED_PAGE_TYPES = {p.value for p in PageType} def validate_page_type(page_type: str) -> str: page_type = validate_non_empty(page_type, "page_type").strip().lower() if page_type not in _ALLOWED_PAGE_TYPES: raise ValidationError( "page_type نامعتبر است", {"field": "page_type", "allowed": sorted(_ALLOWED_PAGE_TYPES)}, ) return page_type def ensure_template_status_transition( current: TemplateStatus, target: TemplateStatus ) -> None: if current == target: return allowed = TEMPLATE_STATUS_TRANSITIONS.get(current, frozenset()) if target not in allowed: raise AppError( "گذار وضعیت قالب مجاز نیست", error_code="invalid_template_status_transition", status_code=409, details={"current": current.value, "target": target.value}, ) def ensure_template_version_status_transition( current: TemplateVersionStatus, target: TemplateVersionStatus ) -> None: if current == target: return allowed = TEMPLATE_VERSION_STATUS_TRANSITIONS.get(current, frozenset()) if target not in allowed: raise AppError( "گذار وضعیت نسخه قالب مجاز نیست", error_code="invalid_template_version_status_transition", status_code=409, details={"current": current.value, "target": target.value}, ) def ensure_template_version_mutable(version: Any) -> None: if getattr(version, "is_immutable", False) or getattr( version, "status", None ) == TemplateVersionStatus.PUBLISHED: raise AppError( "نسخه منتشرشده قالب قابل ویرایش نیست", error_code="template_version_immutable", status_code=409, ) if getattr(version, "status", None) == TemplateVersionStatus.RETIRED: raise AppError( "نسخه بازنشسته‌شده قالب قابل ویرایش نیست", error_code="template_version_retired", status_code=409, ) def ensure_blueprint_json(payload: dict | None) -> dict | None: if payload is None: return None if not isinstance(payload, dict): raise ValidationError( "blueprint_json باید شیء JSON باشد", {"field": "blueprint_json"} ) if "page_type" in payload and payload["page_type"] is not None: payload = {**payload, "page_type": validate_page_type(str(payload["page_type"]))} placements = payload.get("placement_shells") if placements is not None and not isinstance(placements, list): raise ValidationError( "placement_shells باید آرایه باشد", {"field": "blueprint_json.placement_shells"}, ) return payload