use polars::prelude::*;
#[derive(Debug, thiserror::Error)]
pub enum DataError {
#[error("Polars error: {0}")]
Polars(#[from] PolarsError),
#[error("Data format error: {0}")]
Format(String),
#[error("Data validation error: {0}")]
Validation(String),
#[error("Data source error: {0}")]
Source(String),
#[error("Processing error: {0}")]
Processing(String),
#[error("Expression parsing error: {0}")]
Expression(String),
#[error("Column not found: {0}")]
ColumnNotFound(String),
#[error("Type mismatch: expected {expected}, got {actual}")]
TypeMismatch { expected: String, actual: String },
#[error("Memory allocation error: {0}")]
MemoryAllocation(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("IO error: {0}")]
Io(String),
#[error("Timeout error: {0}")]
Timeout(String),
#[error("Concurrency error: {0}")]
Concurrency(String),
}
impl DataError {
pub fn format(message: impl Into<String>) -> Self {
Self::Format(message.into())
}
pub fn validation(message: impl Into<String>) -> Self {
Self::Validation(message.into())
}
pub fn source(message: impl Into<String>) -> Self {
Self::Source(message.into())
}
pub fn processing(message: impl Into<String>) -> Self {
Self::Processing(message.into())
}
pub fn expression(message: impl Into<String>) -> Self {
Self::Expression(message.into())
}
pub fn column_not_found(column_name: impl Into<String>) -> Self {
Self::ColumnNotFound(column_name.into())
}
pub fn type_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self::TypeMismatch {
expected: expected.into(),
actual: actual.into(),
}
}
pub fn memory_allocation(message: impl Into<String>) -> Self {
Self::MemoryAllocation(message.into())
}
pub fn configuration(message: impl Into<String>) -> Self {
Self::Configuration(message.into())
}
pub fn serialization(message: impl Into<String>) -> Self {
Self::Serialization(message.into())
}
pub fn deserialization(message: impl Into<String>) -> Self {
Self::Deserialization(message.into())
}
pub fn io(message: impl Into<String>) -> Self {
Self::Io(message.into())
}
pub fn timeout(message: impl Into<String>) -> Self {
Self::Timeout(message.into())
}
pub fn concurrency(message: impl Into<String>) -> Self {
Self::Concurrency(message.into())
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
DataError::Timeout(_) | DataError::MemoryAllocation(_) | DataError::Concurrency(_)
)
}
pub fn is_validation_error(&self) -> bool {
matches!(
self,
DataError::Validation(_) | DataError::TypeMismatch { .. }
)
}
pub fn is_configuration_error(&self) -> bool {
matches!(self, DataError::Configuration(_))
}
pub fn is_processing_error(&self) -> bool {
matches!(
self,
DataError::Processing(_) | DataError::Expression(_) | DataError::ColumnNotFound(_)
)
}
pub fn severity(&self) -> ErrorSeverity {
match self {
DataError::Polars(_) => ErrorSeverity::High,
DataError::MemoryAllocation(_) => ErrorSeverity::Critical,
DataError::TypeMismatch { .. } => ErrorSeverity::High,
DataError::ColumnNotFound(_) => ErrorSeverity::Medium,
DataError::Validation(_) => ErrorSeverity::Medium,
DataError::Format(_) => ErrorSeverity::Low,
DataError::Configuration(_) => ErrorSeverity::Low,
_ => ErrorSeverity::Medium,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ErrorSeverity {
Low,
Medium,
High,
Critical,
}
impl ErrorSeverity {
pub fn should_stop_processing(&self) -> bool {
matches!(self, ErrorSeverity::Critical | ErrorSeverity::High)
}
pub fn should_log(&self) -> bool {
matches!(
self,
ErrorSeverity::Medium | ErrorSeverity::High | ErrorSeverity::Critical
)
}
}
pub type DataResult<T> = Result<T, DataError>;
#[derive(Debug, Clone)]
pub struct ErrorContext {
pub operation: String,
pub data_size: Option<usize>,
pub column_name: Option<String>,
pub row_index: Option<usize>,
pub additional_info: std::collections::HashMap<String, String>,
}
impl ErrorContext {
pub fn new(operation: impl Into<String>) -> Self {
Self {
operation: operation.into(),
data_size: None,
column_name: None,
row_index: None,
additional_info: std::collections::HashMap::new(),
}
}
pub fn with_data_size(mut self, size: usize) -> Self {
self.data_size = Some(size);
self
}
pub fn with_column_name(mut self, name: impl Into<String>) -> Self {
self.column_name = Some(name.into());
self
}
pub fn with_row_index(mut self, index: usize) -> Self {
self.row_index = Some(index);
self
}
pub fn add_info(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.additional_info.insert(key.into(), value.into());
}
pub fn format_error(&self, error: &DataError) -> String {
let mut message = format!("Error in {}: {}", self.operation, error);
if let Some(size) = self.data_size {
message.push_str(&format!(" (Data size: {})", size));
}
if let Some(column) = &self.column_name {
message.push_str(&format!(" (Column: {})", column));
}
if let Some(row) = self.row_index {
message.push_str(&format!(" (Row: {})", row));
}
if !self.additional_info.is_empty() {
message.push_str(" (Additional info: ");
let info_parts: Vec<String> = self
.additional_info
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
message.push_str(&info_parts.join(", "));
message.push(')');
}
message
}
}