util.error
The util.error
library implements a common object
for passing around errors.
local errors = require "util.error".init(module.name, {
["invalid-room-name"] = { code = 400, text = "Invalid room name" };
["room-not-found"] = { code = 404, text = "Room not found" };
["invalid-data"] = { code = 400, text = "Invalid data" };
["invalid-json"] = { code = 400, text = "Invalid JSON" };
});
function foo(json)
if json == invalid then
return nil, errors.new("invalid-json", { request = request });
end
end
Overview
Error objects
Every time an error occurs, an error object is created. Error objects
are typically “thrown” using Lua’s built-in error()
function, or they are returned from a function
(e.g. return nil, err
), or possibly a promise may be
rejected with an error object (reject(err)
).
Each error object has a unique id. This can be e.g. reported to the user, and allow an admin to look up the specific occurrence of the error in the log files. An object also has an attached ‘context’ table, which may contain additional information that helps to diagnose the error.
The following fields are present on error objects:
- instance_id (string)
- source (string, optional)
- context (table)
- type (string)
- condition (string)
- text (string, optional)
- code (number, optional)
- extra (table, optional)
For definitions of ‘type’, ‘condition’, ‘text’ and ‘code’ see the ‘Error classes’ section. For a description of the ‘context’ field, see the ‘Error contexts’ section.
The ‘instance_id’ is a short random string to identify this specific occurrence of the error. ‘source’ is an optional human-readable string identifying the module or component that created the error object.
The ‘extra’ table may contain additional properties of the error that doesn’t fit anywhere else.
Error contexts
The error context is an additional table that can be used to attach certain metadata and objects to an error instance.
All fields in the context are optional, and all code should be prepared to handle their omission.
The following fields are currently defined:
- actor (human-readable string identifying the entity that issued the action that caused the error to happen, e.g. IP or JID)
- by (identity of the entity that issues the error)
- request (HTTP request object)
- stanza (util.stanza object)
- session (util.session object)
- wrapped_error (string or any other type - a “legacy” error value that has been caught and wrapped in an error object)
- traceback (string)
Error classes
Where possible, an error object should be derived from a predefined “error class”. These are typically collected in an error “registry”. Each error class provides information about a certain kind of error that can happen, and this information can be used to convert the error to e.g an XMPP error stanza or an appropriate HTTP response as needed.
You can think of an error class as a “template” for new errors of a certain type.
The possible fields of an error class are described in the following section.
Error class fields
These mostly correspond to XMPP stanza errors.
type
-
Which basic kind of error is. Defaults to
cancel
. Other common values areauth
,modify
orwait
. condition
-
The specific error condition. Defaults to
undefined-condition
. text
- Human-readable textual description of what happened. Often shown to users, so keep that in mind.
code
-
Numeric error code. Defaults to
500
. Used e.g. in HTTP.
Extras
Certain errors may need additional properties that don’t fit in any
of the previously mentioned fields. These can be put into the
extra
table.
uri
-
Counterpart to the URI carried in the
<gone>
stanza error.
Application-Specific Conditions
The XMPP Core
specification allows stanza errors to carry Application-Specific
Conditions for e.g. PubSub-specific
errors. These are represented in the extra
table of error
objects as a tag
field or as a tuple of
namespace
and condition
.
Error registries
An error registry is simply a mapping of predefined values to error classes.
local my_error_registry = {
["my-error-type"] = { type = "cancel", condition = "unexpected-request", text = "This is a custom error" };
["my-other-error-type"] = { type = "auth", condition = "forbidden", text = "This is another custom error" };
}
local errors = require "util.error".init("my_module_name", my_error_registry);
-- Throw a new error of the class 'my-error-type' with a context identifying
-- 'me@example.com' as the entity that caused the error
error(errors.new("my-error-type", { actor = "me@example.com" }));
API
new(error_class, context, registry, source)
The base function to create a new error object. For convenience it is usually easier to use the init() method described above.
If registry
is not provided and/or
error_class
does not map to a value defined in the
registry, and the error_class
value is a table, it is
assumed to be an error class object, e.g.:
({ condition = "bad-request" }, { session = some_session }) new
If there is no registry provided, the error_class key is not found in the registry, and error_class is not a table, it is ignored.
init(source, registry)
This function allows you to set a custom source
and/or
registry
for created errors. It returns an object with a
.new(error_class, context)
method, which behaves
identically to the new()
function described above, minus
the registry
and source
parameters.
Compact registry format
If typing type
, condition
and
text
repeatedly gets tedious, there is also a compact form
of the registry:
local my_errors = require"util.error".init(module.name, {
= {"cancel", "internal-server-error", "something went wrong"};
bork })
This format only works with
.init()
.
Namespaced error conditions
When using Application-Specific Conditions the namespace can be given as a second argument between the module name and the registry:
local nserr = require"util.error".init(module.name, "urn:example:error:ns", {
= {"cancel", "internal-server-error", "something went wrong", "it-went-sideways"}
bork })