Skip to main content

reifydb_routine/procedure/
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 ProcedureError {
12	#[error("procedure {} expects {expected} arguments, got {actual}", procedure.text())]
13	ArityMismatch {
14		procedure: Fragment,
15		expected: usize,
16		actual: usize,
17	},
18
19	#[error("procedure {} argument {} has invalid type: got {actual:?}", procedure.text(), argument_index + 1)]
20	InvalidArgumentType {
21		procedure: Fragment,
22		argument_index: usize,
23		expected: Vec<Type>,
24		actual: Type,
25	},
26
27	#[error("procedure {} execution failed: {reason}", procedure.text())]
28	ExecutionFailed {
29		procedure: Fragment,
30		reason: String,
31	},
32
33	#[error(transparent)]
34	Wrapped(Box<Error>),
35}
36
37impl From<Error> for ProcedureError {
38	fn from(err: Error) -> Self {
39		ProcedureError::Wrapped(Box::new(err))
40	}
41}
42
43impl From<TypeError> for ProcedureError {
44	fn from(err: TypeError) -> Self {
45		ProcedureError::Wrapped(Box::new(Error::from(err)))
46	}
47}
48
49impl From<Box<TypeError>> for ProcedureError {
50	fn from(err: Box<TypeError>) -> Self {
51		ProcedureError::Wrapped(Box::new(Error::from(err)))
52	}
53}
54
55impl ProcedureError {
56	/// Attach procedure name context to Wrapped errors.
57	/// Named variants already carry the procedure name and convert normally.
58	/// Wrapped variants become PROCEDURE_003 with the inner error as `cause`.
59	pub fn with_context(self, procedure: Fragment) -> Error {
60		match self {
61			ProcedureError::Wrapped(inner) => {
62				let name = procedure.text().to_string();
63				let mut cause = *inner.0;
64				cause.with_fragment(procedure.clone());
65				Error(Box::new(Diagnostic {
66					code: "PROCEDURE_003".to_string(),
67					statement: None,
68					message: format!("Procedure {} execution failed", name),
69					column: None,
70					fragment: procedure,
71					label: Some("execution failed".to_string()),
72					help: Some("Check procedure arguments and context".to_string()),
73					notes: vec![],
74					cause: Some(Box::new(cause)),
75					operator_chain: None,
76				}))
77			}
78			other => Error(Box::new(other.into_diagnostic())),
79		}
80	}
81}
82
83impl From<ProcedureError> for Error {
84	fn from(err: ProcedureError) -> Self {
85		Error(Box::new(err.into_diagnostic()))
86	}
87}
88
89impl IntoDiagnostic for ProcedureError {
90	fn into_diagnostic(self) -> Diagnostic {
91		match self {
92			ProcedureError::ArityMismatch {
93				procedure,
94				expected,
95				actual,
96			} => {
97				let name = procedure.text().to_string();
98				Diagnostic {
99					code: "PROCEDURE_001".to_string(),
100					statement: None,
101					message: format!(
102						"Procedure {} expects {} arguments, got {}",
103						name, expected, actual
104					),
105					column: None,
106					fragment: procedure,
107					label: Some("wrong number of arguments".to_string()),
108					help: Some(format!(
109						"Provide exactly {} arguments to procedure {}",
110						expected, name
111					)),
112					notes: vec![],
113					cause: None,
114					operator_chain: None,
115				}
116			}
117			ProcedureError::InvalidArgumentType {
118				procedure,
119				argument_index,
120				expected,
121				actual,
122			} => {
123				let name = procedure.text().to_string();
124				let expected_types =
125					expected.iter().map(|t| format!("{:?}", t)).collect::<Vec<_>>().join(", ");
126				Diagnostic {
127					code: "PROCEDURE_002".to_string(),
128					statement: None,
129					message: format!(
130						"Procedure {} argument {} has invalid type: expected one of [{}], got {:?}",
131						name,
132						argument_index + 1,
133						expected_types,
134						actual
135					),
136					column: None,
137					fragment: procedure,
138					label: Some("invalid argument type".to_string()),
139					help: Some(format!("Provide an argument of type: {}", expected_types)),
140					notes: vec![],
141					cause: None,
142					operator_chain: None,
143				}
144			}
145			ProcedureError::ExecutionFailed {
146				procedure,
147				reason,
148			} => {
149				let name = procedure.text().to_string();
150				Diagnostic {
151					code: "PROCEDURE_003".to_string(),
152					statement: None,
153					message: format!("Procedure {} execution failed: {}", name, reason),
154					column: None,
155					fragment: procedure,
156					label: Some("execution failed".to_string()),
157					help: Some("Check procedure arguments and context".to_string()),
158					notes: vec![],
159					cause: None,
160					operator_chain: None,
161				}
162			}
163			ProcedureError::Wrapped(err) => *err.0,
164		}
165	}
166}