Skip to main content

reifydb_type/error/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
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	DateTimeOutOfRange,
283	DateTimeOverflow {
284		message: String,
285	},
286	DurationOverflow {
287		message: String,
288	},
289	DurationMixedSign {
290		days: i32,
291		nanos: i64,
292	},
293	TimeOverflow {
294		message: String,
295	},
296	DateOverflow {
297		message: String,
298	},
299}
300
301#[derive(Debug, Clone, PartialEq)]
302pub enum BlobEncodingKind {
303	InvalidHex,
304	InvalidBase64,
305	InvalidBase64Url,
306	InvalidBase58,
307	InvalidUtf8Sequence {
308		error: String,
309	},
310}
311
312#[derive(Debug, Clone, PartialEq)]
313pub enum AstErrorKind {
314	TokenizeError {
315		message: String,
316	},
317	UnexpectedEof,
318	ExpectedIdentifier,
319	InvalidColumnProperty,
320	InvalidPolicy,
321	UnexpectedToken {
322		expected: String,
323	},
324	UnsupportedToken,
325	MultipleExpressionsWithoutBraces,
326	UnrecognizedType,
327	UnsupportedAstNode {
328		node_type: String,
329	},
330	EmptyPipeline,
331}
332
333#[derive(Debug, Clone, PartialEq)]
334pub enum ProcedureErrorKind {
335	UndefinedProcedure {
336		name: String,
337	},
338}
339
340#[derive(Debug, Clone, PartialEq)]
341pub enum RuntimeErrorKind {
342	VariableNotFound {
343		name: String,
344	},
345	VariableIsDataframe {
346		name: String,
347	},
348	VariableIsImmutable {
349		name: String,
350	},
351	BreakOutsideLoop,
352	ContinueOutsideLoop,
353	MaxIterationsExceeded {
354		limit: usize,
355	},
356	UndefinedFunction {
357		name: String,
358	},
359	FieldNotFound {
360		variable: String,
361		field: String,
362		available: Vec<String>,
363	},
364	AppendTargetNotFrame {
365		name: String,
366	},
367}
368
369#[derive(Debug, Clone, PartialEq)]
370pub enum NetworkErrorKind {
371	Connection {
372		message: String,
373	},
374	Engine {
375		message: String,
376	},
377	Transport {
378		message: String,
379	},
380	Status {
381		message: String,
382	},
383}
384
385#[derive(Debug, Clone, PartialEq)]
386pub enum AuthErrorKind {
387	AuthenticationFailed {
388		reason: String,
389	},
390	AuthorizationDenied {
391		resource: String,
392	},
393	TokenExpired,
394	InvalidToken,
395}
396
397#[derive(Debug, Clone, PartialEq)]
398pub enum FunctionErrorKind {
399	UnknownFunction,
400	ArityMismatch {
401		expected: usize,
402		actual: usize,
403	},
404	TooManyArguments {
405		max_args: usize,
406		actual: usize,
407	},
408	InvalidArgumentType {
409		index: usize,
410		expected: Vec<Type>,
411		actual: Type,
412	},
413	UndefinedArgument {
414		index: usize,
415	},
416	MissingInput,
417	ExecutionFailed {
418		reason: String,
419	},
420	InternalError {
421		details: String,
422	},
423	GeneratorNotFound,
424}
425
426#[derive(Debug, thiserror::Error)]
427pub enum TypeError {
428	#[error("Cannot apply {operator} operator to {operand_category}")]
429	LogicalOperatorNotApplicable {
430		operator: LogicalOp,
431		operand_category: OperandCategory,
432		fragment: Fragment,
433	},
434
435	#[error("Cannot apply '{operator}' operator to {left} and {right}")]
436	BinaryOperatorNotApplicable {
437		operator: BinaryOp,
438		left: Type,
439		right: Type,
440		fragment: Fragment,
441	},
442
443	#[error("unsupported cast from {from} to {to}")]
444	UnsupportedCast {
445		from: Type,
446		to: Type,
447		fragment: Fragment,
448	},
449
450	#[error("failed to cast to {target}")]
451	CastToNumberFailed {
452		target: Type,
453		fragment: Fragment,
454		cause: Box<TypeError>,
455	},
456
457	#[error("failed to cast to {target}")]
458	CastToTemporalFailed {
459		target: Type,
460		fragment: Fragment,
461		cause: Box<TypeError>,
462	},
463
464	#[error("failed to cast to bool")]
465	CastToBooleanFailed {
466		fragment: Fragment,
467		cause: Box<TypeError>,
468	},
469
470	#[error("failed to cast to {target}")]
471	CastToUuidFailed {
472		target: Type,
473		fragment: Fragment,
474		cause: Box<TypeError>,
475	},
476
477	#[error("failed to cast BLOB to UTF8")]
478	CastBlobToUtf8Failed {
479		fragment: Fragment,
480		cause: Box<TypeError>,
481	},
482
483	#[error("{message}")]
484	ConstraintViolation {
485		kind: ConstraintKind,
486		message: String,
487		fragment: Fragment,
488	},
489
490	#[error("invalid number format")]
491	InvalidNumberFormat {
492		target: Type,
493		fragment: Fragment,
494	},
495
496	#[error("number out of range")]
497	NumberOutOfRange {
498		target: Type,
499		fragment: Fragment,
500		descriptor: Option<NumberOutOfRangeDescriptor>,
501	},
502
503	#[error("NaN not allowed")]
504	NanNotAllowed,
505
506	#[error("too large for precise float conversion")]
507	IntegerPrecisionLoss {
508		source_type: Type,
509		target: Type,
510		fragment: Fragment,
511	},
512
513	#[error("decimal scale exceeds precision")]
514	DecimalScaleExceedsPrecision {
515		scale: u8,
516		precision: u8,
517		fragment: Fragment,
518	},
519
520	#[error("invalid decimal precision")]
521	DecimalPrecisionInvalid {
522		precision: u8,
523	},
524
525	#[error("invalid boolean format")]
526	InvalidBooleanFormat {
527		fragment: Fragment,
528	},
529
530	#[error("empty boolean value")]
531	EmptyBooleanValue {
532		fragment: Fragment,
533	},
534
535	#[error("invalid boolean")]
536	InvalidNumberBoolean {
537		fragment: Fragment,
538	},
539
540	#[error("{message}")]
541	Temporal {
542		kind: TemporalKind,
543		message: String,
544		fragment: Fragment,
545	},
546
547	#[error("invalid UUID v4 format")]
548	InvalidUuid4Format {
549		fragment: Fragment,
550	},
551
552	#[error("invalid UUID v7 format")]
553	InvalidUuid7Format {
554		fragment: Fragment,
555	},
556
557	#[error("{message}")]
558	BlobEncoding {
559		kind: BlobEncodingKind,
560		message: String,
561		fragment: Fragment,
562	},
563
564	#[error("Serde deserialization error: {message}")]
565	SerdeDeserialize {
566		message: String,
567	},
568
569	#[error("Serde serialization error: {message}")]
570	SerdeSerialize {
571		message: String,
572	},
573
574	#[error("Keycode serialization error: {message}")]
575	SerdeKeycode {
576		message: String,
577	},
578
579	#[error("Array conversion error: {message}")]
580	ArrayConversion {
581		message: String,
582	},
583
584	#[error("UTF-8 conversion error: {message}")]
585	Utf8Conversion {
586		message: String,
587	},
588
589	#[error("Integer conversion error: {message}")]
590	IntegerConversion {
591		message: String,
592	},
593
594	#[error("{message}")]
595	Network {
596		kind: NetworkErrorKind,
597		message: String,
598	},
599
600	#[error("{message}")]
601	Auth {
602		kind: AuthErrorKind,
603		message: String,
604	},
605
606	#[error("dictionary entry ID {value} exceeds maximum {max_value} for type {id_type}")]
607	DictionaryCapacityExceeded {
608		id_type: Type,
609		value: u128,
610		max_value: u128,
611	},
612
613	#[error("{message}")]
614	AssertionFailed {
615		fragment: Fragment,
616		message: String,
617		expression: Option<String>,
618	},
619
620	#[error("{message}")]
621	Function {
622		kind: FunctionErrorKind,
623		message: String,
624		fragment: Fragment,
625	},
626
627	#[error("{message}")]
628	Ast {
629		kind: AstErrorKind,
630		message: String,
631		fragment: Fragment,
632	},
633
634	#[error("{message}")]
635	Runtime {
636		kind: RuntimeErrorKind,
637		message: String,
638	},
639
640	#[error("{message}")]
641	Procedure {
642		kind: ProcedureErrorKind,
643		message: String,
644		fragment: Fragment,
645	},
646}
647
648#[derive(Debug, Clone, PartialEq)]
649pub struct NumberOutOfRangeDescriptor {
650	pub namespace: Option<String>,
651	pub table: Option<String>,
652	pub column: Option<String>,
653	pub column_type: Option<Type>,
654}
655
656impl NumberOutOfRangeDescriptor {
657	pub fn location_string(&self) -> String {
658		match (self.namespace.as_deref(), self.table.as_deref(), self.column.as_deref()) {
659			(Some(s), Some(t), Some(c)) => format!("{}::{}.{}", s, t, c),
660			(Some(s), Some(t), None) => format!("{}::{}", s, t),
661			(None, Some(t), Some(c)) => format!("{}.{}", t, c),
662			(Some(s), None, Some(c)) => format!("{}::{}", s, c),
663			(Some(s), None, None) => s.to_string(),
664			(None, Some(t), None) => t.to_string(),
665			(None, None, Some(c)) => c.to_string(),
666			(None, None, None) => "unknown location".to_string(),
667		}
668	}
669}
670
671#[derive(Debug, PartialEq)]
672pub struct Error(pub Diagnostic);
673
674impl Deref for Error {
675	type Target = Diagnostic;
676
677	fn deref(&self) -> &Self::Target {
678		&self.0
679	}
680}
681
682impl DerefMut for Error {
683	fn deref_mut(&mut self) -> &mut Self::Target {
684		&mut self.0
685	}
686}
687
688impl Display for Error {
689	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
690		let out = DefaultRenderer::render_string(&self.0);
691		f.write_str(out.as_str())
692	}
693}
694
695impl Error {
696	pub fn diagnostic(self) -> Diagnostic {
697		self.0
698	}
699}
700
701impl error::Error for Error {}
702
703impl de::Error for Error {
704	fn custom<T: Display>(msg: T) -> Self {
705		TypeError::SerdeDeserialize {
706			message: msg.to_string(),
707		}
708		.into()
709	}
710}
711
712impl ser::Error for Error {
713	fn custom<T: Display>(msg: T) -> Self {
714		TypeError::SerdeSerialize {
715			message: msg.to_string(),
716		}
717		.into()
718	}
719}
720
721impl From<num::TryFromIntError> for Error {
722	fn from(err: num::TryFromIntError) -> Self {
723		TypeError::IntegerConversion {
724			message: err.to_string(),
725		}
726		.into()
727	}
728}
729
730impl From<TryFromSliceError> for Error {
731	fn from(err: TryFromSliceError) -> Self {
732		TypeError::ArrayConversion {
733			message: err.to_string(),
734		}
735		.into()
736	}
737}
738
739impl From<string::FromUtf8Error> for Error {
740	fn from(err: string::FromUtf8Error) -> Self {
741		TypeError::Utf8Conversion {
742			message: err.to_string(),
743		}
744		.into()
745	}
746}
747
748impl From<TypeError> for Error {
749	fn from(err: TypeError) -> Self {
750		Error(err.into_diagnostic())
751	}
752}
753
754impl From<num::TryFromIntError> for TypeError {
755	fn from(err: num::TryFromIntError) -> Self {
756		TypeError::IntegerConversion {
757			message: err.to_string(),
758		}
759	}
760}
761
762impl From<TryFromSliceError> for TypeError {
763	fn from(err: TryFromSliceError) -> Self {
764		TypeError::ArrayConversion {
765			message: err.to_string(),
766		}
767	}
768}
769
770impl From<string::FromUtf8Error> for TypeError {
771	fn from(err: string::FromUtf8Error) -> Self {
772		TypeError::Utf8Conversion {
773			message: err.to_string(),
774		}
775	}
776}
777
778impl de::Error for TypeError {
779	fn custom<T: Display>(msg: T) -> Self {
780		TypeError::SerdeDeserialize {
781			message: msg.to_string(),
782		}
783	}
784}
785
786impl ser::Error for TypeError {
787	fn custom<T: Display>(msg: T) -> Self {
788		TypeError::SerdeSerialize {
789			message: msg.to_string(),
790		}
791	}
792}