Skip to main content

surrealdb_expr/expr/
error.rs

1//! Failures raised by the value algebra.
2//!
3//! These are the ways an expression can fail to produce a value: an operand
4//! pair no operator accepts, a conversion the target type refuses, a document
5//! shape a clause cannot consume. They describe the data, not the machinery
6//! evaluating it, which is why a subset of them may be resolved to `NONE`
7//! rather than aborting the query - see [`Error::is_ignorable`].
8
9// The mapper below is the only place this layer's failures become public.
10// A new variant must make that decision explicitly rather than inheriting
11// whatever the last arm happened to be.
12#![deny(clippy::wildcard_enum_match_arm)]
13
14use common::{LeafError, internal_todo};
15use surrealdb_types::{Error as TypesError, SerializationError, ToSql, ValidationError};
16
17use crate::expr::operation::PatchError;
18use crate::val::{CastError, CoerceError, Value};
19
20/// A failure in the value algebra.
21#[derive(Debug, thiserror::Error)]
22pub enum Error {
23	/// There was an error with the SQL query
24	#[error("Cannot use {} in a CONTENT clause", value.to_sql())]
25	InvalidContent {
26		value: Value,
27	},
28
29	/// There was an error with the SQL query
30	#[error("Cannot use {} in a MERGE clause", value.to_sql())]
31	InvalidMerge {
32		value: Value,
33	},
34
35	/// A value is nested deeper than the engine will walk
36	#[error(
37		"The value is nested too deeply. A value may be nested at most {max} levels; reduce the nesting or split the data across records"
38	)]
39	ValueTooDeep {
40		max: usize,
41	},
42
43	/// There was an error with the provided JSON Patch
44	#[error("The JSON Patch contains invalid operations. {0}")]
45	InvalidPatch(PatchError),
46
47	/// Given test operation failed for JSON Patch
48	#[error(
49		"Given test operation failed for JSON Patch. Expected `{expected}`, but got `{got}` instead."
50	)]
51	PatchTest {
52		expected: String,
53		got: String,
54	},
55
56	/// The wrong quantity or magnitude of arguments was given for the specified
57	/// function
58	#[error("Incorrect arguments for function {name}(). {message}")]
59	InvalidFunctionArguments {
60		name: String,
61		message: String,
62	},
63
64	/// The wrong quantity or magnitude of arguments was given for the specified
65	/// method
66	#[error("Incorrect arguments for method {name}(). {message}")]
67	InvalidMethodArguments {
68		name: String,
69		message: String,
70	},
71
72	/// Invalid regular expression
73	#[error("Invalid regular expression: {0:?}")]
74	InvalidRegex(String),
75
76	/// Found a record id for the record but this is not a valid id
77	#[error("Found {value} for the Record ID but this is not a valid id")]
78	IdInvalid {
79		value: String,
80	},
81
82	/// Found a table name for the record but this is not a valid table
83	#[error("Found {value} for the Record ID but this is not a valid table name")]
84	TbInvalid {
85		value: String,
86	},
87
88	/// A destructuring variant was used in a context where it is not supported
89	#[error("{variant} destructuring method is not supported here")]
90	UnsupportedDestructure {
91		variant: String,
92	},
93
94	/// Unable to coerce to a value to another value
95	#[error("{0}")]
96	Coerce(#[from] CoerceError),
97
98	/// Unable to convert a value to another value
99	#[error("{0}")]
100	Cast(#[from] CastError),
101
102	/// Cannot perform addition
103	#[error("Cannot perform addition with '{0}' and '{1}'")]
104	TryAdd(String, String),
105
106	/// Cannot perform subtraction
107	#[error("Cannot perform subtraction with '{0}' and '{1}'")]
108	TrySub(String, String),
109
110	/// Cannot perform multiplication
111	#[error("Cannot perform multiplication with '{0}' and '{1}'")]
112	TryMul(String, String),
113
114	/// Cannot perform division
115	#[error("Cannot perform division with '{0}' and '{1}'")]
116	TryDiv(String, String),
117
118	/// Cannot perform remainder
119	#[error("Cannot perform remainder with '{0}' and '{1}'")]
120	TryRem(String, String),
121
122	/// Cannot perform power
123	#[error("Cannot raise the value '{0}' with '{1}'")]
124	TryPow(String, String),
125
126	/// Cannot perform negation
127	#[error("Cannot negate the value '{0}'")]
128	TryNeg(String),
129
130	/// Cannot extend a non-array value
131	#[error("Cannot extend '{0}' as it is not an array")]
132	TryExtend(String),
133
134	/// It's is not possible to convert between the two types
135	#[error("Cannot convert from '{0}' to '{1}'")]
136	TryFrom(String, &'static str),
137
138	/// Represents a failure in timestamp arithmetic related to database
139	/// internals
140	#[error("Timestamp arithmetic error: {0}")]
141	TimestampOverflow(String),
142
143	/// The supplied type could not be serialized into `expr::Value`
144	#[error("Serialization error: {0}")]
145	Serialization(String),
146
147	/// Represents an arithmetic result that does not fit the value's type
148	#[error("Failed to compute: \"{0}\", as the operation results in an arithmetic overflow.")]
149	ArithmeticOverflow(String),
150
151	/// Represents a negative value for a type that must be zero or positive
152	#[error("Failed to compute: \"{0}\", as the operation results in a negative value.")]
153	ArithmeticNegativeOverflow(String),
154
155	#[error("The string could not be parsed into a bytesize")]
156	InvalidBytesize,
157}
158
159impl LeafError for Error {
160	fn map_kind(self, message: String) -> TypesError {
161		match self {
162			// Validation: the value handed to the operation had the wrong shape.
163			Error::InvalidContent {
164				value,
165			} => TypesError::validation(
166				message,
167				ValidationError::InvalidContent {
168					value: value.to_sql(),
169				},
170			),
171			Error::InvalidMerge {
172				value,
173			} => TypesError::validation(
174				message,
175				ValidationError::InvalidMerge {
176					value: value.to_sql(),
177				},
178			),
179			Error::InvalidPatch(_) => TypesError::validation(message, None),
180			// A rejected shape of input, so it is reported like any other
181			// validation failure rather than as an engine fault.
182			Error::ValueTooDeep {
183				..
184			} => TypesError::validation(message, None),
185			Error::Coerce(_) => TypesError::validation(message, None),
186			Error::Cast(_) => TypesError::validation(message, None),
187			Error::TryAdd(..)
188			| Error::TrySub(..)
189			| Error::TryMul(..)
190			| Error::TryDiv(..)
191			| Error::TryRem(..)
192			| Error::TryPow(..)
193			| Error::TryNeg(_)
194			| Error::TryExtend(_) => TypesError::validation(message, None),
195			Error::TryFrom(..) => TypesError::validation(message, None),
196
197			// Serialization
198			Error::Serialization(..) => {
199				TypesError::serialization(message, SerializationError::Serialization)
200			}
201
202			// A versionstamp that will not fit is an engine-internals fault, not
203			// something the caller phrased wrongly.
204			Error::TimestampOverflow(..) => TypesError::internal(message),
205
206			// Reach clients as an untyped internal error only because they always
207			// have. Each is a candidate for a real kind, and giving one a kind
208			// moves the wire snapshot, so it is a deliberate change and not a
209			// tidy-up.
210			Error::PatchTest {
211				..
212			}
213			| Error::InvalidFunctionArguments {
214				..
215			}
216			| Error::InvalidMethodArguments {
217				..
218			}
219			| Error::InvalidRegex(_)
220			| Error::IdInvalid {
221				..
222			}
223			| Error::TbInvalid {
224				..
225			}
226			| Error::UnsupportedDestructure {
227				..
228			}
229			| Error::ArithmeticOverflow(_)
230			| Error::ArithmeticNegativeOverflow(_)
231			| Error::InvalidBytesize => internal_todo(message),
232		}
233	}
234}
235
236impl Error {
237	/// Returns true if this error represents a data-shape problem (type
238	/// mismatch, coercion failure, bad operand pair) that expression evaluation
239	/// may resolve to `NONE` instead of propagating.
240	///
241	/// Returns false for everything else, including the arithmetic failures
242	/// that are not listed. Membership is decided per variant and is not a
243	/// family rule: `TryDiv` and `TryPow` are ignorable, `TryRem` and `TryFrom`
244	/// are not. Do not widen the list to make it look tidy.
245	pub fn is_ignorable(&self) -> bool {
246		matches!(
247			self,
248			Error::Coerce(_)
249				| Error::Cast(_)
250				| Error::InvalidFunctionArguments { .. }
251				| Error::TryAdd(..)
252				| Error::TrySub(..)
253				| Error::TryMul(..)
254				| Error::TryDiv(..)
255				| Error::TryPow(..)
256				| Error::TryNeg(..)
257				| Error::TryExtend(..)
258		)
259	}
260}
261
262impl From<regex::Error> for Error {
263	fn from(error: regex::Error) -> Self {
264		Error::InvalidRegex(error.to_string())
265	}
266}