use crate::runtime::RuntimeKind;
use serde::{Deserialize, Serialize};
use std::fmt;
use thiserror::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ErrorCode {
RuntimeUnavailable,
RuntimeInitializationFailure,
ExecutionFailure,
ExecutionTimeout,
ExecutionCancelled,
CaptureFailure,
RestoreFailure,
SerializationFailure,
InvalidSnapshot,
ProtocolViolation,
CapabilityViolation,
MigrationRejected,
SemanticLoss,
UnsupportedValue,
AdapterCrashed,
ResourceLimitExceeded,
Internal,
InvalidArgument,
Io,
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_json::to_value(self)
.ok()
.and_then(|v| v.as_str().map(str::to_string))
.unwrap_or_else(|| format!("{:?}", self));
write!(f, "{}", s)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Remediation {
pub action: String,
pub detail: Option<String>,
}
impl Remediation {
pub fn new(action: impl Into<String>) -> Self {
Self {
action: action.into(),
detail: None,
}
}
pub fn with_detail(action: impl Into<String>, detail: impl Into<String>) -> Self {
Self {
action: action.into(),
detail: Some(detail.into()),
}
}
}
#[derive(Clone, Debug, Error, Serialize, Deserialize)]
#[error("{code}: {message}")]
pub struct ParallaxError {
pub code: ErrorCode,
pub message: String,
pub source_component: String,
pub operation: String,
pub runtime: Option<RuntimeKind>,
pub context: Vec<(String, String)>,
pub remediations: Vec<Remediation>,
pub diagnostic: Option<String>,
}
impl ParallaxError {
pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
source_component: "parallax".into(),
operation: "unknown".into(),
runtime: None,
context: Vec::new(),
remediations: Vec::new(),
diagnostic: None,
}
}
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.source_component = source.into();
self
}
pub fn with_operation(mut self, op: impl Into<String>) -> Self {
self.operation = op.into();
self
}
pub fn with_runtime(mut self, runtime: RuntimeKind) -> Self {
self.runtime = Some(runtime);
self
}
pub fn context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.context.push((key.into(), value.into()));
self
}
pub fn remediate(mut self, remediation: Remediation) -> Self {
self.remediations.push(remediation);
self
}
pub fn with_diagnostic(mut self, diagnostic: impl Into<String>) -> Self {
self.diagnostic = Some(diagnostic.into());
self
}
pub fn format_report(&self, verbose: bool) -> String {
let mut out = String::new();
out.push_str(&format!("{}: {}\n", self.code, self.message));
if let Some(rt) = &self.runtime {
out.push_str(&format!("Runtime: {}\n", rt));
}
out.push_str(&format!("Operation: {}\n", self.operation));
for (k, v) in &self.context {
out.push_str(&format!("{}:\n {}\n", k, v));
}
if !self.remediations.is_empty() {
out.push_str("Possible actions:\n");
for r in &self.remediations {
out.push_str(&format!(" • {}\n", r.action));
if let Some(d) = &r.detail {
out.push_str(&format!(" {}\n", d));
}
}
}
if verbose {
if let Some(diag) = &self.diagnostic {
out.push_str("\nDiagnostic:\n");
out.push_str(diag);
out.push('\n');
}
}
out
}
}
pub type Result<T> = std::result::Result<T, ParallaxError>;
impl From<std::io::Error> for ParallaxError {
fn from(err: std::io::Error) -> Self {
ParallaxError::new(ErrorCode::Io, err.to_string())
.with_source("std::io")
.with_operation("io")
}
}
impl From<serde_json::Error> for ParallaxError {
fn from(err: serde_json::Error) -> Self {
ParallaxError::new(ErrorCode::SerializationFailure, err.to_string())
.with_source("serde_json")
.with_operation("serialize")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn report_includes_remediation() {
let err = ParallaxError::new(ErrorCode::MigrationRejected, "integer precision")
.context("Value", "9007199254740993")
.remediate(Remediation::new("migrate using BigInt"));
let report = err.format_report(false);
assert!(report.contains("PYTHON_INT_PRECISION") || report.contains("MIGRATION_REJECTED"));
assert!(report.contains("BigInt"));
}
}