Skip to main content

reifydb_type/error/
mod.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	fmt::{Display, Formatter},
6	ops::{Deref, DerefMut},
7};
8
9use serde::{Deserialize, Serialize, de, ser};
10
11mod diagnostic;
12pub mod r#macro;
13pub mod render;
14pub mod util;
15
16use std::{array::TryFromSliceError, error, fmt, mem, num, string};
17
18use render::DefaultRenderer;
19
20use crate::{fragment::Fragment, value::r#type::Type};
21
22/// Entry in the operator call chain for flow operator errors
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct OperatorChainEntry {
25	pub node_id: u64,
26	pub operator_name: String,
27	pub operator_version: String,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct Diagnostic {
32	pub code: String,
33	pub statement: Option<String>,
34	pub message: String,
35	pub column: Option<DiagnosticColumn>,
36	pub fragment: Fragment,
37	pub label: Option<String>,
38	pub help: Option<String>,
39	pub notes: Vec<String>,
40	pub cause: Option<Box<Diagnostic>>,
41	/// Operator call chain when error occurred (for flow operator errors)
42	pub operator_chain: Option<Vec<OperatorChainEntry>>,
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct DiagnosticColumn {
47	pub name: String,
48	pub r#type: Type,
49}
50
51impl Default for Diagnostic {
52	fn default() -> Self {
53		Self {
54			code: String::new(),
55			statement: None,
56			message: String::new(),
57			column: None,
58			fragment: Fragment::None,
59			label: None,
60			help: None,
61			notes: Vec::new(),
62			cause: None,
63			operator_chain: None,
64		}
65	}
66}
67
68impl Display for Diagnostic {
69	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
70		f.write_fmt(format_args!("{}", self.code))
71	}
72}
73
74impl Diagnostic {
75	/// Set the statement for this diagnostic and all nested diagnostics
76	/// recursively
77	pub fn with_statement(&mut self, statement: String) {
78		self.statement = Some(statement.clone());
79
80		// Recursively set statement for all nested diagnostics
81		if let Some(ref mut cause) = self.cause {
82			let mut updated_cause = mem::replace(cause.as_mut(), Diagnostic::default());
83			updated_cause.with_statement(statement);
84			*cause = Box::new(updated_cause);
85		}
86	}
87
88	/// Set or update the fragment for this diagnostic and all nested
89	/// diagnostics recursively
90	pub fn with_fragment(&mut self, new_fragment: Fragment) {
91		// Always update the fragment, not just when it's None
92		// This is needed for cast errors that need to update the
93		// fragment
94		self.fragment = new_fragment;
95
96		if let Some(ref mut cause) = self.cause {
97			cause.with_fragment(self.fragment.clone());
98		}
99	}
100
101	/// Get the fragment if this is a Statement fragment (for backward
102	/// compatibility)
103	pub fn fragment(&self) -> Option<Fragment> {
104		match &self.fragment {
105			Fragment::Statement {
106				..
107			} => Some(self.fragment.clone()),
108			_ => None,
109		}
110	}
111}
112
113/// Trait for converting error types into Diagnostic.
114///
115/// Implement this trait to provide rich diagnostic information for custom error types.
116/// The trait consumes the error (takes `self` by value) to allow moving owned data
117/// into the diagnostic.
118pub trait IntoDiagnostic {
119	/// Convert self into a Diagnostic with error code, message, fragment, and other metadata.
120	fn into_diagnostic(self) -> Diagnostic;
121}
122
123#[derive(Debug, Clone, PartialEq)]
124pub enum UnaryOp {
125	Not,
126}
127
128impl Display for UnaryOp {
129	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
130		match self {
131			UnaryOp::Not => f.write_str("NOT"),
132		}
133	}
134}
135
136#[derive(Debug, Clone, PartialEq)]
137pub enum BinaryOp {
138	Add,
139	Sub,
140	Mul,
141	Div,
142	Rem,
143	Equal,
144	NotEqual,
145	LessThan,
146	LessThanEqual,
147	GreaterThan,
148	GreaterThanEqual,
149	Between,
150}
151
152impl BinaryOp {
153	pub fn symbol(&self) -> &'static str {
154		match self {
155			BinaryOp::Add => "+",
156			BinaryOp::Sub => "-",
157			BinaryOp::Mul => "*",
158			BinaryOp::Div => "/",
159			BinaryOp::Rem => "%",
160			BinaryOp::Equal => "==",
161			BinaryOp::NotEqual => "!=",
162			BinaryOp::LessThan => "<",
163			BinaryOp::LessThanEqual => "<=",
164			BinaryOp::GreaterThan => ">",
165			BinaryOp::GreaterThanEqual => ">=",
166			BinaryOp::Between => "BETWEEN",
167		}
168	}
169}
170
171impl Display for BinaryOp {
172	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
173		f.write_str(self.symbol())
174	}
175}
176
177#[derive(Debug, Clone, PartialEq)]
178pub enum LogicalOp {
179	Not,
180	And,
181	Or,
182	Xor,
183}
184
185impl Display for LogicalOp {
186	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
187		match self {
188			LogicalOp::Not => f.write_str("NOT"),
189			LogicalOp::And => f.write_str("AND"),
190			LogicalOp::Or => f.write_str("OR"),
191			LogicalOp::Xor => f.write_str("XOR"),
192		}
193	}
194}
195
196#[derive(Debug, Clone, PartialEq)]
197pub enum OperandCategory {
198	Number,
199	Text,
200	Temporal,
201	Uuid,
202}
203
204impl Display for OperandCategory {
205	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
206		match self {
207			OperandCategory::Number => f.write_str("number"),
208			OperandCategory::Text => f.write_str("text"),
209			OperandCategory::Temporal => f.write_str("temporal value"),
210			OperandCategory::Uuid => f.write_str("UUID"),
211		}
212	}
213}
214
215#[derive(Debug, Clone, PartialEq)]
216pub enum ConstraintKind {
217	Utf8MaxBytes {
218		actual: usize,
219		max: usize,
220	},
221	BlobMaxBytes {
222		actual: usize,
223		max: usize,
224	},
225	IntMaxBytes {
226		actual: usize,
227		max: usize,
228	},
229	UintMaxBytes {
230		actual: usize,
231		max: usize,
232	},
233	DecimalPrecision {
234		actual: u8,
235		max: u8,
236	},
237	DecimalScale {
238		actual: u8,
239		max: u8,
240	},
241	NoneNotAllowed {
242		column_type: Type,
243	},
244}
245
246#[derive(Debug, Clone, PartialEq)]
247pub enum TemporalKind {
248	InvalidDateFormat,
249	InvalidDateTimeFormat,
250	InvalidTimeFormat,
251	InvalidDurationFormat,
252	InvalidYear,
253	InvalidTimeComponentFormat {
254		component: String,
255	},
256	InvalidMonth,
257	InvalidDay,
258	InvalidHour,
259	InvalidMinute,
260	InvalidSecond,
261	InvalidFractionalSeconds,
262	InvalidDateValues,
263	InvalidTimeValues,
264	InvalidDurationCharacter,
265	IncompleteDurationSpecification,
266	InvalidUnitInContext {
267		unit: char,
268		in_time_part: bool,
269	},
270	InvalidDurationComponentValue {
271		unit: char,
272	},
273	UnrecognizedTemporalPattern,
274	EmptyDateComponent,
275	EmptyTimeComponent,
276	DuplicateDurationComponent {
277		component: char,
278	},
279	OutOfOrderDurationComponent {
280		component: char,
281	},
282}
283
284#[derive(Debug, Clone, PartialEq)]
285pub enum BlobEncodingKind {
286	InvalidHex,
287	InvalidBase64,
288	InvalidBase64Url,
289	InvalidBase58,
290	InvalidUtf8Sequence {
291		error: String,
292	},
293}
294
295#[derive(Debug, Clone, PartialEq)]
296pub enum AstErrorKind {
297	TokenizeError {
298		message: String,
299	},
300	UnexpectedEof,
301	ExpectedIdentifier,
302	InvalidColumnProperty,
303	InvalidPolicy,
304	UnexpectedToken {
305		expected: String,
306	},
307	UnsupportedToken,
308	MultipleExpressionsWithoutBraces,
309	UnrecognizedType,
310	UnsupportedAstNode {
311		node_type: String,
312	},
313	EmptyPipeline,
314}
315
316#[derive(Debug, Clone, PartialEq)]
317pub enum ProcedureErrorKind {
318	UndefinedProcedure {
319		name: String,
320	},
321}
322
323#[derive(Debug, Clone, PartialEq)]
324pub enum RuntimeErrorKind {
325	VariableNotFound {
326		name: String,
327	},
328	VariableIsDataframe {
329		name: String,
330	},
331	VariableIsImmutable {
332		name: String,
333	},
334	BreakOutsideLoop,
335	ContinueOutsideLoop,
336	MaxIterationsExceeded {
337		limit: usize,
338	},
339	UndefinedFunction {
340		name: String,
341	},
342	FieldNotFound {
343		variable: String,
344		field: String,
345		available: Vec<String>,
346	},
347	AppendTargetNotFrame {
348		name: String,
349	},
350}
351
352#[derive(Debug, Clone, PartialEq)]
353pub enum NetworkErrorKind {
354	Connection {
355		message: String,
356	},
357	Engine {
358		message: String,
359	},
360	Transport {
361		message: String,
362	},
363	Status {
364		message: String,
365	},
366}
367
368#[derive(Debug, Clone, PartialEq)]
369pub enum AuthErrorKind {
370	AuthenticationFailed {
371		reason: String,
372	},
373	AuthorizationDenied {
374		resource: String,
375	},
376	TokenExpired,
377	InvalidToken,
378}
379
380#[derive(Debug, Clone, PartialEq)]
381pub enum FunctionErrorKind {
382	UnknownFunction,
383	ArityMismatch {
384		expected: usize,
385		actual: usize,
386	},
387	TooManyArguments {
388		max_args: usize,
389		actual: usize,
390	},
391	InvalidArgumentType {
392		index: usize,
393		expected: Vec<Type>,
394		actual: Type,
395	},
396	UndefinedArgument {
397		index: usize,
398	},
399	MissingInput,
400	ExecutionFailed {
401		reason: String,
402	},
403	InternalError {
404		details: String,
405	},
406	GeneratorNotFound,
407}
408
409#[derive(Debug, thiserror::Error)]
410pub enum TypeError {
411	#[error("Cannot apply {operator} operator to {operand_category}")]
412	LogicalOperatorNotApplicable {
413		operator: LogicalOp,
414		operand_category: OperandCategory,
415		fragment: Fragment,
416	},
417
418	#[error("Cannot apply '{operator}' operator to {left} and {right}")]
419	BinaryOperatorNotApplicable {
420		operator: BinaryOp,
421		left: Type,
422		right: Type,
423		fragment: Fragment,
424	},
425
426	#[error("unsupported cast from {from} to {to}")]
427	UnsupportedCast {
428		from: Type,
429		to: Type,
430		fragment: Fragment,
431	},
432
433	#[error("failed to cast to {target}")]
434	CastToNumberFailed {
435		target: Type,
436		fragment: Fragment,
437		cause: Box<TypeError>,
438	},
439
440	#[error("failed to cast to {target}")]
441	CastToTemporalFailed {
442		target: Type,
443		fragment: Fragment,
444		cause: Box<TypeError>,
445	},
446
447	#[error("failed to cast to bool")]
448	CastToBooleanFailed {
449		fragment: Fragment,
450		cause: Box<TypeError>,
451	},
452
453	#[error("failed to cast to {target}")]
454	CastToUuidFailed {
455		target: Type,
456		fragment: Fragment,
457		cause: Box<TypeError>,
458	},
459
460	#[error("failed to cast BLOB to UTF8")]
461	CastBlobToUtf8Failed {
462		fragment: Fragment,
463		cause: Box<TypeError>,
464	},
465
466	#[error("{message}")]
467	ConstraintViolation {
468		kind: ConstraintKind,
469		message: String,
470		fragment: Fragment,
471	},
472
473	#[error("invalid number format")]
474	InvalidNumberFormat {
475		target: Type,
476		fragment: Fragment,
477	},
478
479	#[error("number out of range")]
480	NumberOutOfRange {
481		target: Type,
482		fragment: Fragment,
483		descriptor: Option<NumberOutOfRangeDescriptor>,
484	},
485
486	#[error("NaN not allowed")]
487	NanNotAllowed,
488
489	#[error("too large for precise float conversion")]
490	IntegerPrecisionLoss {
491		source_type: Type,
492		target: Type,
493		fragment: Fragment,
494	},
495
496	#[error("decimal scale exceeds precision")]
497	DecimalScaleExceedsPrecision {
498		scale: u8,
499		precision: u8,
500		fragment: Fragment,
501	},
502
503	#[error("invalid decimal precision")]
504	DecimalPrecisionInvalid {
505		precision: u8,
506	},
507
508	#[error("invalid boolean format")]
509	InvalidBooleanFormat {
510		fragment: Fragment,
511	},
512
513	#[error("empty boolean value")]
514	EmptyBooleanValue {
515		fragment: Fragment,
516	},
517
518	#[error("invalid boolean")]
519	InvalidNumberBoolean {
520		fragment: Fragment,
521	},
522
523	#[error("{message}")]
524	Temporal {
525		kind: TemporalKind,
526		message: String,
527		fragment: Fragment,
528	},
529
530	#[error("invalid UUID v4 format")]
531	InvalidUuid4Format {
532		fragment: Fragment,
533	},
534
535	#[error("invalid UUID v7 format")]
536	InvalidUuid7Format {
537		fragment: Fragment,
538	},
539
540	#[error("{message}")]
541	BlobEncoding {
542		kind: BlobEncodingKind,
543		message: String,
544		fragment: Fragment,
545	},
546
547	#[error("Serde deserialization error: {message}")]
548	SerdeDeserialize {
549		message: String,
550	},
551
552	#[error("Serde serialization error: {message}")]
553	SerdeSerialize {
554		message: String,
555	},
556
557	#[error("Keycode serialization error: {message}")]
558	SerdeKeycode {
559		message: String,
560	},
561
562	#[error("Array conversion error: {message}")]
563	ArrayConversion {
564		message: String,
565	},
566
567	#[error("UTF-8 conversion error: {message}")]
568	Utf8Conversion {
569		message: String,
570	},
571
572	#[error("Integer conversion error: {message}")]
573	IntegerConversion {
574		message: String,
575	},
576
577	#[error("{message}")]
578	Network {
579		kind: NetworkErrorKind,
580		message: String,
581	},
582
583	#[error("{message}")]
584	Auth {
585		kind: AuthErrorKind,
586		message: String,
587	},
588
589	#[error("dictionary entry ID {value} exceeds maximum {max_value} for type {id_type}")]
590	DictionaryCapacityExceeded {
591		id_type: Type,
592		value: u128,
593		max_value: u128,
594	},
595
596	#[error("{message}")]
597	AssertionFailed {
598		fragment: Fragment,
599		message: String,
600		expression: Option<String>,
601	},
602
603	#[error("{message}")]
604	Function {
605		kind: FunctionErrorKind,
606		message: String,
607		fragment: Fragment,
608	},
609
610	#[error("{message}")]
611	Ast {
612		kind: AstErrorKind,
613		message: String,
614		fragment: Fragment,
615	},
616
617	#[error("{message}")]
618	Runtime {
619		kind: RuntimeErrorKind,
620		message: String,
621	},
622
623	#[error("{message}")]
624	Procedure {
625		kind: ProcedureErrorKind,
626		message: String,
627		fragment: Fragment,
628	},
629}
630
631#[derive(Debug, Clone, PartialEq)]
632pub struct NumberOutOfRangeDescriptor {
633	pub namespace: Option<String>,
634	pub table: Option<String>,
635	pub column: Option<String>,
636	pub column_type: Option<Type>,
637}
638
639impl NumberOutOfRangeDescriptor {
640	pub fn location_string(&self) -> String {
641		match (self.namespace.as_deref(), self.table.as_deref(), self.column.as_deref()) {
642			(Some(s), Some(t), Some(c)) => format!("{}::{}.{}", s, t, c),
643			(Some(s), Some(t), None) => format!("{}::{}", s, t),
644			(None, Some(t), Some(c)) => format!("{}.{}", t, c),
645			(Some(s), None, Some(c)) => format!("{}::{}", s, c),
646			(Some(s), None, None) => s.to_string(),
647			(None, Some(t), None) => t.to_string(),
648			(None, None, Some(c)) => c.to_string(),
649			(None, None, None) => "unknown location".to_string(),
650		}
651	}
652}
653
654#[derive(Debug, PartialEq)]
655pub struct Error(pub Diagnostic);
656
657impl Deref for Error {
658	type Target = Diagnostic;
659
660	fn deref(&self) -> &Self::Target {
661		&self.0
662	}
663}
664
665impl DerefMut for Error {
666	fn deref_mut(&mut self) -> &mut Self::Target {
667		&mut self.0
668	}
669}
670
671impl Display for Error {
672	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
673		let out = DefaultRenderer::render_string(&self.0);
674		f.write_str(out.as_str())
675	}
676}
677
678impl Error {
679	pub fn diagnostic(self) -> Diagnostic {
680		self.0
681	}
682}
683
684impl error::Error for Error {}
685
686impl de::Error for Error {
687	fn custom<T: Display>(msg: T) -> Self {
688		TypeError::SerdeDeserialize {
689			message: msg.to_string(),
690		}
691		.into()
692	}
693}
694
695impl ser::Error for Error {
696	fn custom<T: Display>(msg: T) -> Self {
697		TypeError::SerdeSerialize {
698			message: msg.to_string(),
699		}
700		.into()
701	}
702}
703
704impl From<num::TryFromIntError> for Error {
705	fn from(err: num::TryFromIntError) -> Self {
706		TypeError::IntegerConversion {
707			message: err.to_string(),
708		}
709		.into()
710	}
711}
712
713impl From<TryFromSliceError> for Error {
714	fn from(err: TryFromSliceError) -> Self {
715		TypeError::ArrayConversion {
716			message: err.to_string(),
717		}
718		.into()
719	}
720}
721
722impl From<string::FromUtf8Error> for Error {
723	fn from(err: string::FromUtf8Error) -> Self {
724		TypeError::Utf8Conversion {
725			message: err.to_string(),
726		}
727		.into()
728	}
729}
730
731impl From<TypeError> for Error {
732	fn from(err: TypeError) -> Self {
733		Error(err.into_diagnostic())
734	}
735}
736
737impl From<num::TryFromIntError> for TypeError {
738	fn from(err: num::TryFromIntError) -> Self {
739		TypeError::IntegerConversion {
740			message: err.to_string(),
741		}
742	}
743}
744
745impl From<TryFromSliceError> for TypeError {
746	fn from(err: TryFromSliceError) -> Self {
747		TypeError::ArrayConversion {
748			message: err.to_string(),
749		}
750	}
751}
752
753impl From<string::FromUtf8Error> for TypeError {
754	fn from(err: string::FromUtf8Error) -> Self {
755		TypeError::Utf8Conversion {
756			message: err.to_string(),
757		}
758	}
759}
760
761impl de::Error for TypeError {
762	fn custom<T: Display>(msg: T) -> Self {
763		TypeError::SerdeDeserialize {
764			message: msg.to_string(),
765		}
766	}
767}
768
769impl ser::Error for TypeError {
770	fn custom<T: Display>(msg: T) -> Self {
771		TypeError::SerdeSerialize {
772			message: msg.to_string(),
773		}
774	}
775}