use std::error::Error;
use std::fmt;
use tokio::task::JoinError;
#[derive(Debug, Clone)]
pub enum ChainError {
SessionError(String),
StdError(String),
InvalidState(String),
Internal(String),
NotFound(String),
AlreadyExists(String),
Conflict(String),
SimulatorError(String),
ClickHouseError(String),
NotEnoughData(String),
Validation {
field: String,
reason: String,
},
}
impl<'a> From<&'a str> for ChainError {
fn from(msg: &'a str) -> Self {
ChainError::StdError(msg.to_string())
}
}
impl From<String> for ChainError {
fn from(msg: String) -> Self {
ChainError::StdError(msg)
}
}
impl From<std::io::Error> for ChainError {
fn from(err: std::io::Error) -> Self {
ChainError::StdError(err.to_string())
}
}
impl From<Box<dyn Error>> for ChainError {
fn from(err: Box<dyn Error>) -> Self {
ChainError::StdError(err.to_string())
}
}
impl From<clickhouse::error::Error> for ChainError {
fn from(err: clickhouse::error::Error) -> Self {
ChainError::ClickHouseError(err.to_string())
}
}
impl From<JoinError> for ChainError {
fn from(err: JoinError) -> Self {
ChainError::ClickHouseError(err.to_string())
}
}
impl fmt::Display for ChainError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ChainError::SessionError(msg) => write!(f, "Session Error: {}", msg),
ChainError::StdError(msg) => write!(f, "Std Error: {}", msg),
ChainError::InvalidState(msg) => write!(f, "Invalid State: {}", msg),
ChainError::Internal(msg) => write!(f, "Internal Error: {}", msg),
ChainError::NotFound(msg) => write!(f, "Not Found: {}", msg),
ChainError::AlreadyExists(msg) => write!(f, "Already Exists: {}", msg),
ChainError::Conflict(msg) => write!(f, "Conflict: {}", msg),
ChainError::SimulatorError(msg) => write!(f, "Simulator Error: {}", msg),
ChainError::ClickHouseError(msg) => write!(f, "ClickHouse Error: {}", msg),
ChainError::NotEnoughData(msg) => write!(f, "Not Enough Data: {}", msg),
ChainError::Validation { field, reason } => {
write!(f, "Validation Error: {}: {}", field, reason)
}
}
}
}
impl Error for ChainError {}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
use std::io;
#[test]
fn test_from_str() {
let error: ChainError = "test error".into();
assert!(matches!(error, ChainError::StdError(msg) if msg == "test error"));
}
#[test]
fn test_from_string() {
let error_msg = "test error".to_string();
let error: ChainError = error_msg.clone().into();
assert!(matches!(error, ChainError::StdError(msg) if msg == error_msg));
}
#[test]
fn test_from_io_error() {
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
let error: ChainError = io_error.into();
assert!(matches!(error, ChainError::StdError(msg) if msg == "file not found"));
}
#[test]
fn test_from_boxed_error() {
let boxed_error: Box<dyn Error> = Box::new(io::Error::other("generic error"));
let error: ChainError = boxed_error.into();
assert!(matches!(error, ChainError::StdError(msg) if msg == "generic error"));
}
#[test]
fn test_from_clickhouse_error() {
#[derive(Debug)]
struct MockClickHouseError;
impl std::fmt::Display for MockClickHouseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "mock clickhouse error")
}
}
impl std::error::Error for MockClickHouseError {}
impl From<MockClickHouseError> for ChainError {
fn from(_err: MockClickHouseError) -> Self {
ChainError::ClickHouseError("mock clickhouse error".to_string())
}
}
let ch_error = MockClickHouseError;
let error: ChainError = ch_error.into();
assert!(
matches!(error, ChainError::ClickHouseError(msg) if msg == "mock clickhouse error")
);
}
#[test]
fn test_display_trait() {
let test_cases = vec![
(
ChainError::SessionError("session problem".to_string()),
"Session Error: session problem",
),
(
ChainError::StdError("standard error".to_string()),
"Std Error: standard error",
),
(
ChainError::InvalidState("invalid state".to_string()),
"Invalid State: invalid state",
),
(
ChainError::Internal("internal error".to_string()),
"Internal Error: internal error",
),
(
ChainError::NotFound("resource missing".to_string()),
"Not Found: resource missing",
),
(
ChainError::AlreadyExists("resource present".to_string()),
"Already Exists: resource present",
),
(
ChainError::Conflict("version mismatch".to_string()),
"Conflict: version mismatch",
),
(
ChainError::SimulatorError("simulation failed".to_string()),
"Simulator Error: simulation failed",
),
(
ChainError::ClickHouseError("database error".to_string()),
"ClickHouse Error: database error",
),
(
ChainError::NotEnoughData("insufficient data points".to_string()),
"Not Enough Data: insufficient data points",
),
(
ChainError::Validation {
field: "initial_price".to_string(),
reason: "must be greater than zero".to_string(),
},
"Validation Error: initial_price: must be greater than zero",
),
];
for (error, expected_str) in test_cases {
assert_eq!(error.to_string(), expected_str);
}
}
#[test]
fn test_error_trait() {
let error = ChainError::SessionError("test error".to_string());
let _: &dyn Error = &error;
assert!(!error.to_string().is_empty());
}
#[test]
fn test_error_variants() {
let errors = vec![
ChainError::SessionError("session issue".to_string()),
ChainError::StdError("standard issue".to_string()),
ChainError::InvalidState("invalid state".to_string()),
ChainError::Internal("internal issue".to_string()),
ChainError::NotFound("not found".to_string()),
ChainError::AlreadyExists("already exists".to_string()),
ChainError::Conflict("version conflict".to_string()),
ChainError::SimulatorError("simulation problem".to_string()),
ChainError::ClickHouseError("database issue".to_string()),
ChainError::NotEnoughData("insufficient data".to_string()),
ChainError::Validation {
field: "steps".to_string(),
reason: "out of range".to_string(),
},
];
for error in errors {
match error {
ChainError::SessionError(msg) => assert_eq!(msg, "session issue"),
ChainError::StdError(msg) => assert_eq!(msg, "standard issue"),
ChainError::InvalidState(msg) => assert_eq!(msg, "invalid state"),
ChainError::Internal(msg) => assert_eq!(msg, "internal issue"),
ChainError::NotFound(msg) => assert_eq!(msg, "not found"),
ChainError::AlreadyExists(msg) => assert_eq!(msg, "already exists"),
ChainError::Conflict(msg) => assert_eq!(msg, "version conflict"),
ChainError::SimulatorError(msg) => assert_eq!(msg, "simulation problem"),
ChainError::ClickHouseError(msg) => assert_eq!(msg, "database issue"),
ChainError::NotEnoughData(msg) => assert_eq!(msg, "insufficient data"),
ChainError::Validation { field, reason } => {
assert_eq!(field, "steps");
assert_eq!(reason, "out of range");
}
}
}
}
#[test]
fn test_validation_display_names_field_and_reason() {
let error = ChainError::Validation {
field: "volatility".to_string(),
reason: "must be a finite number".to_string(),
};
assert_eq!(
error.to_string(),
"Validation Error: volatility: must be a finite number"
);
}
}