Skip to main content

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