1use std::fmt;
6use thiserror::Error;
7
8#[derive(Debug, Clone)]
10pub struct ErrorLocation {
11 pub file: &'static str,
13 pub line: u32,
15 pub column: Option<u32>,
17 pub function: Option<&'static str>,
19}
20
21impl ErrorLocation {
22 #[inline]
24 pub fn new(file: &'static str, line: u32) -> Self {
25 Self {
26 file,
27 line,
28 column: None,
29 function: None,
30 }
31 }
32
33 #[inline]
35 pub fn with_function(file: &'static str, line: u32, function: &'static str) -> Self {
36 Self {
37 file,
38 line,
39 column: None,
40 function: Some(function),
41 }
42 }
43
44 #[inline]
46 pub fn with_column(file: &'static str, line: u32, column: u32) -> Self {
47 Self {
48 file,
49 line,
50 column: Some(column),
51 function: None,
52 }
53 }
54
55 #[inline]
57 pub fn full(file: &'static str, line: u32, column: u32, function: &'static str) -> Self {
58 Self {
59 file,
60 line,
61 column: Some(column),
62 function: Some(function),
63 }
64 }
65}
66
67impl fmt::Display for ErrorLocation {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{}:{}", self.file, self.line)?;
70 if let Some(column) = self.column {
71 write!(f, ":{}", column)?;
72 }
73 if let Some(function) = self.function {
74 write!(f, " in {}", function)?;
75 }
76 Ok(())
77 }
78}
79
80#[derive(Debug)]
82pub struct ErrorContext {
83 pub message: String,
85 pub location: Option<ErrorLocation>,
87 pub cause: Option<Box<CoreError>>,
89}
90
91impl ErrorContext {
92 pub fn new<S: Into<String>>(message: S) -> Self {
94 Self {
95 message: message.into(),
96 location: None,
97 cause: None,
98 }
99 }
100
101 pub fn with_location(mut self, location: ErrorLocation) -> Self {
103 self.location = Some(location);
104 self
105 }
106
107 pub fn with_cause(mut self, cause: CoreError) -> Self {
109 self.cause = Some(Box::new(cause));
110 self
111 }
112}
113
114impl fmt::Display for ErrorContext {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "{}", self.message)?;
117 if let Some(location) = &self.location {
118 write!(f, " at {}", location)?;
119 }
120 if let Some(cause) = &self.cause {
121 write!(f, "\nCaused by: {}", cause)?;
122 }
123 Ok(())
124 }
125}
126
127#[derive(Error, Debug)]
129pub enum CoreError {
130 #[error("{0}")]
132 ComputationError(ErrorContext),
133
134 #[error("{0}")]
136 DomainError(ErrorContext),
137
138 #[error("{0}")]
140 DispatchError(ErrorContext),
141
142 #[error("{0}")]
144 ConvergenceError(ErrorContext),
145
146 #[error("{0}")]
148 DimensionError(ErrorContext),
149
150 #[error("{0}")]
152 ShapeError(ErrorContext),
153
154 #[error("{0}")]
156 IndexError(ErrorContext),
157
158 #[error("{0}")]
160 ValueError(ErrorContext),
161
162 #[error("{0}")]
164 TypeError(ErrorContext),
165
166 #[error("{0}")]
168 NotImplementedError(ErrorContext),
169
170 #[error("{0}")]
172 ImplementationError(ErrorContext),
173
174 #[error("{0}")]
176 MemoryError(ErrorContext),
177
178 #[error("{0}")]
180 ConfigError(ErrorContext),
181
182 #[error("{0}")]
184 InvalidArgument(ErrorContext),
185
186 #[error("{0}")]
188 PermissionError(ErrorContext),
189
190 #[error("{0}")]
192 ValidationError(ErrorContext),
193
194 #[error("{0}")]
196 JITError(ErrorContext),
197
198 #[error("JSON error: {0}")]
200 JSONError(ErrorContext),
201
202 #[error("IO error: {0}")]
204 IoError(#[from] std::io::Error),
205}
206
207pub type CoreResult<T> = Result<T, CoreError>;
209
210#[cfg(feature = "serialization")]
212impl From<serde_json::Error> for CoreError {
213 fn from(err: serde_json::Error) -> Self {
214 CoreError::JSONError(ErrorContext::new(format!("JSON error: {}", err)))
215 }
216}
217
218impl From<crate::array_protocol::OperationError> for CoreError {
220 fn from(err: crate::array_protocol::OperationError) -> Self {
221 use crate::array_protocol::OperationError;
222 match err {
223 OperationError::NotImplemented(msg) => {
226 CoreError::NotImplementedError(ErrorContext::new(msg))
227 }
228 OperationError::ShapeMismatch(msg) => CoreError::ShapeError(ErrorContext::new(msg)),
229 OperationError::TypeMismatch(msg) => CoreError::TypeError(ErrorContext::new(msg)),
230 OperationError::Other(msg) => CoreError::ComputationError(ErrorContext::new(msg)),
231 }
232 }
233}
234
235#[macro_export]
251macro_rules! error_context {
252 ($message:expr) => {
253 $crate::error::ErrorContext::new($message)
254 .with_location($crate::error::ErrorLocation::new(file!(), line!()))
255 };
256 ($message:expr, $function:expr) => {
257 $crate::error::ErrorContext::new($message).with_location(
258 $crate::error::ErrorLocation::with_function(file!(), line!(), $function),
259 )
260 };
261}
262
263#[macro_export]
265macro_rules! domain_error {
266 ($message:expr) => {
267 $crate::error::CoreError::DomainError(error_context!($message))
268 };
269 ($message:expr, $function:expr) => {
270 $crate::error::CoreError::DomainError(error_context!($message, $function))
271 };
272}
273
274#[macro_export]
276macro_rules! dimension_error {
277 ($message:expr) => {
278 $crate::error::CoreError::DimensionError(error_context!($message))
279 };
280 ($message:expr, $function:expr) => {
281 $crate::error::CoreError::DimensionError(error_context!($message, $function))
282 };
283}
284
285#[macro_export]
287macro_rules! value_error {
288 ($message:expr) => {
289 $crate::error::CoreError::ValueError(error_context!($message))
290 };
291 ($message:expr, $function:expr) => {
292 $crate::error::CoreError::ValueError(error_context!($message, $function))
293 };
294}
295
296#[macro_export]
298macro_rules! computation_error {
299 ($message:expr) => {
300 $crate::error::CoreError::ComputationError(error_context!($message))
301 };
302 ($message:expr, $function:expr) => {
303 $crate::error::CoreError::ComputationError(error_context!($message, $function))
304 };
305}
306
307pub fn check_domain<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
319 if condition {
320 Ok(())
321 } else {
322 Err(CoreError::DomainError(
323 ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
324 ))
325 }
326}
327
328pub fn check_dimensions<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
340 if condition {
341 Ok(())
342 } else {
343 Err(CoreError::DimensionError(
344 ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
345 ))
346 }
347}
348
349pub fn check_value<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
361 if condition {
362 Ok(())
363 } else {
364 Err(CoreError::ValueError(
365 ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
366 ))
367 }
368}
369
370pub fn validate<T, F, S>(value: T, validator: F, message: S) -> CoreResult<T>
383where
384 F: FnOnce(&T) -> bool,
385 S: Into<String>,
386{
387 if validator(&value) {
388 Ok(value)
389 } else {
390 Err(CoreError::ValidationError(
391 ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
392 ))
393 }
394}
395
396pub fn convert_error<E, S>(error: E, message: S) -> CoreError
407where
408 E: std::error::Error + 'static,
409 S: Into<String>,
410{
411 let message_str = message.into();
414 let error_message = format!("{} | Original error: {}", message_str, error);
415
416 CoreError::ComputationError(
423 ErrorContext::new(error_message).with_location(ErrorLocation::new(file!(), line!())),
424 )
425}
426
427pub fn chain_error<S>(error: CoreError, message: S) -> CoreError
438where
439 S: Into<String>,
440{
441 CoreError::ComputationError(
442 ErrorContext::new(message)
443 .with_location(ErrorLocation::new(file!(), line!()))
444 .with_cause(error),
445 )
446}