Skip to main content

reifydb_routine/function/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_type::{
5	error::{Diagnostic, Error, IntoDiagnostic, TypeError},
6	fragment::Fragment,
7	value::r#type::Type,
8};
9
10#[derive(Debug, thiserror::Error)]
11pub enum FunctionError {
12	#[error("function {} expects {expected} arguments, got {actual}", function.text())]
13	ArityMismatch {
14		function: Fragment,
15		expected: usize,
16		actual: usize,
17	},
18
19	#[error("function {} argument {} has invalid type: got {actual:?}", function.text(), argument_index + 1)]
20	InvalidArgumentType {
21		function: Fragment,
22		argument_index: usize,
23		expected: Vec<Type>,
24		actual: Type,
25	},
26
27	#[error("function {} execution failed: {reason}", function.text())]
28	ExecutionFailed {
29		function: Fragment,
30		reason: String,
31	},
32
33	#[error("generator function '{}' not found", function.text())]
34	NotFound {
35		function: Fragment,
36	},
37
38	#[error(transparent)]
39	Wrapped(Box<Error>),
40}
41
42impl From<Error> for FunctionError {
43	fn from(err: Error) -> Self {
44		FunctionError::Wrapped(Box::new(err))
45	}
46}
47
48impl From<TypeError> for FunctionError {
49	fn from(err: TypeError) -> Self {
50		FunctionError::Wrapped(Box::new(Error::from(err)))
51	}
52}
53
54impl FunctionError {
55	/// Attach function name context to Wrapped errors.
56	/// Named variants already carry the function name and convert normally.
57	/// Wrapped variants become FUNCTION_007 with the inner error as `cause`.
58	pub fn with_context(self, function: Fragment) -> Error {
59		match self {
60			FunctionError::Wrapped(inner) => {
61				let name = function.text().to_string();
62				let mut cause = inner.0;
63				cause.with_fragment(function.clone());
64				Error(Diagnostic {
65					code: "FUNCTION_007".to_string(),
66					statement: None,
67					message: format!("Function {} execution failed", name),
68					column: None,
69					fragment: function,
70					label: Some("execution failed".to_string()),
71					help: Some("Check function arguments and data".to_string()),
72					notes: vec![],
73					cause: Some(Box::new(cause)),
74					operator_chain: None,
75				})
76			}
77			other => Error(other.into_diagnostic()),
78		}
79	}
80}
81
82impl From<FunctionError> for Error {
83	fn from(err: FunctionError) -> Self {
84		Error(err.into_diagnostic())
85	}
86}
87
88impl IntoDiagnostic for FunctionError {
89	fn into_diagnostic(self) -> Diagnostic {
90		match self {
91			FunctionError::ArityMismatch {
92				function,
93				expected,
94				actual,
95			} => {
96				let name = function.text().to_string();
97				Diagnostic {
98					code: "FUNCTION_002".to_string(),
99					statement: None,
100					message: format!(
101						"Function {} expects {} arguments, got {}",
102						name, expected, actual
103					),
104					column: None,
105					fragment: function,
106					label: Some("wrong number of arguments".to_string()),
107					help: Some(format!(
108						"Provide exactly {} arguments to function {}",
109						expected, name
110					)),
111					notes: vec![],
112					cause: None,
113					operator_chain: None,
114				}
115			}
116			FunctionError::InvalidArgumentType {
117				function,
118				argument_index,
119				expected,
120				actual,
121			} => {
122				let name = function.text().to_string();
123				let expected_types =
124					expected.iter().map(|t| format!("{:?}", t)).collect::<Vec<_>>().join(", ");
125				Diagnostic {
126					code: "FUNCTION_004".to_string(),
127					statement: None,
128					message: format!(
129						"Function {} argument {} has invalid type: expected one of [{}], got {:?}",
130						name,
131						argument_index + 1,
132						expected_types,
133						actual
134					),
135					column: None,
136					fragment: function,
137					label: Some("invalid argument type".to_string()),
138					help: Some(format!("Provide an argument of type: {}", expected_types)),
139					notes: vec![],
140					cause: None,
141					operator_chain: None,
142				}
143			}
144			FunctionError::ExecutionFailed {
145				function,
146				reason,
147			} => {
148				let name = function.text().to_string();
149				Diagnostic {
150					code: "FUNCTION_007".to_string(),
151					statement: None,
152					message: format!("Function {} execution failed: {}", name, reason),
153					column: None,
154					fragment: function,
155					label: Some("execution failed".to_string()),
156					help: Some("Check function arguments and data".to_string()),
157					notes: vec![],
158					cause: None,
159					operator_chain: None,
160				}
161			}
162			FunctionError::NotFound {
163				function,
164			} => {
165				let name = function.text().to_string();
166				Diagnostic {
167					code: "FUNCTION_009".to_string(),
168					statement: None,
169					message: format!("Generator function '{}' not found", name),
170					column: None,
171					fragment: function,
172					label: Some("unknown generator function".to_string()),
173					help: Some("Check the generator function name and ensure it is registered"
174						.to_string()),
175					notes: vec![],
176					cause: None,
177					operator_chain: None,
178				}
179			}
180			FunctionError::Wrapped(err) => err.0,
181		}
182	}
183}
184
185pub type ScalarFunctionError = FunctionError;
186pub type AggregateFunctionError = FunctionError;
187pub type GeneratorFunctionError = FunctionError;
188
189pub type ScalarFunctionResult<T> = Result<T, FunctionError>;
190pub type AggregateFunctionResult<T> = Result<T, FunctionError>;
191pub type GeneratorFunctionResult<T> = Result<T, FunctionError>;