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}
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<ValueType>,
411		actual: ValueType,
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: ValueType,
439		right: ValueType,
440		fragment: Fragment,
441	},
442
443	#[error("unsupported cast from {from} to {to}")]
444	UnsupportedCast {
445		from: ValueType,
446		to: ValueType,
447		fragment: Fragment,
448	},
449
450	#[error("failed to cast to {target}")]
451	CastToNumberFailed {
452		target: ValueType,
453		fragment: Fragment,
454		cause: Box<TypeError>,
455	},
456
457	#[error("failed to cast to {target}")]
458	CastToTemporalFailed {
459		target: ValueType,
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: ValueType,
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: ValueType,
493		fragment: Fragment,
494	},
495
496	#[error("number out of range")]
497	NumberOutOfRange {
498		target: ValueType,
499		fragment: Fragment,
500		descriptor: Option<NumberOutOfRangeDescriptor>,
501	},
502
503	#[error("division by zero")]
504	DivisionByZero {
505		target: ValueType,
506		fragment: Fragment,
507	},
508
509	#[error("NaN not allowed")]
510	NanNotAllowed,
511
512	#[error("too large for precise float conversion")]
513	IntegerPrecisionLoss {
514		object_type: ValueType,
515		target: ValueType,
516		fragment: Fragment,
517	},
518
519	#[error("decimal scale exceeds precision")]
520	DecimalScaleExceedsPrecision {
521		scale: u8,
522		precision: u8,
523		fragment: Fragment,
524	},
525
526	#[error("invalid decimal precision")]
527	DecimalPrecisionInvalid {
528		precision: u8,
529	},
530
531	#[error("invalid boolean format")]
532	InvalidBooleanFormat {
533		fragment: Fragment,
534	},
535
536	#[error("empty boolean value")]
537	EmptyBooleanValue {
538		fragment: Fragment,
539	},
540
541	#[error("invalid boolean")]
542	InvalidNumberBoolean {
543		fragment: Fragment,
544	},
545
546	#[error("{message}")]
547	Temporal {
548		kind: TemporalKind,
549		message: String,
550		fragment: Fragment,
551	},
552
553	#[error("invalid UUID v4 format")]
554	InvalidUuid4Format {
555		fragment: Fragment,
556	},
557
558	#[error("invalid UUID v7 format")]
559	InvalidUuid7Format {
560		fragment: Fragment,
561	},
562
563	#[error("{message}")]
564	BlobEncoding {
565		kind: BlobEncodingKind,
566		message: String,
567		fragment: Fragment,
568	},
569
570	#[error("Serde deserialization error: {message}")]
571	SerdeDeserialize {
572		message: String,
573	},
574
575	#[error("Serde serialization error: {message}")]
576	SerdeSerialize {
577		message: String,
578	},
579
580	#[error("Keycode serialization error: {message}")]
581	SerdeKeycode {
582		message: String,
583	},
584
585	#[error("Array conversion error: {message}")]
586	ArrayConversion {
587		message: String,
588	},
589
590	#[error("UTF-8 conversion error: {message}")]
591	Utf8Conversion {
592		message: String,
593	},
594
595	#[error("Integer conversion error: {message}")]
596	IntegerConversion {
597		message: String,
598	},
599
600	#[error("{message}")]
601	Network {
602		kind: NetworkErrorKind,
603		message: String,
604	},
605
606	#[error("{message}")]
607	Auth {
608		kind: AuthErrorKind,
609		message: String,
610	},
611
612	#[error("dictionary entry ID {value} exceeds maximum {max_value} for type {id_type}")]
613	DictionaryCapacityExceeded {
614		id_type: ValueType,
615		value: u128,
616		max_value: u128,
617	},
618
619	#[error("{message}")]
620	AssertionFailed {
621		fragment: Fragment,
622		message: String,
623		expression: Option<String>,
624	},
625
626	#[error("{message}")]
627	Function {
628		kind: FunctionErrorKind,
629		message: String,
630		fragment: Fragment,
631	},
632
633	#[error("{message}")]
634	Ast {
635		kind: AstErrorKind,
636		message: String,
637		fragment: Fragment,
638	},
639
640	#[error("{message}")]
641	Runtime {
642		kind: RuntimeErrorKind,
643		message: String,
644	},
645
646	#[error("{message}")]
647	Procedure {
648		kind: ProcedureErrorKind,
649		message: String,
650		fragment: Fragment,
651	},
652}
653
654#[derive(Debug, Clone, PartialEq)]
655pub struct NumberOutOfRangeDescriptor {
656	pub namespace: Option<String>,
657	pub table: Option<String>,
658	pub column: Option<String>,
659	pub column_type: Option<ValueType>,
660}
661
662impl NumberOutOfRangeDescriptor {
663	pub fn location_string(&self) -> String {
664		match (self.namespace.as_deref(), self.table.as_deref(), self.column.as_deref()) {
665			(Some(s), Some(t), Some(c)) => format!("{}::{}.{}", s, t, c),
666			(Some(s), Some(t), None) => format!("{}::{}", s, t),
667			(None, Some(t), Some(c)) => format!("{}.{}", t, c),
668			(Some(s), None, Some(c)) => format!("{}::{}", s, c),
669			(Some(s), None, None) => s.to_string(),
670			(None, Some(t), None) => t.to_string(),
671			(None, None, Some(c)) => c.to_string(),
672			(None, None, None) => "unknown location".to_string(),
673		}
674	}
675}
676
677#[derive(Debug, PartialEq)]
678pub struct Error(pub Box<Diagnostic>);
679
680impl Deref for Error {
681	type Target = Diagnostic;
682
683	fn deref(&self) -> &Self::Target {
684		&self.0
685	}
686}
687
688impl DerefMut for Error {
689	fn deref_mut(&mut self) -> &mut Self::Target {
690		&mut self.0
691	}
692}
693
694impl Display for Error {
695	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
696		let out = DefaultRenderer::render_string(&self.0);
697		f.write_str(out.as_str())
698	}
699}
700
701impl Error {
702	pub fn diagnostic(self) -> Diagnostic {
703		*self.0
704	}
705}
706
707impl error::Error for Error {}
708
709impl de::Error for Error {
710	fn custom<T: Display>(msg: T) -> Self {
711		TypeError::SerdeDeserialize {
712			message: msg.to_string(),
713		}
714		.into()
715	}
716}
717
718impl ser::Error for Error {
719	fn custom<T: Display>(msg: T) -> Self {
720		TypeError::SerdeSerialize {
721			message: msg.to_string(),
722		}
723		.into()
724	}
725}
726
727impl From<num::TryFromIntError> for Error {
728	fn from(err: num::TryFromIntError) -> Self {
729		TypeError::IntegerConversion {
730			message: err.to_string(),
731		}
732		.into()
733	}
734}
735
736impl From<TryFromSliceError> for Error {
737	fn from(err: TryFromSliceError) -> Self {
738		TypeError::ArrayConversion {
739			message: err.to_string(),
740		}
741		.into()
742	}
743}
744
745impl From<string::FromUtf8Error> for Error {
746	fn from(err: string::FromUtf8Error) -> Self {
747		TypeError::Utf8Conversion {
748			message: err.to_string(),
749		}
750		.into()
751	}
752}
753
754impl From<TypeError> for Error {
755	fn from(err: TypeError) -> Self {
756		Error(Box::new(err.into_diagnostic()))
757	}
758}
759
760impl From<SystemColumnsError> for Error {
761	fn from(err: SystemColumnsError) -> Self {
762		Error(Box::new(err.into_diagnostic()))
763	}
764}
765
766impl From<Box<TypeError>> for Error {
767	fn from(err: Box<TypeError>) -> Self {
768		Error(Box::new(err.into_diagnostic()))
769	}
770}
771
772impl From<num::TryFromIntError> for TypeError {
773	fn from(err: num::TryFromIntError) -> Self {
774		TypeError::IntegerConversion {
775			message: err.to_string(),
776		}
777	}
778}
779
780impl From<TryFromSliceError> for TypeError {
781	fn from(err: TryFromSliceError) -> Self {
782		TypeError::ArrayConversion {
783			message: err.to_string(),
784		}
785	}
786}
787
788impl From<string::FromUtf8Error> for TypeError {
789	fn from(err: string::FromUtf8Error) -> Self {
790		TypeError::Utf8Conversion {
791			message: err.to_string(),
792		}
793	}
794}
795
796impl de::Error for TypeError {
797	fn custom<T: Display>(msg: T) -> Self {
798		TypeError::SerdeDeserialize {
799			message: msg.to_string(),
800		}
801	}
802}
803
804impl ser::Error for TypeError {
805	fn custom<T: Display>(msg: T) -> Self {
806		TypeError::SerdeSerialize {
807			message: msg.to_string(),
808		}
809	}
810}