fix: WsDevTest non-standard error format and align doc examples

- Fix two error returns in ws_dev_test.py using non-standard {"_error": True, ...} format, which _collect_results misidentifies as a success response and wraps as ok=True, sending a false-success to the client
- Switch to WsResult.error(code=..., message=...) standard API, consistent with all other handlers
- Fix 5 occurrences of process_event → process in documentation examples
- Remove non-existent HANDLER_ID and HANDLED_EVENTS class attribute examples from docs
- Fix validate_event_types (plural) → validate_event_type (singular) in docs
This commit is contained in:
keyboardstaff 2026-03-27 23:34:52 -07:00
parent 4065385630
commit 0749ddc932
2 changed files with 23 additions and 19 deletions

View file

@ -2,6 +2,7 @@ import asyncio
from typing import Any
from helpers.ws import WsHandler
from helpers.ws_manager import WsResult
from helpers.print_style import PrintStyle
from helpers import runtime
@ -9,15 +10,19 @@ from helpers import runtime
class WsDevTest(WsHandler):
"""Developer-only WebSocket test harness handler."""
async def process(self, event: str, data: dict, sid: str) -> dict[str, Any] | None:
async def process(self, event: str, data: dict, sid: str) -> dict[str, Any] | WsResult | None:
if event == "ws_event_console_subscribe":
if not runtime.is_development():
return {"_error": True, "code": "NOT_AVAILABLE",
"message": "Event console is available only in development mode"}
return WsResult.error(
code="NOT_AVAILABLE",
message="Event console is available only in development mode",
)
registered = self.manager.register_diagnostic_watcher(self.namespace, sid)
if not registered:
return {"_error": True, "code": "SUBSCRIBE_FAILED",
"message": "Unable to subscribe to diagnostics"}
return WsResult.error(
code="SUBSCRIBE_FAILED",
message="Unable to subscribe to diagnostics",
)
return {"status": "subscribed", "timestamp": data.get("requestedAt")}
if event == "ws_event_console_unsubscribe":

View file

