asobi_error (asobi v0.75.1)
View SourceThe one error object.
Every failure asobi reports - REST, WebSocket, and the ops/RPC surfaces built on top of them - is describable as:
{"error": {"code": "storage.not_found", "message": "...", "details": {}}}code is the contract. It is machine-readable, namespaced by domain
(storage., save., auth., social., match., world., chat.,
matchmaker. and the rest) or bare when it is cross-cutting (rate_limited,
internal), and drawn from the closed set in codes/0 - a client may branch
on it. message is prose for a human reading a log; it may be reworded at any
time and must not be parsed. details is always a map, #{} when there is
nothing to add, so no client needs a null branch.
The code set is closed on purpose: script- and client-supplied strings never
become codes. An unrecognised reason is reported under a known code with the
raw string in details.
An installed extension widens the set by declaring codes/0 in its manifest
(see asobi_extension). That set is read once, at
asobi_extensions:resolve/0, from validated manifests, so the set is still
closed per deployment: nothing reachable from a request can add to it. Core's
own codes are core_codes/0, which is what asobi_extension_reserved
derives core's reserved namespaces from - an extension may only mint codes in
a domain it owns.
Codes carry their HTTP status (status/1), so a REST controller states the
failure and never the number:
{asobi_error, ~"storage.not_found"}
{asobi_error, ~"save.version_conflict", #{current_version => 4}}register_handler/0 installs handle/3 as the Nova return handler for those
tuples.
A handful of routes answered with more than error before the object existed
(fields, errors, retry_after, field). Those keys stay exactly where
they were and are also the object's details, so an existing client keeps
working and a new one reads one place: see legacy/2.
Summary
Functions
Every defined code: core's, plus every installed extension's.
The codes asobi itself defines.
Whether Code is defined by core or by an installed extension.
The error object for a WebSocket reason string.
Nova return handler for {asobi_error, ...} controller results.
A Nova {json, ...} result carrying the object for Code plus Extra.
legacy/2's body, for a caller that writes the response itself.
The registered human-readable message for Code.
The error object for Code, with no details.
The error object for Code, carrying Details.
The error object for Code with the message overridden.
Install handle/3 as Nova's handler for {asobi_error, ...} results.
The HTTP status for Code. An undefined code is a server bug: 500.
The WebSocket reason -> code mapping, as {Reason, Code} pairs.
Types
Functions
-spec codes() -> [code()].
Every defined code: core's, plus every installed extension's.
The client-facing contract, enumerated. Use core_codes/0 when the question
is what asobi itself defines.
-spec core_codes() -> [code()].
The codes asobi itself defines.
asobi_extension_reserved derives core's reserved RPC prefixes from these
domains, and must not see extension codes: an extension would then be told it
claims a namespace it owns.
Whether Code is defined by core or by an installed extension.
For a caller that reports an error on a surface with no Nova return handler
to log for it - asobi_rpc - and must not let an undefined code reach a
client unnoticed.
The error object for a WebSocket reason string.
reason is the pre-existing WebSocket error dialect and stays on the wire
untouched; this maps it onto a code. A reason with no code of its own becomes
ws.request_failed with the raw reason in details - script-supplied strings
must not be able to mint codes.
-spec handle({asobi_error, code()} | {asobi_error, code(), details()} | {asobi_error, pos_integer(), code(), details()}, fun(), cowboy_req:req()) -> {ok, cowboy_req:req()}.
Nova return handler for {asobi_error, ...} controller results.
{asobi_error, Code} and {asobi_error, Code, Details} take their HTTP
status from the code. {asobi_error, Status, Code, Details} overrides it,
for the caller that must return a status the code does not imply.
-spec legacy(code(), details()) -> {json, pos_integer(), #{}, map()}.
A Nova {json, ...} result carrying the object for Code plus Extra.
For the routes that answered with more than an error string before the
object existed. Extra is those top-level keys, unchanged, so a client reading
fields or retry_after keeps working; the same map is the object's
details. The status still comes from the code.
legacy/2's body, for a caller that writes the response itself.
The plugins reply through cowboy_req rather than returning to Nova.
The registered human-readable message for Code.
The error object for Code, with no details.
The error object for Code, carrying Details.
The error object for Code with the message overridden.
For the rare failure whose registered message would hide the one fact the
caller needs. Prefer object/2: a per-call message is not part of the
contract and cannot be translated or reused.
-spec register_handler() -> ok.
Install handle/3 as Nova's handler for {asobi_error, ...} results.
-spec status(code()) -> pos_integer().
The HTTP status for Code. An undefined code is a server bug: 500.
The WebSocket reason -> code mapping, as {Reason, Code} pairs.