"""What every operation shares: the refusal it raises and the lookups into `state`.""" from __future__ import annotations from typing import Any class OpError(Exception): """ An operation refused, with enough for any adapter to report it. `status` is an HTTP code because one adapter needs one; the others ignore it. `extra` carries the "here is what would have worked" payload — a bare "no such group" leaves an operator guessing at a UUID. """ def __init__(self, message: str, *, status: int = 400, extra: dict[str, Any] | None = None): super().__init__(message) self.message = message self.status = status self.extra = extra or {} def as_dict(self) -> dict: return {"error": self.message, **self.extra} # ── Shared lookups ─────────────────────────────────────────────────────────── def _roster(state: dict): roster = state.get("roster") if not roster: raise OpError("Roster not available", status=503) return roster def _hub(state: dict): hub = state.get("hub") if not hub or not hub._session: raise OpError("Hub not connected", status=503) return hub def _group_ctx(state: dict, group_id: str) -> dict: groups_ctx = state.get("groups_ctx", {}) if group_id not in groups_ctx: raise OpError("Group not hosted on this node", status=404, extra={"available": [ {"id": gid} for gid in groups_ctx]}) return groups_ctx[group_id] def _config(state: dict): config = state.get("config") if not config: raise OpError("No config loaded", status=503) return config