"""Small, dependency-free contracts shared by the ARR web adapters.""" from __future__ import annotations import json import re from dataclasses import dataclass from typing import Any, Dict, Mapping, Optional API_VERSION = "1.0" MAX_UPLOAD_BYTES = 25 * 1024 * 1024 MONTH_RE = re.compile(r"^(\d{4})-(\d{2})$") SAFE_FILENAME_RE = re.compile(r"^[^/\\\x00-\x1f\x7f]{1,255}$") JOB_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$") class PortalError(RuntimeError): """A public-safe portal failure with an HTTP status.""" def __init__(self, code: str, safe_message: str, status: int = 400): super().__init__(safe_message) self.code = code self.safe_message = safe_message self.status = status def validate_month(value: str) -> str: match = MONTH_RE.fullmatch(value) if not match or not 1 <= int(match.group(2)) <= 12: raise PortalError("MONTH_INVALID", "month must use YYYY-MM") return value def validate_upload_filename(value: object) -> str: if ( not isinstance(value, str) or SAFE_FILENAME_RE.fullmatch(value) is None or not value.lower().endswith(".xml") or value in {".", ".."} ): raise PortalError("UPLOAD_FILENAME_INVALID", "请选择 XML 文件") return value def validate_job_id(value: object) -> str: if not isinstance(value, str) or JOB_ID_RE.fullmatch(value) is None: raise PortalError("JOB_ID_INVALID", "任务编号无效") return value def validate_xml_payload(value: bytes) -> None: if not value: raise PortalError("UPLOAD_EMPTY", "XML 文件为空") if len(value) > MAX_UPLOAD_BYTES: raise PortalError("UPLOAD_TOO_LARGE", "XML 文件超过 25 MB") sample = value[:4096] if sample.startswith(b"\xef\xbb\xbf"): sample = sample[3:] if not sample.lstrip().startswith(b"<"): raise PortalError("UPLOAD_CONTENT_INVALID", "文件内容不是 XML") @dataclass(frozen=True) class Response: status: int body: bytes content_type: str headers: Mapping[str, str] @classmethod def json( cls, status: int, payload: Mapping[str, Any], headers: Optional[Mapping[str, str]] = None, ) -> "Response": body = ( json.dumps( payload, ensure_ascii=False, allow_nan=False, separators=(",", ":"), ) + "\n" ).encode("utf-8") response_headers = {"Cache-Control": "no-store"} if headers: response_headers.update(headers) return cls(status, body, "application/json; charset=utf-8", response_headers) def success(data: Any, **extra: Any) -> Dict[str, Any]: payload: Dict[str, Any] = {"ok": True, "api_version": API_VERSION, "data": data} payload.update(extra) return payload def failure(error: PortalError) -> Dict[str, Any]: return { "ok": False, "api_version": API_VERSION, "error": {"code": error.code, "message": error.safe_message}, }