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