Skip to main content

surrealdb_common/error/
leaf.rs

1//! The contract a SurrealDB error type implements to reach the wire.
2//!
3//! Each layer of the engine owns its own error enum, but they all have to
4//! arrive at the client as one [`surrealdb_types::Error`]. [`LeafError`] is
5//! that conversion, stated once so every layer answers the same question the
6//! same way.
7
8use surrealdb_types::Error as TypesError;
9
10/// Converts an error into its public form.
11///
12/// Implement [`map_kind`](LeafError::map_kind) and nothing else.
13///
14/// # Nesting
15///
16/// When one error wraps another, the outer arm must call the inner error's
17/// `map_kind`, **not** its `to_types_error`. `to_types_error` attaches the
18/// cause, so calling it inward attaches one at each level and the outer
19/// attachment silently replaces the inner one. `map_kind` classifies without
20/// touching the cause, which is why it is the only method to write and
21/// `to_types_error` is provided.
22pub trait LeafError: std::error::Error + Sized + Send + Sync + 'static {
23	/// Classify this error: kind, details and code.
24	///
25	/// `message` is the error's own `Display` output, computed once by
26	/// [`to_types_error`](LeafError::to_types_error) and passed in so an
27	/// implementation can move payload fields out of `self` rather than
28	/// cloning them to build its message.
29	///
30	/// Do not attach a cause here.
31	fn map_kind(self, message: String) -> TypesError;
32
33	/// Produce the public error.
34	///
35	/// Frames [`map_kind`](LeafError::map_kind) with the error's message and
36	/// one level of its source chain. Deeper levels are deliberately dropped:
37	/// they routinely name storage internals and raw field values, which are
38	/// not for clients.
39	fn to_types_error(self) -> TypesError {
40		let message = self.to_string();
41		let cause = std::error::Error::source(&self).map(|s| TypesError::internal(s.to_string()));
42		let mapped = self.map_kind(message);
43		match cause {
44			Some(cause) => mapped.with_cause(cause),
45			None => mapped,
46		}
47	}
48}
49
50/// A variant that reaches clients as an untyped internal error only because it
51/// always has.
52///
53/// Behaviourally identical to [`TypesError::internal`]. It exists to keep those
54/// variants greppable and countable while they are worked off, so that a
55/// deliberate `internal` and an unclassified one do not look alike.
56#[inline]
57pub fn internal_todo(message: String) -> TypesError {
58	TypesError::internal(message)
59}