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