Errors
Every boundary in Riverbase returns RiverbaseResult<T>, which is Result<T, RiverbaseError>.
Errors are structured, so clients can show a stable message while logs keep the details.
Shape
| Field | Role |
|---|---|
http_status, http_title | HTTP status and title for the problem response |
errcode | A library-wide unique code in XXX-NNN form: a 3-character module prefix and a 3-digit serial (CMD-002, TDO-001) |
errmesg | A unique, user-safe sentence |
errdata | JSON details for developers: identifiers, the inner cause |
errhint | Optional remediation text |
RiverbaseError serializes for JSON APIs, and its Display is {errmesg} ({errcode}). Over HTTP it
becomes an RFC 7807 problem document: type is {type_base}{errcode}, detail is the errmesg, and errcode, errdata, and errhint ride along.
{
"type": "https://riverbase.io/~/rs/error/TDO-001",
"title": "Unprocessable Content",
"status": 422,
"detail": "Todo update requires at least one of 'done' or 'notes'.",
"errcode": "TDO-001",
"errdata": "update requires at least one of done or notes"
}
Declare a catalogue
Declare each outcome once per crate, then raise it from any site:
riverbase_core::declare_errors! {
type_base: "https://example.com/~/errors/",
TDO_030 {
status: 409,
title: "Conflict",
code: "TDO-030",
message: "A todo with this title already exists.",
hint: "Pick a different title or update the existing item.",
}
}
return Err(errors::TDO_030.with_data(json!({ "title": title })));
// Replace the catalogue hint at one site only:
return Err(errors::TDO_030.with_hint(format!("'{title}' is taken")));
Rules that keep codes meaningful:
- Each
errcodeand eacherrmesgis unique. The same outcome reached from many places reuses one spec. - Raise at the detection site. Call
SPEC.with_data(…),raise,with_hint, orfrom_driverwhere the failure is found. Do not wrap a spec in a helper function that only forwards to it. - Propagate with
?. Do not remap an error that is already aRiverbaseErrorat a boundary.
Category helpers
For quick, uncatalogued cases the category constructors pick the status for you:
| Helper | Status |
|---|---|
InvalidArgumentError::with(code, message, data) | 422 |
ForbiddenError::with(…) | 403 |
NotFoundError::with(…) | 404 |
ConflictError::with(…) | 409 |
StorageError::with(…) | 500 |
ConfigError::with(…) | 500 |
InvalidArgumentError is 422 Unprocessable Content, not 400.
Datastore errors
Diesel errors map to stable codes: not-found is DAT-001 (404), and known database error kinds use
DAT-090 to DAT-098. A unique violation is DAT-090 (409). For a database error, the client-facing
errdata is only { "kind": "<DatabaseErrorKind>" }, so SQL never leaks.
Framework prefixes
| Prefix | Area |
|---|---|
C00 | Shared base and context |
CMD | Command runtime |
DOM | Domain composition and aggregate sessions |
QRY | Query and report runtime |
DAT | Datastore |
LOG | Audit, response, and idempotency stores |
CFG | Configuration and startup checks |
AUT | Authentication |
CAS | Casbin authorization |
WEB | HTTP adapter |
Extension crates and your domains reserve their own prefixes, such as TDO for the todo service.
Pick a prefix per crate and keep a registry of allocated codes; the framework's CI checks code
uniqueness.