Skip to main content

reifydb_routine/routine/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_catalog::error::CatalogError;
5use reifydb_type::{
6	error::{Diagnostic, Error, IntoDiagnostic, TypeError},
7	fragment::Fragment,
8	value::r#type::Type,
9};
10
11#[derive(Debug, thiserror::Error)]
12pub enum RoutineError {
13	#[error("function {} expects {expected} arguments, got {actual}", function.text())]
14	FunctionArityMismatch {
15		function: Fragment,
16		expected: usize,
17		actual: usize,
18	},
19
20	#[error("function {} argument {} has invalid type: got {actual:?}", function.text(), argument_index + 1)]
21	FunctionInvalidArgumentType {
22		function: Fragment,
23		argument_index: usize,
24		expected: Vec<Type>,
25		actual: Type,
26	},
27
28	#[error("function {} execution failed: {reason}", function.text())]
29	FunctionExecutionFailed {
30		function: Fragment,
31		reason: String,
32	},
33
34	#[error("generator function '{}' not found", function.text())]
35	FunctionNotFound {
36		function: Fragment,
37	},
38
39	#[error("procedure {} expects {expected} arguments, got {actual}", procedure.text())]
40	ProcedureArityMismatch {
41		procedure: Fragment,
42		expected: usize,
43		actual: usize,
44	},
45
46	#[error("procedure {} argument {} has invalid type: got {actual:?}", procedure.text(), argument_index + 1)]
47	ProcedureInvalidArgumentType {
48		procedure: Fragment,
49		argument_index: usize,
50		expected: Vec<Type>,
51		actual: Type,
52	},
53
54	#[error("procedure {} execution failed: {reason}", procedure.text())]
55	ProcedureExecutionFailed {
56		procedure: Fragment,
57		reason: String,
58	},
59
60	#[error(transparent)]
61	Wrapped(Box<Error>),
62}
63
64impl From<Error> for RoutineError {
65	fn from(err: Error) -> Self {
66		RoutineError::Wrapped(Box::new(err))
67	}
68}
69
70impl From<CatalogError> for RoutineError {
71	fn from(err: CatalogError) -> Self {
72		RoutineError::Wrapped(Box::new(Error::from(err)))
73	}
74}
75
76impl From<TypeError> for RoutineError {
77	fn from(err: TypeError) -> Self {
78		RoutineError::Wrapped(Box::new(Error::from(err)))
79	}
80}
81
82impl From<Box<TypeError>> for RoutineError {
83	fn from(err: Box<TypeError>) -> Self {
84		RoutineError::Wrapped(Box::new(Error::from(err)))
85	}
86}
87
88impl From<RoutineError> for Error {
89	fn from(err: RoutineError) -> Self {
90		Error(Box::new(err.into_diagnostic()))
91	}
92}
93
94impl IntoDiagnostic for RoutineError {
95	fn into_diagnostic(self) -> Diagnostic {
96		match self {
97			RoutineError::FunctionArityMismatch {
98				function,
99				expected,
100				actual,
101			} => {
102				let name = function.text().to_string();
103				Diagnostic {
104					code: "FUNCTION_002".to_string(),
105					rql: None,
106					message: format!(
107						"Function {} expects {} arguments, got {}",
108						name, expected, actual
109					),
110					column: None,
111					fragment: function,
112					label: Some("wrong number of arguments".to_string()),
113					help: Some(format!(
114						"Provide exactly {} arguments to function {}",
115						expected, name
116					)),
117					notes: vec![],
118					cause: None,
119					operator_chain: None,
120				}
121			}
122			RoutineError::FunctionInvalidArgumentType {
123				function,
124				argument_index,
125				expected,
126				actual,
127			} => {
128				let name = function.text().to_string();
129				let expected_types =
130					expected.iter().map(|t| format!("{:?}", t)).collect::<Vec<_>>().join(", ");
131				Diagnostic {
132					code: "FUNCTION_004".to_string(),
133					rql: None,
134					message: format!(
135						"Function {} argument {} has invalid type: expected one of [{}], got {:?}",
136						name,
137						argument_index + 1,
138						expected_types,
139						actual
140					),
141					column: None,
142					fragment: function,
143					label: Some("invalid argument type".to_string()),
144					help: Some(format!("Provide an argument of type: {}", expected_types)),
145					notes: vec![],
146					cause: None,
147					operator_chain: None,
148				}
149			}
150			RoutineError::FunctionExecutionFailed {
151				function,
152				reason,
153			} => {
154				let name = function.text().to_string();
155				Diagnostic {
156					code: "FUNCTION_007".to_string(),
157					rql: None,
158					message: format!("Function {} execution failed: {}", name, reason),
159					column: None,
160					fragment: function,
161					label: Some("execution failed".to_string()),
162					help: Some("Check function arguments and data".to_string()),
163					notes: vec![],
164					cause: None,
165					operator_chain: None,
166				}
167			}
168			RoutineError::FunctionNotFound {
169				function,
170			} => {
171				let name = function.text().to_string();
172				Diagnostic {
173					code: "FUNCTION_009".to_string(),
174					rql: None,
175					message: format!("Generator function '{}' not found", name),
176					column: None,
177					fragment: function,
178					label: Some("unknown generator function".to_string()),
179					help: Some("Check the generator function name and ensure it is registered"
180						.to_string()),
181					notes: vec![],
182					cause: None,
183					operator_chain: None,
184				}
185			}
186			RoutineError::ProcedureArityMismatch {
187				procedure,
188				expected,
189				actual,
190			} => {
191				let name = procedure.text().to_string();
192				Diagnostic {
193					code: "PROCEDURE_001".to_string(),
194					rql: None,
195					message: format!(
196						"Procedure {} expects {} arguments, got {}",
197						name, expected, actual
198					),
199					column: None,
200					fragment: procedure,
201					label: Some("wrong number of arguments".to_string()),
202					help: Some(format!(
203						"Provide exactly {} arguments to procedure {}",
204						expected, name
205					)),
206					notes: vec![],
207					cause: None,
208					operator_chain: None,
209				}
210			}
211			RoutineError::ProcedureInvalidArgumentType {
212				procedure,
213				argument_index,
214				expected,
215				actual,
216			} => {
217				let name = procedure.text().to_string();
218				let expected_types =
219					expected.iter().map(|t| format!("{:?}", t)).collect::<Vec<_>>().join(", ");
220				Diagnostic {
221					code: "PROCEDURE_002".to_string(),
222					rql: None,
223					message: format!(
224						"Procedure {} argument {} has invalid type: expected one of [{}], got {:?}",
225						name,
226						argument_index + 1,
227						expected_types,
228						actual
229					),
230					column: None,
231					fragment: procedure,
232					label: Some("invalid argument type".to_string()),
233					help: Some(format!("Provide an argument of type: {}", expected_types)),
234					notes: vec![],
235					cause: None,
236					operator_chain: None,
237				}
238			}
239			RoutineError::ProcedureExecutionFailed {
240				procedure,
241				reason,
242			} => {
243				let name = procedure.text().to_string();
244				Diagnostic {
245					code: "PROCEDURE_003".to_string(),
246					rql: None,
247					message: format!("Procedure {} execution failed: {}", name, reason),
248					column: None,
249					fragment: procedure,
250					label: Some("execution failed".to_string()),
251					help: Some("Check procedure arguments and context".to_string()),
252					notes: vec![],
253					cause: None,
254					operator_chain: None,
255				}
256			}
257			RoutineError::Wrapped(err) => *err.0,
258		}
259	}
260}
261
262impl RoutineError {
263	pub fn with_context(self, fragment: Fragment, is_procedure: bool) -> Error {
264		match self {
265			RoutineError::Wrapped(inner) => {
266				let name = fragment.text().to_string();
267				let mut cause = *inner.0;
268				cause.with_fragment(fragment.clone());
269				let (code, message, help) = if is_procedure {
270					(
271						"PROCEDURE_003",
272						format!("Procedure {} execution failed", name),
273						"Check procedure arguments and context",
274					)
275				} else {
276					(
277						"FUNCTION_007",
278						format!("Function {} execution failed", name),
279						"Check function arguments and data",
280					)
281				};
282				Error(Box::new(Diagnostic {
283					code: code.to_string(),
284					rql: None,
285					message,
286					column: None,
287					fragment,
288					label: Some("execution failed".to_string()),
289					help: Some(help.to_string()),
290					notes: vec![],
291					cause: Some(Box::new(cause)),
292					operator_chain: None,
293				}))
294			}
295			other => Error(Box::new(other.into_diagnostic())),
296		}
297	}
298}