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