"""Content-pack parsing, normalization, and JSON Schema publication.""" from __future__ import annotations import hashlib import json from dataclasses import dataclass from typing import Any from pydantic import ValidationError from .domain.models import ContentPack MAX_PACK_BYTES = 512 * 1024 @dataclass(frozen=True, slots=True) class ParsedPack: """Validated content plus its canonical representation.""" pack: ContentPack canonical_json: str digest: str class ContentPackError(ValueError): """A safe, user-visible content validation error.""" def parse_content_pack(raw: bytes | str) -> ParsedPack: """Validate a content pack and return stable canonical JSON.""" data = raw.encode() if isinstance(raw, str) else raw if len(data) > MAX_PACK_BYTES: error = "Content pack exceeds the 512 KiB limit" raise ContentPackError(error) try: decoded: Any = json.loads(data) except (UnicodeDecodeError, json.JSONDecodeError) as exc: error = f"Invalid JSON: {exc}" raise ContentPackError(error) from exc try: pack = ContentPack.model_validate(decoded) except ValidationError as exc: messages = [] for issue in exc.errors(include_url=False): path = ".".join(str(part) for part in issue["loc"]) messages.append(f"{path or '$'}: {issue['msg']}") raise ContentPackError("\n".join(messages)) from exc canonical = json.dumps(pack.model_dump(mode="json", by_alias=False), sort_keys=True, separators=(",", ":")) return ParsedPack(pack=pack, canonical_json=canonical, digest=hashlib.sha256(canonical.encode()).hexdigest()) def content_pack_schema() -> dict[str, Any]: """Return the authoritative version-one JSON Schema.""" return ContentPack.model_json_schema(by_alias=False)