use thiserror::Error;
#[derive(Error, Debug)]
pub enum ProcessorError {
#[error("window error: {0}")]
Window(#[from] WindowError),
#[error("aggregation error: {0}")]
Aggregation(#[from] AggregationError),
#[error("state error: {0}")]
State(#[from] StateError),
#[error("watermark error: {0}")]
Watermark(#[from] WatermarkError),
#[error("configuration error: {source}")]
Configuration {
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("execution error: {source}")]
Execution {
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("serialization error: {0}")]
Serialization(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("kafka error: {source}")]
Kafka {
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("unexpected error: {0}")]
Unexpected(String),
}
#[derive(Error, Debug)]
pub enum WindowError {
#[error("invalid event timestamp: {timestamp}, reason: {reason}")]
InvalidTimestamp { timestamp: i64, reason: String },
#[error("invalid window size: {size}ms, must be greater than 0")]
InvalidWindowSize { size: u64 },
#[error("invalid slide size: {slide}ms, must be greater than 0 and less than or equal to window size {window}ms")]
InvalidSlideSize { slide: u64, window: u64 },
#[error("invalid gap size: {gap}ms, must be greater than 0")]
InvalidGapSize { gap: u64 },
#[error("late event: event timestamp {event_time} is before watermark {watermark}, late by {late_by}ms")]
LateEvent {
event_time: i64,
watermark: i64,
late_by: i64,
},
#[error("window already closed: window_id={window_id}, close_time={close_time}")]
WindowClosed { window_id: String, close_time: i64 },
#[error("window assignment failed: {reason}")]
AssignmentFailed { reason: String },
#[error("window merge failed: {reason}")]
MergeFailed { reason: String },
}
#[derive(Error, Debug)]
pub enum AggregationError {
#[error("numeric overflow in {operation}: {details}")]
NumericOverflow { operation: String, details: String },
#[error("division by zero in {operation}")]
DivisionByZero { operation: String },
#[error("invalid metric value: {value}, reason: {reason}")]
InvalidValue { value: f64, reason: String },
#[error("percentile calculation failed: percentile={percentile}, sample_count={sample_count}, reason: {reason}")]
PercentileFailed {
percentile: u8,
sample_count: usize,
reason: String,
},
#[error("invalid histogram configuration: {reason}")]
HistogramConfig { reason: String },
#[error("insufficient data for {aggregation_type}: need at least {required} samples, got {actual}")]
InsufficientData {
aggregation_type: String,
required: usize,
actual: usize,
},
#[error("corrupted aggregation state for {aggregation_type}: {details}")]
CorruptedState {
aggregation_type: String,
details: String,
},
#[error("type mismatch: expected {expected}, got {actual}")]
TypeMismatch { expected: String, actual: String },
}
#[derive(Error, Debug)]
pub enum StateError {
#[error("state backend not initialized: {backend_type}")]
NotInitialized { backend_type: String },
#[error("state key not found: {key}")]
KeyNotFound { key: String },
#[error("state serialization failed for key '{key}': {reason}")]
SerializationFailed { key: String, reason: String },
#[error("state deserialization failed for key '{key}': {reason}")]
DeserializationFailed { key: String, reason: String },
#[error("storage error in {backend_type}: {details}")]
StorageError {
backend_type: String,
details: String,
},
#[error("checkpoint failed at {checkpoint_id}: {reason}")]
CheckpointFailed {
checkpoint_id: String,
reason: String,
},
#[error("restore failed from checkpoint {checkpoint_id}: {reason}")]
RestoreFailed {
checkpoint_id: String,
reason: String,
},
#[error("state expired for key '{key}': ttl={ttl}ms, age={age}ms")]
StateExpired { key: String, ttl: u64, age: u64 },
#[error("transaction failed: {operation}, reason: {reason}")]
TransactionFailed { operation: String, reason: String },
#[error("concurrent modification detected for key '{key}'")]
ConcurrentModification { key: String },
#[error("state backend full: used {used} bytes of {capacity} bytes")]
BackendFull { used: u64, capacity: u64 },
}
#[derive(Error, Debug)]
pub enum WatermarkError {
#[error("watermark regression: new watermark {new_watermark} is before current {current_watermark}")]
WatermarkRegression {
current_watermark: i64,
new_watermark: i64,
},
#[error("source idle timeout: source_id={source_id}, idle_duration={idle_duration}ms, timeout={timeout}ms")]
SourceIdle {
source_id: String,
idle_duration: u64,
timeout: u64,
},
#[error("invalid watermark configuration: {reason}")]
InvalidConfig { reason: String },
#[error("invalid watermark delay: {delay}ms, must be non-negative")]
InvalidDelay { delay: i64 },
#[error("event missing timestamp: event_id={event_id}")]
MissingTimestamp { event_id: String },
#[error("clock skew detected: event time {event_time} is in the future, ahead by {ahead_by}ms")]
ClockSkew { event_time: i64, ahead_by: i64 },
#[error("watermark alignment failed: {reason}")]
AlignmentFailed { reason: String },
}
pub type Result<T> = std::result::Result<T, ProcessorError>;
pub type WindowResult<T> = std::result::Result<T, WindowError>;
pub type AggregationResult<T> = std::result::Result<T, AggregationError>;
pub type StateResult<T> = std::result::Result<T, StateError>;
pub type WatermarkResult<T> = std::result::Result<T, WatermarkError>;
impl From<bincode::Error> for ProcessorError {
fn from(err: bincode::Error) -> Self {
ProcessorError::Serialization(err.to_string())
}
}
impl From<serde_json::Error> for ProcessorError {
fn from(err: serde_json::Error) -> Self {
ProcessorError::Serialization(err.to_string())
}
}
impl From<anyhow::Error> for ProcessorError {
fn from(err: anyhow::Error) -> Self {
ProcessorError::Unexpected(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_window_error_display() {
let err = WindowError::InvalidWindowSize { size: 0 };
assert!(err.to_string().contains("invalid window size"));
}
#[test]
fn test_aggregation_error_display() {
let err = AggregationError::DivisionByZero {
operation: "average".to_string(),
};
assert!(err.to_string().contains("division by zero"));
}
#[test]
fn test_state_error_display() {
let err = StateError::KeyNotFound {
key: "test_key".to_string(),
};
assert!(err.to_string().contains("not found"));
}
#[test]
fn test_watermark_error_display() {
let err = WatermarkError::WatermarkRegression {
current_watermark: 1000,
new_watermark: 900,
};
assert!(err.to_string().contains("regression"));
}
#[test]
fn test_processor_error_from_window_error() {
let window_err = WindowError::InvalidWindowSize { size: 0 };
let processor_err: ProcessorError = window_err.into();
assert!(matches!(processor_err, ProcessorError::Window(_)));
}
}