1use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum ErrorCode {
10 MalformedInput,
12 UnknownFunction,
14 UnknownModule,
16 DisabledModule,
18 DomainViolation,
20 DivisionByZero,
22 IncompatibleUnits,
24 CurrencyMismatch,
27 InsufficientObservations,
29 SingularMatrix,
31 IllConditioned,
33 UnsupportedNumericMode,
35 PrecisionLimit,
37 NonConvergence,
39 ResourceLimit,
41 Cancelled,
43 BatchDependencyFailed,
45 UnsupportedOperation,
47 NotFound,
49 Internal,
51}
52
53impl ErrorCode {
54 pub fn as_str(self) -> &'static str {
55 match self {
56 ErrorCode::MalformedInput => "malformed_input",
57 ErrorCode::UnknownFunction => "unknown_function",
58 ErrorCode::UnknownModule => "unknown_module",
59 ErrorCode::DisabledModule => "disabled_module",
60 ErrorCode::DomainViolation => "domain_violation",
61 ErrorCode::DivisionByZero => "division_by_zero",
62 ErrorCode::IncompatibleUnits => "incompatible_units",
63 ErrorCode::CurrencyMismatch => "currency_mismatch",
64 ErrorCode::InsufficientObservations => "insufficient_observations",
65 ErrorCode::SingularMatrix => "singular_matrix",
66 ErrorCode::IllConditioned => "ill_conditioned",
67 ErrorCode::UnsupportedNumericMode => "unsupported_numeric_mode",
68 ErrorCode::PrecisionLimit => "precision_limit",
69 ErrorCode::NonConvergence => "non_convergence",
70 ErrorCode::ResourceLimit => "resource_limit",
71 ErrorCode::Cancelled => "cancelled",
72 ErrorCode::BatchDependencyFailed => "batch_dependency_failed",
73 ErrorCode::UnsupportedOperation => "unsupported_operation",
74 ErrorCode::NotFound => "not_found",
75 ErrorCode::Internal => "internal",
76 }
77 }
78}
79
80impl std::fmt::Display for ErrorCode {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 f.write_str(self.as_str())
83 }
84}
85
86#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
88pub struct EngineError {
89 pub code: ErrorCode,
90 pub message: String,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub path: Option<String>,
94 #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
96 pub details: serde_json::Value,
97}
98
99impl EngineError {
100 pub fn new(code: ErrorCode, message: impl Into<String>) -> EngineError {
101 EngineError {
102 code,
103 message: message.into(),
104 path: None,
105 details: serde_json::Value::Null,
106 }
107 }
108
109 pub fn malformed(message: impl Into<String>) -> EngineError {
110 Self::new(ErrorCode::MalformedInput, message)
111 }
112
113 pub fn domain(message: impl Into<String>) -> EngineError {
114 Self::new(ErrorCode::DomainViolation, message)
115 }
116
117 pub fn division_by_zero(message: impl Into<String>) -> EngineError {
118 Self::new(ErrorCode::DivisionByZero, message)
119 }
120
121 pub fn unsupported(message: impl Into<String>) -> EngineError {
122 Self::new(ErrorCode::UnsupportedOperation, message)
123 }
124
125 pub fn resource(message: impl Into<String>) -> EngineError {
126 Self::new(ErrorCode::ResourceLimit, message)
127 }
128
129 pub fn internal(message: impl Into<String>) -> EngineError {
130 Self::new(ErrorCode::Internal, message)
131 }
132
133 pub fn cancelled() -> EngineError {
134 Self::new(ErrorCode::Cancelled, "calculation was cancelled")
135 }
136
137 pub fn with_path(mut self, path: impl Into<String>) -> EngineError {
138 self.path = Some(path.into());
139 self
140 }
141
142 pub fn with_details(mut self, details: serde_json::Value) -> EngineError {
143 self.details = details;
144 self
145 }
146
147 pub fn batch_dependency(node: &str, source: &EngineError) -> EngineError {
149 EngineError::new(
150 ErrorCode::BatchDependencyFailed,
151 format!("node {node:?} failed: {}", source.message),
152 )
153 .with_details(serde_json::json!({
154 "node": node,
155 "source_code": source.code.as_str(),
156 }))
157 }
158}
159
160impl std::fmt::Display for EngineError {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 write!(f, "{}: {}", self.code, self.message)?;
163 if let Some(path) = &self.path {
164 write!(f, " (at {path})")?;
165 }
166 Ok(())
167 }
168}
169
170impl std::error::Error for EngineError {}