use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorCode {
MalformedInput,
UnknownFunction,
UnknownModule,
DisabledModule,
DomainViolation,
DivisionByZero,
IncompatibleUnits,
CurrencyMismatch,
InsufficientObservations,
SingularMatrix,
IllConditioned,
UnsupportedNumericMode,
PrecisionLimit,
NonConvergence,
ResourceLimit,
Cancelled,
BatchDependencyFailed,
UnsupportedOperation,
NotFound,
Internal,
}
impl ErrorCode {
pub fn as_str(self) -> &'static str {
match self {
ErrorCode::MalformedInput => "malformed_input",
ErrorCode::UnknownFunction => "unknown_function",
ErrorCode::UnknownModule => "unknown_module",
ErrorCode::DisabledModule => "disabled_module",
ErrorCode::DomainViolation => "domain_violation",
ErrorCode::DivisionByZero => "division_by_zero",
ErrorCode::IncompatibleUnits => "incompatible_units",
ErrorCode::CurrencyMismatch => "currency_mismatch",
ErrorCode::InsufficientObservations => "insufficient_observations",
ErrorCode::SingularMatrix => "singular_matrix",
ErrorCode::IllConditioned => "ill_conditioned",
ErrorCode::UnsupportedNumericMode => "unsupported_numeric_mode",
ErrorCode::PrecisionLimit => "precision_limit",
ErrorCode::NonConvergence => "non_convergence",
ErrorCode::ResourceLimit => "resource_limit",
ErrorCode::Cancelled => "cancelled",
ErrorCode::BatchDependencyFailed => "batch_dependency_failed",
ErrorCode::UnsupportedOperation => "unsupported_operation",
ErrorCode::NotFound => "not_found",
ErrorCode::Internal => "internal",
}
}
}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EngineError {
pub code: ErrorCode,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub details: serde_json::Value,
}
impl EngineError {
pub fn new(code: ErrorCode, message: impl Into<String>) -> EngineError {
EngineError {
code,
message: message.into(),
path: None,
details: serde_json::Value::Null,
}
}
pub fn malformed(message: impl Into<String>) -> EngineError {
Self::new(ErrorCode::MalformedInput, message)
}
pub fn domain(message: impl Into<String>) -> EngineError {
Self::new(ErrorCode::DomainViolation, message)
}
pub fn division_by_zero(message: impl Into<String>) -> EngineError {
Self::new(ErrorCode::DivisionByZero, message)
}
pub fn unsupported(message: impl Into<String>) -> EngineError {
Self::new(ErrorCode::UnsupportedOperation, message)
}
pub fn resource(message: impl Into<String>) -> EngineError {
Self::new(ErrorCode::ResourceLimit, message)
}
pub fn internal(message: impl Into<String>) -> EngineError {
Self::new(ErrorCode::Internal, message)
}
pub fn cancelled() -> EngineError {
Self::new(ErrorCode::Cancelled, "calculation was cancelled")
}
pub fn with_path(mut self, path: impl Into<String>) -> EngineError {
self.path = Some(path.into());
self
}
pub fn with_details(mut self, details: serde_json::Value) -> EngineError {
self.details = details;
self
}
pub fn batch_dependency(node: &str, source: &EngineError) -> EngineError {
EngineError::new(
ErrorCode::BatchDependencyFailed,
format!("node {node:?} failed: {}", source.message),
)
.with_details(serde_json::json!({
"node": node,
"source_code": source.code.as_str(),
}))
}
}
impl std::fmt::Display for EngineError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.code, self.message)?;
if let Some(path) = &self.path {
write!(f, " (at {path})")?;
}
Ok(())
}
}
impl std::error::Error for EngineError {}