1use crate::runtime::RuntimeKind;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use thiserror::Error;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
11pub enum ErrorCode {
12 RuntimeUnavailable,
14 RuntimeInitializationFailure,
16 ExecutionFailure,
18 ExecutionTimeout,
20 ExecutionCancelled,
22 CaptureFailure,
24 RestoreFailure,
26 SerializationFailure,
28 InvalidSnapshot,
30 ProtocolViolation,
32 CapabilityViolation,
34 MigrationRejected,
36 SemanticLoss,
38 UnsupportedValue,
40 AdapterCrashed,
42 ResourceLimitExceeded,
44 Internal,
46 InvalidArgument,
48 Io,
50}
51
52impl fmt::Display for ErrorCode {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 let s = serde_json::to_value(self)
55 .ok()
56 .and_then(|v| v.as_str().map(str::to_string))
57 .unwrap_or_else(|| format!("{:?}", self));
58 write!(f, "{}", s)
59 }
60}
61
62#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
64pub struct Remediation {
65 pub action: String,
67 pub detail: Option<String>,
69}
70
71impl Remediation {
72 pub fn new(action: impl Into<String>) -> Self {
74 Self {
75 action: action.into(),
76 detail: None,
77 }
78 }
79
80 pub fn with_detail(action: impl Into<String>, detail: impl Into<String>) -> Self {
82 Self {
83 action: action.into(),
84 detail: Some(detail.into()),
85 }
86 }
87}
88
89#[derive(Clone, Debug, Error, Serialize, Deserialize)]
91#[error("{code}: {message}")]
92pub struct ParallaxError {
93 pub code: ErrorCode,
95 pub message: String,
97 pub source_component: String,
99 pub operation: String,
101 pub runtime: Option<RuntimeKind>,
103 pub context: Vec<(String, String)>,
105 pub remediations: Vec<Remediation>,
107 pub diagnostic: Option<String>,
109}
110
111impl ParallaxError {
112 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
114 Self {
115 code,
116 message: message.into(),
117 source_component: "parallax".into(),
118 operation: "unknown".into(),
119 runtime: None,
120 context: Vec::new(),
121 remediations: Vec::new(),
122 diagnostic: None,
123 }
124 }
125
126 pub fn with_source(mut self, source: impl Into<String>) -> Self {
128 self.source_component = source.into();
129 self
130 }
131
132 pub fn with_operation(mut self, op: impl Into<String>) -> Self {
134 self.operation = op.into();
135 self
136 }
137
138 pub fn with_runtime(mut self, runtime: RuntimeKind) -> Self {
140 self.runtime = Some(runtime);
141 self
142 }
143
144 pub fn context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
146 self.context.push((key.into(), value.into()));
147 self
148 }
149
150 pub fn remediate(mut self, remediation: Remediation) -> Self {
152 self.remediations.push(remediation);
153 self
154 }
155
156 pub fn with_diagnostic(mut self, diagnostic: impl Into<String>) -> Self {
158 self.diagnostic = Some(diagnostic.into());
159 self
160 }
161
162 pub fn format_report(&self, verbose: bool) -> String {
164 let mut out = String::new();
165 out.push_str(&format!("{}: {}\n", self.code, self.message));
166 if let Some(rt) = &self.runtime {
167 out.push_str(&format!("Runtime: {}\n", rt));
168 }
169 out.push_str(&format!("Operation: {}\n", self.operation));
170 for (k, v) in &self.context {
171 out.push_str(&format!("{}:\n {}\n", k, v));
172 }
173 if !self.remediations.is_empty() {
174 out.push_str("Possible actions:\n");
175 for r in &self.remediations {
176 out.push_str(&format!(" • {}\n", r.action));
177 if let Some(d) = &r.detail {
178 out.push_str(&format!(" {}\n", d));
179 }
180 }
181 }
182 if verbose {
183 if let Some(diag) = &self.diagnostic {
184 out.push_str("\nDiagnostic:\n");
185 out.push_str(diag);
186 out.push('\n');
187 }
188 }
189 out
190 }
191}
192
193pub type Result<T> = std::result::Result<T, ParallaxError>;
195
196impl From<std::io::Error> for ParallaxError {
197 fn from(err: std::io::Error) -> Self {
198 ParallaxError::new(ErrorCode::Io, err.to_string())
199 .with_source("std::io")
200 .with_operation("io")
201 }
202}
203
204impl From<serde_json::Error> for ParallaxError {
205 fn from(err: serde_json::Error) -> Self {
206 ParallaxError::new(ErrorCode::SerializationFailure, err.to_string())
207 .with_source("serde_json")
208 .with_operation("serialize")
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn report_includes_remediation() {
218 let err = ParallaxError::new(ErrorCode::MigrationRejected, "integer precision")
219 .context("Value", "9007199254740993")
220 .remediate(Remediation::new("migrate using BigInt"));
221 let report = err.format_report(false);
222 assert!(report.contains("PYTHON_INT_PRECISION") || report.contains("MIGRATION_REJECTED"));
223 assert!(report.contains("BigInt"));
224 }
225}