refactor(msg-bus): prefer explicit enum on AsyncAPI error
This commit is contained in:
@@ -1,14 +0,0 @@
|
|||||||
enum RtErrorCode {
|
|
||||||
MINUS_32001 = -32001,
|
|
||||||
MINUS_32002 = -32002,
|
|
||||||
MINUS_32003 = -32003,
|
|
||||||
MINUS_32004 = -32004,
|
|
||||||
MINUS_32005 = -32005,
|
|
||||||
MINUS_32006 = -32006,
|
|
||||||
MINUS_32007 = -32007,
|
|
||||||
MINUS_32603 = -32603,
|
|
||||||
MINUS_32600 = -32600,
|
|
||||||
MINUS_32601 = -32601,
|
|
||||||
MINUS_32602 = -32602
|
|
||||||
}
|
|
||||||
export type { RtErrorCode as default };
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import type RtErrorCode from './RtErrorCode';
|
|
||||||
import type RtErrorMessage from './RtErrorMessage';
|
import type RtErrorMessage from './RtErrorMessage';
|
||||||
// AUTO-GENERATED — do not edit by hand.
|
// AUTO-GENERATED — do not edit by hand.
|
||||||
// Regenerate with `just asyncapi-ts`.
|
// Regenerate with `just asyncapi-ts`.
|
||||||
interface RtErrorObject {
|
interface RtErrorObject {
|
||||||
code: RtErrorCode;
|
code: number;
|
||||||
data?: unknown;
|
data?: unknown;
|
||||||
message: RtErrorMessage;
|
message: RtErrorMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Named constants for JSON-RPC 2.0 error codes emitted on the message-bus
|
||||||
|
// WebSocket wire. Mirrors `application/ports/message_bus_ports.rs::error_code`
|
||||||
|
// on the server — the Rust module is the source of truth.
|
||||||
|
//
|
||||||
|
// Hand-written, deliberately not generated: Modelina projects a JSON-Schema
|
||||||
|
// `enum` of numeric values into a TS enum with mangled member names
|
||||||
|
// (`MINUS_32001 = -32001`), which reads worse than no enum at all. The wire
|
||||||
|
// type is just `number`; readable name-to-code lookup lives here.
|
||||||
|
//
|
||||||
|
// Values are frozen across releases — a new denial cause gets a new value,
|
||||||
|
// never repurposes an existing one. Adding a code: bump the Rust module and
|
||||||
|
// this file in the same commit; the wire spec's description text is a
|
||||||
|
// derivative of the Rust constants (see `generate-asyncapi.rs`).
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application-defined codes live in the JSON-RPC 2.0 server-defined range
|
||||||
|
* `-32099..-32000`; standard envelope codes live in `-32700..-32600`.
|
||||||
|
*/
|
||||||
|
export const RtErrorCode = {
|
||||||
|
// ── Application-defined (subscribe / edit path denials) ─────────────
|
||||||
|
/** Resource-scoped topic, caller lacks Read on the resource (or the
|
||||||
|
* resource does not exist — the two outcomes are indistinguishable to
|
||||||
|
* the caller by design, to preserve anti-enumeration). */
|
||||||
|
NO_READ: -32001,
|
||||||
|
/** Resource-scoped topic requires Share, caller has Read but not Share.
|
||||||
|
* Applies to `file:{id}:shares` (Phase B). */
|
||||||
|
NO_SHARE: -32002,
|
||||||
|
/** Resource-scoped topic requires Comment (`file:{id}:comments`, Phase B). */
|
||||||
|
NO_COMMENT: -32003,
|
||||||
|
/** Identity-scoped mismatch, OR unknown/malformed topic. Same wire code
|
||||||
|
* regardless of whether the target exists — anti-enum. */
|
||||||
|
TOPIC_FORBIDDEN: -32004,
|
||||||
|
/** Per-connection subscription cap hit. */
|
||||||
|
SUB_LIMIT: -32005,
|
||||||
|
/** Subscribe-frame token bucket exhausted. */
|
||||||
|
RATE_LIMITED: -32006,
|
||||||
|
/** CRDT edit frame from a caller without Edit on the doc. Emitted as an
|
||||||
|
* `rt.write_denied` notification (not tied to a request id). */
|
||||||
|
NO_EDIT: -32007,
|
||||||
|
|
||||||
|
// ── JSON-RPC 2.0 standard envelope codes ────────────────────────────
|
||||||
|
/** Server-side failure the client should retry. */
|
||||||
|
INTERNAL_ERROR: -32603,
|
||||||
|
/** Malformed JSON-RPC envelope (missing `method`, wrong `jsonrpc` version). */
|
||||||
|
INVALID_REQUEST: -32600,
|
||||||
|
/** Method outside the `rt.*` allow-list. */
|
||||||
|
METHOD_NOT_FOUND: -32601,
|
||||||
|
/** Method known but `params` shape wrong (missing `topic`, unparseable). */
|
||||||
|
INVALID_PARAMS: -32602
|
||||||
|
} as const satisfies Record<string, number>;
|
||||||
|
|
||||||
|
/** Union of every named code's numeric value. Narrows a bare `number` on
|
||||||
|
* `RtErrorObject.code` to the eleven known literals for exhaustive
|
||||||
|
* `switch` blocks. */
|
||||||
|
export type RtErrorCodeValue = (typeof RtErrorCode)[keyof typeof RtErrorCode];
|
||||||
@@ -473,10 +473,30 @@ fn rpc_error_object_schema() -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn rpc_error_code_schema() -> Value {
|
fn rpc_error_code_schema() -> Value {
|
||||||
json!({
|
// Kept as plain `integer` — Modelina projects a JSON-Schema `enum` of
|
||||||
"type": "integer",
|
// numeric values into a TS enum with mangled member names
|
||||||
"description": "Stable integer error code. Values are frozen across releases — a new denial cause gets a new value, never repurposes an existing one.",
|
// (`MINUS_32001 = -32001`), which is worse than no enum at all. The
|
||||||
"enum": [
|
// Rust `error_code` module is the source of truth for named
|
||||||
|
// constants; the FE mirrors it in `frontend/src/lib/message-bus/
|
||||||
|
// error-codes.ts` (hand-written, 11 lines, sits alongside the
|
||||||
|
// generated DTOs). Description enumerates the full set inline so the
|
||||||
|
// AsyncAPI spec is still self-documenting.
|
||||||
|
let full_description = format!(
|
||||||
|
"Stable integer error code. Values are frozen across releases — a \
|
||||||
|
new denial cause gets a new value, never repurposes an existing \
|
||||||
|
one. Application-defined codes ({}..={}):\n\
|
||||||
|
· {} NO_READ — resource-scoped topic, caller lacks Read (or \
|
||||||
|
resource doesn't exist — indistinguishable by design)\n\
|
||||||
|
· {} NO_SHARE — resource requires Share, caller has Read but not Share\n\
|
||||||
|
· {} NO_COMMENT — resource requires Comment\n\
|
||||||
|
· {} TOPIC_FORBIDDEN — identity-scoped mismatch or unknown/malformed topic\n\
|
||||||
|
· {} SUB_LIMIT — per-connection subscription cap hit\n\
|
||||||
|
· {} RATE_LIMITED — subscribe-frame token bucket exhausted\n\
|
||||||
|
· {} NO_EDIT — CRDT edit frame from a caller without Edit\n\
|
||||||
|
Standard JSON-RPC 2.0 codes:\n\
|
||||||
|
· {} INTERNAL_ERROR · {} INVALID_REQUEST · {} METHOD_NOT_FOUND · {} INVALID_PARAMS",
|
||||||
|
-32099,
|
||||||
|
-32000,
|
||||||
error_code::NO_READ,
|
error_code::NO_READ,
|
||||||
error_code::NO_SHARE,
|
error_code::NO_SHARE,
|
||||||
error_code::NO_COMMENT,
|
error_code::NO_COMMENT,
|
||||||
@@ -488,7 +508,10 @@ fn rpc_error_code_schema() -> Value {
|
|||||||
error_code::INVALID_REQUEST,
|
error_code::INVALID_REQUEST,
|
||||||
error_code::METHOD_NOT_FOUND,
|
error_code::METHOD_NOT_FOUND,
|
||||||
error_code::INVALID_PARAMS,
|
error_code::INVALID_PARAMS,
|
||||||
],
|
);
|
||||||
|
json!({
|
||||||
|
"type": "integer",
|
||||||
|
"description": full_description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user