feat(gems): add multiplayer gem game with custom content packs
treefmt / nix fmt (pull_request) Successful in 6s
test ebook search / test-ebook-search (pull_request) Successful in 39s
pytest / pytest (pull_request) Successful in 40s
build_systems / build-brain (pull_request) Successful in 1m0s
build_systems / build-bob (pull_request) Successful in 1m2s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m33s
build_systems / build-jeeves (pull_request) Successful in 2m25s
test ebook search / test-ebook-search (push) Successful in 35s
build_systems / build-brain (push) Successful in 37s
build_systems / build-bob (push) Successful in 37s
build_systems / build-jeeves (push) Successful in 2m17s
treefmt / nix fmt (push) Successful in 5s
pytest / pytest (push) Successful in 29s
build_systems / build-rhapsody-in-green (push) Successful in 49s
treefmt / nix fmt (pull_request) Successful in 6s
test ebook search / test-ebook-search (pull_request) Successful in 39s
pytest / pytest (pull_request) Successful in 40s
build_systems / build-brain (pull_request) Successful in 1m0s
build_systems / build-bob (pull_request) Successful in 1m2s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m33s
build_systems / build-jeeves (pull_request) Successful in 2m25s
test ebook search / test-ebook-search (push) Successful in 35s
build_systems / build-brain (push) Successful in 37s
build_systems / build-bob (push) Successful in 37s
build_systems / build-jeeves (push) Successful in 2m17s
treefmt / nix fmt (push) Successful in 5s
pytest / pytest (push) Successful in 29s
build_systems / build-rhapsody-in-green (push) Successful in 49s
- implement FastAPI and HTMX lobby and game interfaces - support up to four human and AI-controlled players - add configurable rules, victory conditions, and expansion modules - support validated JSON cards, patrons, objectives, and outposts
This commit was merged in pull request #43.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user