use std::fmt;
use thiserror::Error;
#[derive(Debug, Clone)]
pub struct ErrorLocation {
pub file: &'static str,
pub line: u32,
pub column: Option<u32>,
pub function: Option<&'static str>,
}
impl ErrorLocation {
#[inline]
pub fn new(file: &'static str, line: u32) -> Self {
Self {
file,
line,
column: None,
function: None,
}
}
#[inline]
pub fn with_function(file: &'static str, line: u32, function: &'static str) -> Self {
Self {
file,
line,
column: None,
function: Some(function),
}
}
#[inline]
pub fn with_column(file: &'static str, line: u32, column: u32) -> Self {
Self {
file,
line,
column: Some(column),
function: None,
}
}
#[inline]
pub fn full(file: &'static str, line: u32, column: u32, function: &'static str) -> Self {
Self {
file,
line,
column: Some(column),
function: Some(function),
}
}
}
impl fmt::Display for ErrorLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.file, self.line)?;
if let Some(column) = self.column {
write!(f, ":{}", column)?;
}
if let Some(function) = self.function {
write!(f, " in {}", function)?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct ErrorContext {
pub message: String,
pub location: Option<ErrorLocation>,
pub cause: Option<Box<CoreError>>,
}
impl ErrorContext {
pub fn new<S: Into<String>>(message: S) -> Self {
Self {
message: message.into(),
location: None,
cause: None,
}
}
pub fn with_location(mut self, location: ErrorLocation) -> Self {
self.location = Some(location);
self
}
pub fn with_cause(mut self, cause: CoreError) -> Self {
self.cause = Some(Box::new(cause));
self
}
}
impl fmt::Display for ErrorContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)?;
if let Some(location) = &self.location {
write!(f, " at {}", location)?;
}
if let Some(cause) = &self.cause {
write!(f, "\nCaused by: {}", cause)?;
}
Ok(())
}
}
#[derive(Error, Debug)]
pub enum CoreError {
#[error("{0}")]
ComputationError(ErrorContext),
#[error("{0}")]
DomainError(ErrorContext),
#[error("{0}")]
DispatchError(ErrorContext),
#[error("{0}")]
ConvergenceError(ErrorContext),
#[error("{0}")]
DimensionError(ErrorContext),
#[error("{0}")]
ShapeError(ErrorContext),
#[error("{0}")]
IndexError(ErrorContext),
#[error("{0}")]
ValueError(ErrorContext),
#[error("{0}")]
TypeError(ErrorContext),
#[error("{0}")]
NotImplementedError(ErrorContext),
#[error("{0}")]
ImplementationError(ErrorContext),
#[error("{0}")]
MemoryError(ErrorContext),
#[error("{0}")]
ConfigError(ErrorContext),
#[error("{0}")]
InvalidArgument(ErrorContext),
#[error("{0}")]
PermissionError(ErrorContext),
#[error("{0}")]
ValidationError(ErrorContext),
#[error("{0}")]
JITError(ErrorContext),
#[error("JSON error: {0}")]
JSONError(ErrorContext),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
}
pub type CoreResult<T> = Result<T, CoreError>;
#[cfg(feature = "serialization")]
impl From<serde_json::Error> for CoreError {
fn from(err: serde_json::Error) -> Self {
CoreError::JSONError(ErrorContext::new(format!("JSON error: {}", err)))
}
}
impl From<crate::array_protocol::OperationError> for CoreError {
fn from(err: crate::array_protocol::OperationError) -> Self {
use crate::array_protocol::OperationError;
match err {
OperationError::NotImplemented(msg) => {
CoreError::NotImplementedError(ErrorContext::new(msg))
}
OperationError::ShapeMismatch(msg) => CoreError::ShapeError(ErrorContext::new(msg)),
OperationError::TypeMismatch(msg) => CoreError::TypeError(ErrorContext::new(msg)),
OperationError::Other(msg) => CoreError::ComputationError(ErrorContext::new(msg)),
}
}
}
#[macro_export]
macro_rules! error_context {
($message:expr) => {
$crate::error::ErrorContext::new($message)
.with_location($crate::error::ErrorLocation::new(file!(), line!()))
};
($message:expr, $function:expr) => {
$crate::error::ErrorContext::new($message).with_location(
$crate::error::ErrorLocation::with_function(file!(), line!(), $function),
)
};
}
#[macro_export]
macro_rules! domain_error {
($message:expr) => {
$crate::error::CoreError::DomainError(error_context!($message))
};
($message:expr, $function:expr) => {
$crate::error::CoreError::DomainError(error_context!($message, $function))
};
}
#[macro_export]
macro_rules! dimension_error {
($message:expr) => {
$crate::error::CoreError::DimensionError(error_context!($message))
};
($message:expr, $function:expr) => {
$crate::error::CoreError::DimensionError(error_context!($message, $function))
};
}
#[macro_export]
macro_rules! value_error {
($message:expr) => {
$crate::error::CoreError::ValueError(error_context!($message))
};
($message:expr, $function:expr) => {
$crate::error::CoreError::ValueError(error_context!($message, $function))
};
}
#[macro_export]
macro_rules! computation_error {
($message:expr) => {
$crate::error::CoreError::ComputationError(error_context!($message))
};
($message:expr, $function:expr) => {
$crate::error::CoreError::ComputationError(error_context!($message, $function))
};
}
pub fn check_domain<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
if condition {
Ok(())
} else {
Err(CoreError::DomainError(
ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
))
}
}
pub fn check_dimensions<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
if condition {
Ok(())
} else {
Err(CoreError::DimensionError(
ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
))
}
}
pub fn check_value<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
if condition {
Ok(())
} else {
Err(CoreError::ValueError(
ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
))
}
}
pub fn validate<T, F, S>(value: T, validator: F, message: S) -> CoreResult<T>
where
F: FnOnce(&T) -> bool,
S: Into<String>,
{
if validator(&value) {
Ok(value)
} else {
Err(CoreError::ValidationError(
ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
))
}
}
pub fn convert_error<E, S>(error: E, message: S) -> CoreError
where
E: std::error::Error + 'static,
S: Into<String>,
{
let message_str = message.into();
let error_message = format!("{} | Original error: {}", message_str, error);
CoreError::ComputationError(
ErrorContext::new(error_message).with_location(ErrorLocation::new(file!(), line!())),
)
}
pub fn chain_error<S>(error: CoreError, message: S) -> CoreError
where
S: Into<String>,
{
CoreError::ComputationError(
ErrorContext::new(message)
.with_location(ErrorLocation::new(file!(), line!()))
.with_cause(error),
)
}