@ -148,15 +148,13 @@ Create new handler files as `api/ws_<name>.py` and inherit from `WsHandler`.
from helpers.ws import WsHandler
class WsMyFeature(WsHandler):
HANDLER_ID = "my_feature"
HANDLED_EVENTS = ["my_event_a", "my_event_b"]
async def process_event(self, event_type: str, data: dict[str, Any], sid: str) -> dict | None:
if event_type == "dashboard_refresh":
async def process(self, event: str, data: dict, sid: str) -> dict | None:
if event == "dashboard_refresh":
stats = await self._load_stats(data.get("scope", "all"))
return {"ok": True, "stats": stats}
if event_type == "dashboard_push":
if event == "dashboard_push":
await self.broadcast(
"dashboard_update",
{"stats": data.get("stats", {}), "source": sid},
@ -165,16 +163,16 @@ class WsMyFeature(WsHandler):
return None
```
Handlers are auto-loaded on startup; duplicate event declarations produce warnings but are supported. Use `validate_event_types` to ensure names follow lowercase snake_case and avoid Socket.IO reserved events.
Handlers are auto-loaded on startup. The `handlerId` is derived automatically from the fully-qualified class name (e.g., `api.ws_my_feature.WsMyFeature`). All registered handlers receive every event; use conditional logic inside `process()` to filter by event type.
### 2. Consuming Client Events (Server as Consumer)
- Implement `process_event` and return either `None` (fire-and-forget) or a dict that becomes the handlers contribution in `results[]`.
- Implement `process` and return either `None` (fire-and-forget) or a dict that becomes the handler's contribution in `results[]`.
- Use dependency injection (async functions, database calls, etc.) but keep event loop friendly—no blocking calls.
- Validate input vigorously and return structured errors as needed.
```python
async def process_event(self, event_type: str, data: dict, sid: str) -> dict | None:
async def process(self, event: str, data: dict, sid: str) -> dict | None:
if "query" not in data:
return {"ok": False, "error": {"code": "VALIDATION", "error": "Missing query"}}
@ -271,7 +269,7 @@ console.log(window.runtimeInfo.id, window.runtimeInfo.isDevelopment);
### Namespaces (end-state)
- The root namespace (`/`) is reserved and intentionally unhandled by default for application events. Feature code should connect to an explicit namespace (for example `/webui`).
- The root namespace (`/`) is reserved and intentionally unhandled by default for application events. Feature code should connect to the `/ws` namespace (defined as `NAMESPACE` in `helpers/ws.py`).
- The frontend exposes `createNamespacedClient(namespace)` and `getNamespacedClient(namespace)` (one client instance per namespace per tab). Namespaced clients expose the same minimal API: `emit`, `request`, `on`, `off`.
- Unknown namespaces are rejected deterministically during the Socket.IO connect handshake with a `connect_error` payload:
- `err.message === "UNKNOWN_NAMESPACE"`
@ -509,8 +507,8 @@ websocket.on("confirm_close_tab", async ({ data, correlationId }) => {
Sometimes you want to acknowledge work immediately but stream additional updates later. Combine `request()` for the initial confirmation and `emit_to()` for follow-up events using the same correlation ID.
```python
async def process_event(self, event_type: str, data: dict, sid: str) -> dict | None:
if event_type != "start_long_task":
async def process(self, event: str, data: dict, sid: str) -> dict | None:
if event != "start_long_task":
return None
correlation_id = data.get("correlationId")
@ -609,7 +607,7 @@ The manager validates the payload, resolves/creates `correlationId`, and passes
## Best Practices Checklist
- [ ] Always validate inbound payloads in `process_event` (required fields, type constraints, length limits).
- [ ] Always validate inbound payloads in `process()` (required fields, type constraints, length limits).
- [ ] Propagate `correlationId` through multi-step workflows so logs and envelopes align.
- [ ] Respect the 50MB payload cap; prefer HTTP + polling for bulk data transfers.
- [ ] Ensure long-running operations emit progress via `emit_to` or switch to an async task with periodic updates.
@ -657,7 +655,7 @@ The manager validates the payload, resolves/creates `correlationId`, and passes
> **Tip:** When extending the infrastructure (new metadata) start by updating the contracts, sync the manager/frontend helpers, and then document the change here so producers and consumers stay in lockstep.
## Error Codes Registry (Draft for Phase 6)
## Error Codes Registry
The WebSocket stack standardizes backend error codes returned in `RequestResultItem.error.code`. This registry documents the currently used codes and their intended meaning. Client and server implementations should reference these values verbatim (UPPER_SNAKE_CASE).
@ -666,7 +664,8 @@ The WebSocket stack standardizes backend error codes returned in `RequestResultI
| `NO_HANDLERS` | Manager routing | No handler is registered for the requested `eventType`. | Register a handler for the event or correct the event name. | `{ "handlerId": "WsManager", "ok": false, "error": { "code": "NO_HANDLERS", "error": "No handler for 'missing'" } }` |
| `TIMEOUT` | Aggregated or single request | The request exceeded `timeoutMs`. | Increase `timeoutMs`, reduce handler processing time, or split work. | `{ "handlerId": "ExampleHandler", "ok": false, "error": { "code": "TIMEOUT", "error": "Request timeout" } }` |
| `CONNECTION_NOT_FOUND` | Singlesid request | Target `sid` is not connected/known. | Use an active `sid` or retry after reconnect. | `{ "handlerId": "WsManager", "ok": false, "error": { "code": "CONNECTION_NOT_FOUND", "error": "Connection 'sid-123' not found" } }` |
| `HARNESS_UNKNOWN_EVENT` | Developer harness | Harness test handler received an unsupported event name. | Update harness sources or disable the step before running automation. | `{ "handlerId": "api.ws_dev_test.WsDevTest", "ok": false, "error": { "code": "HARNESS_UNKNOWN_EVENT", "error": "Unhandled event", "details": "ws_tester_foo" } }` |
| `NOT_AVAILABLE` | Developer harness | Feature is restricted to development mode. | Ensure `runtime.is_development()` returns `True` or skip the operation. | `{ "handlerId": "api.ws_dev_test.WsDevTest", "ok": false, "error": { "code": "NOT_AVAILABLE", "error": "Event console is available only in development mode" } }` |
| `SUBSCRIBE_FAILED` | Developer harness | Diagnostic watcher subscription failed. | Verify the SID is connected and retry. | `{ "handlerId": "api.ws_dev_test.WsDevTest", "ok": false, "error": { "code": "SUBSCRIBE_FAILED", "error": "Unable to subscribe to diagnostics" } }` |
Notes
- Error payload shape follows the contract documented in `contracts/event-schemas.md` (`RequestResultItem.error`).