Skip to main content

reifydb_value/error/
mod.rs

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