Skip to main content

reifydb_type/error/
mod.rs

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