Chuyển tới nội dung chính

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​

FieldRole
http_status, http_titleHTTP status and title for the problem response
errcodeA library-wide unique code in XXX-NNN form: a 3-character module prefix and a 3-digit serial (CMD-002, TDO-001)
errmesgA unique, user-safe sentence
errdataJSON details for developers: identifiers, the inner cause
errhintOptional 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:

domain/src/errors.rs
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 errcode and each errmesg is unique. The same outcome reached from many places reuses one spec.
  • Raise at the detection site. Call SPEC.with_data(…), raise, with_hint, or from_driver where 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 a RiverbaseError at a boundary.

Category helpers​

For quick, uncatalogued cases the category constructors pick the status for you:

HelperStatus
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​

PrefixArea
C00Shared base and context
CMDCommand runtime
DOMDomain composition and aggregate sessions
QRYQuery and report runtime
DATDatastore
LOGAudit, response, and idempotency stores
CFGConfiguration and startup checks
AUTAuthentication
CASCasbin authorization
WEBHTTP 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.