Skip to main content

surrealdb_types/
error.rs

1use std::fmt;
2use std::time::Duration;
3
4use crate::{Kind, SurrealValue, ToSql, Value};
5
6// -----------------------------------------------------------------------------
7// JSON-RPC 2.0 and SurrealDB-specific error codes (wire backwards compatibility)
8// -----------------------------------------------------------------------------
9
10/// Numeric error codes used on the wire for RPC. Kept for backwards compatibility.
11#[allow(missing_docs)]
12mod code {
13	pub const PARSE_ERROR: i64 = -32700;
14	pub const INVALID_REQUEST: i64 = -32600;
15	pub const METHOD_NOT_FOUND: i64 = -32601;
16	pub const METHOD_NOT_ALLOWED: i64 = -32602;
17	pub const INVALID_PARAMS: i64 = -32603;
18	pub const LIVE_QUERY_NOT_SUPPORTED: i64 = -32604;
19	pub const BAD_LIVE_QUERY_CONFIG: i64 = -32605;
20	pub const BAD_GRAPHQL_CONFIG: i64 = -32606;
21	pub const INTERNAL_ERROR: i64 = -32000;
22	pub const CLIENT_SIDE_ERROR: i64 = -32001;
23	pub const INVALID_AUTH: i64 = -32002;
24	pub const QUERY_NOT_EXECUTED: i64 = -32003;
25	pub const QUERY_TIMEDOUT: i64 = -32004;
26	pub const QUERY_CANCELLED: i64 = -32005;
27	pub const THROWN: i64 = -32006;
28	pub const SERIALIZATION_ERROR: i64 = -32007;
29	pub const DESERIALIZATION_ERROR: i64 = -32008;
30}
31
32/// Default wire code when none is specified (e.g. for deserialization of older wire format).
33fn default_code() -> i64 {
34	code::INTERNAL_ERROR
35}
36
37// -----------------------------------------------------------------------------
38// Public API error type (wire-friendly, non-lossy, supports chaining)
39// -----------------------------------------------------------------------------
40
41/// Represents an error in SurrealDB
42///
43/// Designed to be returned from public APIs (including over the wire). It is
44/// wire-friendly and non-lossy: serialization preserves `kind`, `message`,
45/// and optional `details`. Use this type whenever an error crosses
46/// an API boundary (e.g. server response, SDK method return).
47///
48/// The `details` field is flattened into the serialized object, so the wire
49/// format contains `kind` (string) and optionally `details` (object) at the
50/// same level as `code` and `message`. The optional `cause` field allows
51/// error chaining so that SDKs can receive and display full error chains.
52#[derive(Debug, Clone, PartialEq, Eq, SurrealValue)]
53#[surreal(crate = "crate")]
54pub struct Error {
55	/// Wire-only error code for RPC backwards compatibility.
56	#[surreal(default = "default_code")]
57	code: i64,
58	/// Human-readable error message describing the error.
59	message: String,
60	/// The error kind and optional structured details. The kind is derived from the variant.
61	/// Flattened into the parent object: contributes `kind` and optionally `details` fields.
62	#[surreal(flatten)]
63	details: ErrorDetails,
64	/// Optional underlying cause for error chaining. Serialized as a nested error object on the
65	/// wire.
66	#[surreal(default)]
67	cause: Option<Box<Error>>,
68}
69
70impl Error {
71	/// Validation error (parse error, invalid request or params), with optional structured details.
72	/// When `details` is provided, the wire code is set from the variant (e.g. `Parse` →
73	/// `PARSE_ERROR`).
74	pub fn validation(message: String, details: impl Into<Option<ValidationError>>) -> Self {
75		let details = details.into();
76		let code = details
77			.as_ref()
78			.map(|d| match d {
79				ValidationError::Parse => code::PARSE_ERROR,
80				ValidationError::InvalidRequest => code::INVALID_REQUEST,
81				ValidationError::InvalidParams => code::INVALID_PARAMS,
82				ValidationError::NamespaceEmpty
83				| ValidationError::DatabaseEmpty
84				| ValidationError::InvalidParameter {
85					..
86				}
87				| ValidationError::InvalidContent {
88					..
89				}
90				| ValidationError::InvalidMerge {
91					..
92				} => code::INVALID_REQUEST,
93			})
94			.unwrap_or(code::INTERNAL_ERROR);
95		Self {
96			message,
97			code,
98			details: ErrorDetails::Validation(details),
99			cause: None,
100		}
101	}
102
103	/// Not-allowed error (e.g. method, scripting, function, net target), with optional
104	/// structured details. When `details` is provided, the wire code is set from the variant.
105	pub fn not_allowed(message: String, details: impl Into<Option<NotAllowedError>>) -> Self {
106		let details = details.into();
107		let code = details
108			.as_ref()
109			.map(|d| match d {
110				NotAllowedError::Auth(auth_error) => match auth_error {
111					AuthError::TokenExpired => code::INVALID_AUTH,
112					AuthError::SessionExpired => code::INTERNAL_ERROR,
113					AuthError::InvalidAuth
114					| AuthError::UnexpectedAuth
115					| AuthError::MissingUserOrPass
116					| AuthError::NoSigninTarget
117					| AuthError::InvalidPass
118					| AuthError::TokenMakingFailed
119					| AuthError::InvalidRole {
120						..
121					}
122					| AuthError::NotAllowed {
123						..
124					}
125					| AuthError::InvalidSignup => code::INVALID_AUTH,
126				},
127				NotAllowedError::Method {
128					..
129				}
130				| NotAllowedError::Scripting
131				| NotAllowedError::Function {
132					..
133				}
134				| NotAllowedError::Target {
135					..
136				} => code::METHOD_NOT_ALLOWED,
137			})
138			.unwrap_or(code::INTERNAL_ERROR);
139		Self {
140			message,
141			code,
142			details: ErrorDetails::NotAllowed(details),
143			cause: None,
144		}
145	}
146
147	/// Configuration error (feature or config not supported), with optional structured details.
148	/// When `details` is provided, the wire code is set from the variant.
149	pub fn configuration(message: String, details: impl Into<Option<ConfigurationError>>) -> Self {
150		let details = details.into();
151		let code = details
152			.as_ref()
153			.map(|d| match d {
154				ConfigurationError::LiveQueryNotSupported => code::LIVE_QUERY_NOT_SUPPORTED,
155				ConfigurationError::BadLiveQueryConfig => code::BAD_LIVE_QUERY_CONFIG,
156				ConfigurationError::BadGraphqlConfig => code::BAD_GRAPHQL_CONFIG,
157			})
158			.unwrap_or(code::INTERNAL_ERROR);
159		Self {
160			message,
161			code,
162			details: ErrorDetails::Configuration(details),
163			cause: None,
164		}
165	}
166
167	/// User-thrown error (e.g. from THROW in SurrealQL). Sets wire code for RPC.
168	pub fn thrown(message: String) -> Self {
169		Self {
170			message,
171			code: code::THROWN,
172			details: ErrorDetails::Thrown,
173			cause: None,
174		}
175	}
176
177	/// Query execution error (not executed, timeout, cancelled), with optional structured details.
178	/// When `details` is provided, the wire code is set from the variant.
179	pub fn query(message: String, details: impl Into<Option<QueryError>>) -> Self {
180		let details = details.into();
181		let code = details
182			.as_ref()
183			.map(|d| match d {
184				QueryError::NotExecuted => code::QUERY_NOT_EXECUTED,
185				QueryError::TimedOut {
186					..
187				} => code::QUERY_TIMEDOUT,
188				QueryError::Cancelled => code::QUERY_CANCELLED,
189			})
190			.unwrap_or(code::INTERNAL_ERROR);
191		Self {
192			message,
193			code,
194			details: ErrorDetails::Query(details),
195			cause: None,
196		}
197	}
198
199	/// Serialisation or deserialisation error, with optional structured details.
200	/// When `details` is provided, the wire code is set from the variant.
201	pub fn serialization(message: String, details: impl Into<Option<SerializationError>>) -> Self {
202		let details = details.into();
203		let code = details
204			.as_ref()
205			.map(|d| match d {
206				SerializationError::Serialization => code::SERIALIZATION_ERROR,
207				SerializationError::Deserialization => code::DESERIALIZATION_ERROR,
208			})
209			.unwrap_or(code::INTERNAL_ERROR);
210		Self {
211			message,
212			code,
213			details: ErrorDetails::Serialization(details),
214			cause: None,
215		}
216	}
217
218	/// Resource not found (e.g. table, record, namespace, RPC method), with optional
219	/// structured details. When `details` is `NotFoundError::Method`, the wire code is set to
220	/// `METHOD_NOT_FOUND` for RPC backwards compatibility.
221	pub fn not_found(message: String, details: impl Into<Option<NotFoundError>>) -> Self {
222		let details = details.into();
223		let code = details
224			.as_ref()
225			.and_then(|d| match d {
226				NotFoundError::Method {
227					..
228				} => Some(code::METHOD_NOT_FOUND),
229				_ => None,
230			})
231			.unwrap_or(code::INTERNAL_ERROR);
232		Self {
233			message,
234			code,
235			details: ErrorDetails::NotFound(details),
236			cause: None,
237		}
238	}
239
240	/// Resource already exists (e.g. table, record), with optional structured details.
241	pub fn already_exists(message: String, details: impl Into<Option<AlreadyExistsError>>) -> Self {
242		let details = details.into();
243		Self {
244			message,
245			code: code::INTERNAL_ERROR,
246			details: ErrorDetails::AlreadyExists(details),
247			cause: None,
248		}
249	}
250
251	/// Connection error (e.g. uninitialised, already connected), with optional structured details.
252	/// Used in the SDK for client-side connection state errors.
253	pub fn connection(message: String, details: impl Into<Option<ConnectionError>>) -> Self {
254		let details = details.into();
255		Self {
256			message,
257			code: code::CLIENT_SIDE_ERROR,
258			details: ErrorDetails::Connection(details),
259			cause: None,
260		}
261	}
262
263	/// Internal or unexpected error (server or client). Sets wire code for RPC.
264	pub fn internal(message: String) -> Self {
265		Self {
266			message,
267			code: code::INTERNAL_ERROR,
268			details: ErrorDetails::Internal,
269			cause: None,
270		}
271	}
272
273	/// Build an error from a message and pre-parsed [`ErrorDetails`].
274	/// Uses [`default_code`] for the wire code. Intended for deserialization paths
275	/// that already have a typed `ErrorDetails` (e.g. via `ErrorDetails::from_value`).
276	#[doc(hidden)]
277	pub fn from_details(message: String, details: ErrorDetails) -> Self {
278		Self {
279			code: default_code(),
280			message,
281			details,
282			cause: None,
283		}
284	}
285
286	/// Build an error from message, details, and optional cause. For deserialization when cause is
287	/// present.
288	#[doc(hidden)]
289	pub fn from_details_with_cause(
290		message: String,
291		details: ErrorDetails,
292		cause: Option<Box<Error>>,
293	) -> Self {
294		Self {
295			code: default_code(),
296			message,
297			details,
298			cause,
299		}
300	}
301
302	/// Build an error from the query-result wire shape (message, optional kind string, details).
303	/// Used when deserialising query result error payloads that do not include `code`. Uses
304	/// [`default_code`] and defaults kind to "Internal" when not present.
305	#[doc(hidden)]
306	pub fn from_parts(message: String, kind: Option<&str>, details: Option<Value>) -> Self {
307		let kind_str = kind.unwrap_or("Internal");
308		let typed_details = match details {
309			Some(v) => ErrorDetails::from_value_with_kind_str(kind_str, v)
310				.unwrap_or_else(|_| ErrorDetails::from_kind_str(kind_str)),
311			None => ErrorDetails::from_kind_str(kind_str),
312		};
313		Self {
314			code: default_code(),
315			message,
316			details: typed_details,
317			cause: None,
318		}
319	}
320
321	/// Returns the optional underlying cause for error chaining.
322	pub fn cause(&self) -> Option<&Error> {
323		self.cause.as_deref()
324	}
325
326	/// Returns an error with the given cause attached. Consumes `self`.
327	pub fn with_cause(mut self, cause: Error) -> Self {
328		self.cause = Some(Box::new(cause));
329		self
330	}
331
332	/// Returns the kind string for this error (e.g. "NotAllowed", "Internal").
333	pub fn kind_str(&self) -> &'static str {
334		self.details.kind_str()
335	}
336
337	/// Returns the human-readable error message.
338	pub fn message(&self) -> &str {
339		&self.message
340	}
341
342	/// Returns the error details (always present). The variant determines the error kind.
343	pub fn details(&self) -> &ErrorDetails {
344		&self.details
345	}
346
347	/// Returns true if this is a validation error.
348	pub fn is_validation(&self) -> bool {
349		self.details.is_validation()
350	}
351
352	/// Returns true if this is a configuration error.
353	pub fn is_configuration(&self) -> bool {
354		self.details.is_configuration()
355	}
356
357	/// Returns true if this is a query error.
358	pub fn is_query(&self) -> bool {
359		self.details.is_query()
360	}
361
362	/// Returns true if this is a serialization error.
363	pub fn is_serialization(&self) -> bool {
364		self.details.is_serialization()
365	}
366
367	/// Returns true if this is a not-allowed error.
368	pub fn is_not_allowed(&self) -> bool {
369		self.details.is_not_allowed()
370	}
371
372	/// Returns true if this is a not-found error.
373	pub fn is_not_found(&self) -> bool {
374		self.details.is_not_found()
375	}
376
377	/// Returns true if this is an already-exists error.
378	pub fn is_already_exists(&self) -> bool {
379		self.details.is_already_exists()
380	}
381
382	/// Returns true if this is a connection error.
383	pub fn is_connection(&self) -> bool {
384		self.details.is_connection()
385	}
386
387	/// Returns true if this is a user-thrown error.
388	pub fn is_thrown(&self) -> bool {
389		self.details.is_thrown()
390	}
391
392	/// Returns true if this is an internal error.
393	pub fn is_internal(&self) -> bool {
394		self.details.is_internal()
395	}
396
397	/// Returns structured validation error details, if this is a validation error with specifics.
398	pub fn validation_details(&self) -> Option<&ValidationError> {
399		match &self.details {
400			ErrorDetails::Validation(d) => d.as_ref(),
401			_ => None,
402		}
403	}
404
405	/// Returns structured not-allowed error details, if this is a not-allowed error with specifics.
406	pub fn not_allowed_details(&self) -> Option<&NotAllowedError> {
407		match &self.details {
408			ErrorDetails::NotAllowed(d) => d.as_ref(),
409			_ => None,
410		}
411	}
412
413	/// Returns structured configuration error details, if this is a configuration error with
414	/// specifics.
415	pub fn configuration_details(&self) -> Option<&ConfigurationError> {
416		match &self.details {
417			ErrorDetails::Configuration(d) => d.as_ref(),
418			_ => None,
419		}
420	}
421
422	/// Returns structured serialization error details, if this is a serialization error with
423	/// specifics.
424	pub fn serialization_details(&self) -> Option<&SerializationError> {
425		match &self.details {
426			ErrorDetails::Serialization(d) => d.as_ref(),
427			_ => None,
428		}
429	}
430
431	/// Returns structured not-found error details, if this is a not-found error with specifics.
432	pub fn not_found_details(&self) -> Option<&NotFoundError> {
433		match &self.details {
434			ErrorDetails::NotFound(d) => d.as_ref(),
435			_ => None,
436		}
437	}
438
439	/// Returns structured query error details, if this is a query error with specifics.
440	pub fn query_details(&self) -> Option<&QueryError> {
441		match &self.details {
442			ErrorDetails::Query(d) => d.as_ref(),
443			_ => None,
444		}
445	}
446
447	/// Returns structured already-exists error details, if this is an already-exists error with
448	/// specifics.
449	pub fn already_exists_details(&self) -> Option<&AlreadyExistsError> {
450		match &self.details {
451			ErrorDetails::AlreadyExists(d) => d.as_ref(),
452			_ => None,
453		}
454	}
455
456	/// Returns structured connection error details, if this is a connection error with specifics.
457	pub fn connection_details(&self) -> Option<&ConnectionError> {
458		match &self.details {
459			ErrorDetails::Connection(d) => d.as_ref(),
460			_ => None,
461		}
462	}
463}
464
465// -----------------------------------------------------------------------------
466// ErrorDetails enum (typed wrapper for all detail variants)
467// -----------------------------------------------------------------------------
468
469/// Typed error details. Each variant represents an error kind and optionally
470/// wraps the detail enum for that kind. This replaces the separate `kind` field
471/// on [`Error`] -- the kind is derived from the variant.
472///
473/// Rust users can pattern-match directly:
474/// ```ignore
475/// match error.details() {
476///     ErrorDetails::NotAllowed(Some(NotAllowedError::Auth(AuthError::TokenExpired))) => ...,
477///     ErrorDetails::NotFound(Some(NotFoundError::Table { name })) => ...,
478///     ErrorDetails::Internal => ...,
479///     _ => ...,
480/// }
481/// ```
482#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
483#[non_exhaustive]
484#[surreal(crate = "crate")]
485#[surreal(tag = "kind", content = "details", skip_content_if = "Value::is_empty")]
486pub enum ErrorDetails {
487	/// Validation error (parse error, invalid request/params).
488	Validation(Option<ValidationError>),
489	/// Configuration error (feature/config not supported).
490	Configuration(Option<ConfigurationError>),
491	/// Query execution error (timeout, cancelled, not executed).
492	Query(Option<QueryError>),
493	/// Serialization/deserialization error.
494	Serialization(Option<SerializationError>),
495	/// Permission or authorization error.
496	NotAllowed(Option<NotAllowedError>),
497	/// Resource not found.
498	NotFound(Option<NotFoundError>),
499	/// Duplicate resource.
500	AlreadyExists(Option<AlreadyExistsError>),
501	/// Client connection error (SDK-side).
502	Connection(Option<ConnectionError>),
503	/// User-thrown error (THROW in SurrealQL). No detail type.
504	Thrown,
505	/// Internal/unexpected error. No detail type.
506	/// Acts as a catch-all for unknown kinds during deserialization (forward compatibility).
507	#[surreal(other)]
508	Internal,
509}
510
511impl ErrorDetails {
512	/// Returns the kind string for wire serialization (e.g. "NotAllowed", "Internal").
513	pub fn kind_str(&self) -> &'static str {
514		match self {
515			Self::Validation(_) => "Validation",
516			Self::Configuration(_) => "Configuration",
517			Self::Query(_) => "Query",
518			Self::Serialization(_) => "Serialization",
519			Self::NotAllowed(_) => "NotAllowed",
520			Self::NotFound(_) => "NotFound",
521			Self::AlreadyExists(_) => "AlreadyExists",
522			Self::Connection(_) => "Connection",
523			Self::Thrown => "Thrown",
524			Self::Internal => "Internal",
525		}
526	}
527
528	/// Returns true if this is a validation error.
529	pub fn is_validation(&self) -> bool {
530		matches!(self, Self::Validation(_))
531	}
532	/// Returns true if this is a configuration error.
533	pub fn is_configuration(&self) -> bool {
534		matches!(self, Self::Configuration(_))
535	}
536	/// Returns true if this is a query error.
537	pub fn is_query(&self) -> bool {
538		matches!(self, Self::Query(_))
539	}
540	/// Returns true if this is a serialization error.
541	pub fn is_serialization(&self) -> bool {
542		matches!(self, Self::Serialization(_))
543	}
544	/// Returns true if this is a not-allowed error.
545	pub fn is_not_allowed(&self) -> bool {
546		matches!(self, Self::NotAllowed(_))
547	}
548	/// Returns true if this is a not-found error.
549	pub fn is_not_found(&self) -> bool {
550		matches!(self, Self::NotFound(_))
551	}
552	/// Returns true if this is an already-exists error.
553	pub fn is_already_exists(&self) -> bool {
554		matches!(self, Self::AlreadyExists(_))
555	}
556	/// Returns true if this is a connection error.
557	pub fn is_connection(&self) -> bool {
558		matches!(self, Self::Connection(_))
559	}
560	/// Returns true if this is a user-thrown error.
561	pub fn is_thrown(&self) -> bool {
562		matches!(self, Self::Thrown)
563	}
564	/// Returns true if this is an internal error.
565	pub fn is_internal(&self) -> bool {
566		matches!(self, Self::Internal)
567	}
568
569	/// Create an `ErrorDetails` from a kind string, with no inner details.
570	/// Unknown kind strings fall back to `Internal` (forward compatibility).
571	pub(crate) fn from_kind_str(kind: &str) -> Self {
572		match kind {
573			"Validation" => Self::Validation(None),
574			"Configuration" => Self::Configuration(None),
575			"Query" => Self::Query(None),
576			"Serialization" => Self::Serialization(None),
577			"NotAllowed" => Self::NotAllowed(None),
578			"NotFound" => Self::NotFound(None),
579			"AlreadyExists" => Self::AlreadyExists(None),
580			"Connection" => Self::Connection(None),
581			"Thrown" => Self::Thrown,
582			// Unknown kinds fall back to Internal (forward compat)
583			_ => Self::Internal,
584		}
585	}
586
587	/// Deserialize details using the kind string to select the right variant.
588	/// O(1) dispatch -- no trial-and-error parsing.
589	pub(crate) fn from_value_with_kind_str(kind: &str, value: Value) -> Result<Self, Error> {
590		match kind {
591			"Validation" => {
592				ValidationError::from_value(value).map(|v| ErrorDetails::Validation(Some(v)))
593			}
594			"Configuration" => {
595				ConfigurationError::from_value(value).map(|v| ErrorDetails::Configuration(Some(v)))
596			}
597			"Query" => QueryError::from_value(value).map(|v| ErrorDetails::Query(Some(v))),
598			"Serialization" => {
599				SerializationError::from_value(value).map(|v| ErrorDetails::Serialization(Some(v)))
600			}
601			"NotAllowed" => {
602				NotAllowedError::from_value(value).map(|v| ErrorDetails::NotAllowed(Some(v)))
603			}
604			"NotFound" => NotFoundError::from_value(value).map(|v| ErrorDetails::NotFound(Some(v))),
605			"AlreadyExists" => {
606				AlreadyExistsError::from_value(value).map(|v| ErrorDetails::AlreadyExists(Some(v)))
607			}
608			"Connection" => {
609				ConnectionError::from_value(value).map(|v| ErrorDetails::Connection(Some(v)))
610			}
611			"Thrown" => Ok(Self::Thrown),
612			_ => Ok(Self::Internal),
613		}
614	}
615
616	/// Returns true if this variant has inner detail data.
617	pub fn has_details(&self) -> bool {
618		match self {
619			Self::Validation(d) => d.is_some(),
620			Self::Configuration(d) => d.is_some(),
621			Self::Query(d) => d.is_some(),
622			Self::Serialization(d) => d.is_some(),
623			Self::NotAllowed(d) => d.is_some(),
624			Self::NotFound(d) => d.is_some(),
625			Self::AlreadyExists(d) => d.is_some(),
626			Self::Connection(d) => d.is_some(),
627			Self::Thrown | Self::Internal => false,
628		}
629	}
630}
631
632// -----------------------------------------------------------------------------
633// Structured error details (wire format in Error.details)
634// -----------------------------------------------------------------------------
635
636/// Auth failure reason for [`ErrorKind::NotAllowed`] errors.
637#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
638#[surreal(crate = "crate")]
639#[surreal(tag = "kind", content = "details")]
640#[non_exhaustive]
641pub enum AuthError {
642	/// The token used for authentication has expired.
643	#[surreal(skip_content)]
644	TokenExpired,
645	/// The session has expired.
646	#[surreal(skip_content)]
647	SessionExpired,
648	/// Authentication failed (invalid credentials or similar).
649	#[surreal(skip_content)]
650	InvalidAuth,
651	/// Unexpected error while performing authentication.
652	#[surreal(skip_content)]
653	UnexpectedAuth,
654	/// Username or password was not provided.
655	#[surreal(skip_content)]
656	MissingUserOrPass,
657	/// No signin target (SC, DB, NS, or KV) specified.
658	#[surreal(skip_content)]
659	NoSigninTarget,
660	/// The password did not verify.
661	#[surreal(skip_content)]
662	InvalidPass,
663	/// Failed to create the authentication token.
664	#[surreal(skip_content)]
665	TokenMakingFailed,
666	/// Signup failed.
667	#[surreal(skip_content)]
668	InvalidSignup,
669	/// Invalid role (IAM). Carries the role name.
670	InvalidRole {
671		/// Name of the invalid role.
672		name: String,
673	},
674	/// Not enough permissions to perform the action (IAM). Carries actor, action, resource.
675	NotAllowed {
676		/// Actor that attempted the action.
677		actor: String,
678		/// Action that was attempted.
679		action: String,
680		/// Resource the action was attempted on.
681		resource: String,
682	},
683}
684
685impl From<AuthError> for Option<NotAllowedError> {
686	fn from(auth_error: AuthError) -> Self {
687		Some(NotAllowedError::Auth(auth_error))
688	}
689}
690
691/// Validation failure reason for [`ErrorKind::Validation`] errors.
692#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
693#[surreal(crate = "crate")]
694#[surreal(tag = "kind", content = "details")]
695#[non_exhaustive]
696pub enum ValidationError {
697	/// Parse error (invalid message or request format).
698	#[surreal(skip_content)]
699	Parse,
700	/// Invalid request structure.
701	#[surreal(skip_content)]
702	InvalidRequest,
703	/// Invalid parameters.
704	#[surreal(skip_content)]
705	InvalidParams,
706	/// Namespace is empty.
707	#[surreal(skip_content)]
708	NamespaceEmpty,
709	/// Database is empty.
710	#[surreal(skip_content)]
711	DatabaseEmpty,
712	/// Invalid parameter with name.
713	InvalidParameter {
714		/// Name of the invalid parameter.
715		name: String,
716	},
717	/// Invalid content value.
718	InvalidContent {
719		/// The invalid value.
720		value: String,
721	},
722	/// Invalid merge value.
723	InvalidMerge {
724		/// The invalid value.
725		value: String,
726	},
727}
728
729/// Not-allowed reason for [`ErrorKind::NotAllowed`] errors.
730#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
731#[surreal(crate = "crate")]
732#[surreal(tag = "kind", content = "details")]
733#[non_exhaustive]
734pub enum NotAllowedError {
735	/// Scripting not allowed.
736	#[surreal(skip_content)]
737	Scripting,
738	/// Authentication or authorisation failure.
739	Auth(AuthError),
740	/// RPC method not allowed.
741	Method {
742		/// Name of the method.
743		name: String,
744	},
745	/// Function not allowed.
746	Function {
747		/// Name of the function.
748		name: String,
749	},
750	/// Net target not allowed.
751	Target {
752		/// Name of the net target.
753		name: String,
754	},
755}
756
757/// Configuration failure reason for [`ErrorKind::Configuration`] errors.
758#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
759#[surreal(crate = "crate")]
760#[surreal(tag = "kind", skip_content)]
761#[non_exhaustive]
762pub enum ConfigurationError {
763	/// Live query not supported.
764	LiveQueryNotSupported,
765	/// Bad live query config.
766	BadLiveQueryConfig,
767	/// Bad GraphQL config.
768	BadGraphqlConfig,
769}
770
771/// Serialisation failure reason for [`ErrorKind::Serialization`] errors.
772#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
773#[surreal(crate = "crate")]
774#[surreal(tag = "kind", skip_content)]
775#[non_exhaustive]
776pub enum SerializationError {
777	/// Serialisation error.
778	Serialization,
779	/// Deserialisation error.
780	Deserialization,
781}
782
783/// Not-found reason for [`ErrorKind::NotFound`] errors.
784#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
785#[surreal(crate = "crate")]
786#[surreal(tag = "kind", content = "details")]
787#[non_exhaustive]
788pub enum NotFoundError {
789	/// RPC method not found.
790	Method {
791		/// Name of the method.
792		name: String,
793	},
794	/// Session not found.
795	Session {
796		/// Optional session ID that was not found.
797		id: Option<String>,
798	},
799	/// Table not found.
800	Table {
801		/// Name of the table.
802		name: String,
803	},
804	/// Record not found.
805	Record {
806		/// ID of the record.
807		id: String,
808	},
809	/// Namespace not found.
810	Namespace {
811		/// Name of the namespace.
812		name: String,
813	},
814	/// Database not found.
815	Database {
816		/// Name of the database.
817		name: String,
818	},
819	/// Transaction not found.
820	#[surreal(skip_content)]
821	Transaction,
822}
823
824/// Query failure reason for [`ErrorKind::Query`] errors.
825#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
826#[surreal(crate = "crate")]
827#[surreal(tag = "kind", content = "details")]
828#[non_exhaustive]
829pub enum QueryError {
830	/// Query was not executed.
831	#[surreal(skip_content)]
832	NotExecuted,
833	/// Query timed out.
834	TimedOut {
835		/// Duration after which the query timed out.
836		duration: Duration,
837	},
838	/// Query was cancelled.
839	#[surreal(skip_content)]
840	Cancelled,
841}
842
843/// Already-exists reason for [`ErrorKind::AlreadyExists`] errors.
844#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
845#[surreal(crate = "crate")]
846#[surreal(tag = "kind", content = "details")]
847#[non_exhaustive]
848pub enum AlreadyExistsError {
849	/// Session already exists.
850	Session {
851		/// Optional session ID that already exists.
852		id: String,
853	},
854	/// Table already exists.
855	Table {
856		/// Name of the table.
857		name: String,
858	},
859	/// Record already exists.
860	Record {
861		/// ID of the record.
862		id: String,
863	},
864	/// Namespace already exists.
865	Namespace {
866		/// Name of the namespace.
867		name: String,
868	},
869	/// Database already exists.
870	Database {
871		/// Name of the database.
872		name: String,
873	},
874}
875
876/// Connection failure reason for [`ErrorKind::Connection`] errors.
877/// Used in the SDK for client-side connection state errors.
878#[derive(Clone, Debug, PartialEq, Eq, SurrealValue)]
879#[surreal(crate = "crate")]
880#[surreal(tag = "kind", skip_content)]
881#[non_exhaustive]
882pub enum ConnectionError {
883	/// Connection was used before being initialised.
884	Uninitialised,
885	/// Connect was called on an instance that is already connected.
886	AlreadyConnected,
887}
888
889impl fmt::Display for Error {
890	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
891		write!(f, "{}", self.message)
892	}
893}
894
895impl std::error::Error for Error {
896	fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
897		self.cause.as_deref().map(|e| e as &(dyn std::error::Error + 'static))
898	}
899}
900
901// -----------------------------------------------------------------------------
902// Type conversion errors (internal to the types layer)
903// -----------------------------------------------------------------------------
904
905/// Errors that can occur when working with SurrealDB types
906#[derive(Debug, Clone)]
907#[non_exhaustive]
908pub enum TypeError {
909	/// Failed to convert between types
910	Conversion(ConversionError),
911	/// Value is out of range for the target type
912	OutOfRange(OutOfRangeError),
913	/// Array or tuple length mismatch
914	LengthMismatch(LengthMismatchError),
915	/// Invalid format or structure
916	Invalid(String),
917}
918
919/// Error when converting between types
920#[derive(Debug, Clone)]
921#[non_exhaustive]
922pub struct ConversionError {
923	/// The expected kind
924	pub expected: Kind,
925	/// The actual kind that was received
926	pub actual: Kind,
927	/// Optional context about what was being converted
928	pub context: Option<String>,
929}
930
931/// Error when a value is out of range for the target type
932#[derive(Debug, Clone)]
933#[non_exhaustive]
934pub struct OutOfRangeError {
935	/// The value that was out of range
936	pub value: String,
937	/// The target type name
938	pub target_type: String,
939	/// Optional additional context
940	pub context: Option<String>,
941}
942
943/// Error when an array or tuple has the wrong length
944#[derive(Debug, Clone)]
945#[non_exhaustive]
946pub struct LengthMismatchError {
947	/// The expected length
948	pub expected: usize,
949	/// The actual length received
950	pub actual: usize,
951	/// The target type name
952	pub target_type: String,
953}
954
955impl ConversionError {
956	/// Create a new conversion error
957	pub fn new(expected: Kind, actual: Kind) -> Self {
958		Self {
959			expected,
960			actual,
961			context: None,
962		}
963	}
964
965	/// Create a conversion error from a value
966	pub fn from_value(expected: Kind, value: &Value) -> Self {
967		Self {
968			expected,
969			actual: value.kind(),
970			context: None,
971		}
972	}
973
974	/// Add context to the error
975	pub fn with_context(mut self, context: impl Into<String>) -> Self {
976		self.context = Some(context.into());
977		self
978	}
979}
980
981impl OutOfRangeError {
982	/// Create a new out of range error
983	pub fn new(value: impl fmt::Display, target_type: impl Into<String>) -> Self {
984		Self {
985			value: value.to_string(),
986			target_type: target_type.into(),
987			context: None,
988		}
989	}
990
991	/// Add context to the error
992	pub fn with_context(mut self, context: impl Into<String>) -> Self {
993		self.context = Some(context.into());
994		self
995	}
996}
997
998impl LengthMismatchError {
999	/// Create a new length mismatch error
1000	pub fn new(expected: usize, actual: usize, target_type: impl Into<String>) -> Self {
1001		Self {
1002			expected,
1003			actual,
1004			target_type: target_type.into(),
1005		}
1006	}
1007}
1008
1009impl fmt::Display for TypeError {
1010	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1011		match self {
1012			TypeError::Conversion(e) => write!(f, "{e}"),
1013			TypeError::OutOfRange(e) => write!(f, "{e}"),
1014			TypeError::LengthMismatch(e) => write!(f, "{e}"),
1015			TypeError::Invalid(e) => write!(f, "Invalid: {e}"),
1016		}
1017	}
1018}
1019
1020impl fmt::Display for ConversionError {
1021	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1022		write!(f, "Expected {}, got {}", self.expected.to_sql(), self.actual.to_sql())?;
1023		if let Some(context) = &self.context {
1024			write!(f, " ({})", context)?;
1025		}
1026		Ok(())
1027	}
1028}
1029
1030impl fmt::Display for OutOfRangeError {
1031	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1032		write!(f, "Value {} is out of range for type {}", self.value, self.target_type)?;
1033		if let Some(context) = &self.context {
1034			write!(f, " ({})", context)?;
1035		}
1036		Ok(())
1037	}
1038}
1039
1040impl fmt::Display for LengthMismatchError {
1041	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1042		write!(
1043			f,
1044			"Length mismatch for {}: expected {}, got {}",
1045			self.target_type, self.expected, self.actual
1046		)
1047	}
1048}
1049
1050impl std::error::Error for TypeError {}
1051impl std::error::Error for ConversionError {}
1052impl std::error::Error for OutOfRangeError {}
1053impl std::error::Error for LengthMismatchError {}
1054
1055impl From<ConversionError> for Error {
1056	fn from(e: ConversionError) -> Self {
1057		Error::internal(e.to_string())
1058	}
1059}
1060
1061impl From<OutOfRangeError> for Error {
1062	fn from(e: OutOfRangeError) -> Self {
1063		Error::internal(e.to_string())
1064	}
1065}
1066
1067impl From<LengthMismatchError> for Error {
1068	fn from(e: LengthMismatchError) -> Self {
1069		Error::internal(e.to_string())
1070	}
1071}
1072
1073impl From<TypeError> for Error {
1074	fn from(e: TypeError) -> Self {
1075		Error::internal(e.to_string())
1076	}
1077}
1078
1079/// Helper function to create a conversion error
1080pub fn conversion_error(expected: Kind, value: impl Into<Value>) -> Error {
1081	let value = value.into();
1082	ConversionError::from_value(expected, &value).into()
1083}
1084
1085/// Helper function to create an out of range error
1086pub fn out_of_range_error(value: impl fmt::Display, target_type: impl Into<String>) -> Error {
1087	OutOfRangeError::new(value, target_type).into()
1088}
1089
1090/// Helper function to create a length mismatch error
1091pub fn length_mismatch_error(
1092	expected: usize,
1093	actual: usize,
1094	target_type: impl Into<String>,
1095) -> Error {
1096	LengthMismatchError::new(expected, actual, target_type).into()
1097}
1098
1099/// Helper function to create a conversion error for union types (Either)
1100/// where the value doesn't match any of the possible types
1101pub fn union_conversion_error(expected: Kind, value: impl Into<Value>) -> Error {
1102	let value = value.into();
1103	ConversionError::from_value(expected, &value)
1104		.with_context("Value does not match any variant in union type")
1105		.into()
1106}