use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("IO 错误: {0}")]
Io(#[from] std::io::Error),
#[error("无效值 (期望: {expected:?}, 实际: {found:?})")]
Invalid { expected: String, found: String },
#[error("{0}")]
Custom(&'static str),
#[error("TCP 错误: {0}")]
Tcp(#[from] TcpError),
#[error("数据验证错误: {0}")]
Validation(#[from] ValidationError),
#[error("缓存错误: {0}")]
Cache(#[from] CacheError),
#[error("技术指标错误: {0}")]
Indicator(#[from] IndicatorError),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum TcpError {
#[error("无法连接到服务器 {host}:{port} (原因: {reason})")]
ConnectionFailed {
host: String,
port: u16,
reason: String,
},
#[error("连接超时 (超时时间: {timeout:?})")]
Timeout { timeout: String },
#[error("发送数据失败 (已发送: {sent} 字节, 原因: {reason})")]
SendFailed { sent: usize, reason: String },
#[error("接收数据失败 (期望: {expected} 字节, 原因: {reason})")]
ReceiveFailed { expected: usize, reason: String },
#[error("数据解压失败 (解压前: {deflate} 字节, 解压后期望: {inflate} 字节)")]
DecompressionFailed { deflate: usize, inflate: usize },
#[error("响应数据格式错误 (期望长度: {expected}, 实际: {actual})")]
InvalidResponse { expected: usize, actual: usize },
#[error("数据解析失败 (字段: {field}, 原因: {reason})")]
ParseFailed { field: String, reason: String },
#[error("IO 错误: {0}")]
Io(#[from] std::io::Error),
}
impl TcpError {
pub fn connection_failed(host: String, port: u16, err: &io::Error) -> Self {
TcpError::ConnectionFailed {
host,
port,
reason: err.to_string(),
}
}
pub fn timeout(duration: std::time::Duration) -> Self {
TcpError::Timeout {
timeout: format!("{:.2}s", duration.as_secs_f64()),
}
}
}
#[derive(Error, Debug)]
pub enum ValidationError {
#[error("K线数据不连续 (代码: {code}, 缺失日期: {missing:?})")]
KlineDiscontinuity {
code: String,
missing: Vec<String>,
},
#[error("K线数据异常 (代码: {code}, 日期: {date}, 类型: {anomaly_type})")]
KlineAnomaly {
code: String,
date: String,
anomaly_type: String,
},
#[error("财务数据不一致 (字段: {field}, 原因: {reason})")]
FinanceInconsistency { field: String, reason: String },
#[error("数据不足 (需要: {required}, 实际: {actual})")]
InsufficientData { required: usize, actual: usize },
#[error("数据集为空 (上下文: {context})")]
EmptyDataset { context: String },
#[error("验证警告: {0}")]
Warning(String),
}
impl ValidationError {
pub fn kline_discontinuity(code: String, missing: Vec<String>) -> Self {
ValidationError::KlineDiscontinuity { code, missing }
}
pub fn kline_anomaly(code: String, date: String, anomaly_type: String) -> Self {
ValidationError::KlineAnomaly {
code,
date,
anomaly_type,
}
}
pub fn insufficient_data(required: usize, actual: usize) -> Self {
ValidationError::InsufficientData { required, actual }
}
}
#[derive(Error, Debug)]
pub enum CacheError {
#[error("缓存文件操作失败 (路径: {path}, 原因: {reason})")]
FileOperationFailed { path: String, reason: String },
#[error("缓存数据损坏 (键: {key}, 原因: {reason})")]
CorruptedData { key: String, reason: String },
#[error("缓存已过期 (键: {key}, TTL: {ttl:?})")]
Expired { key: String, ttl: String },
#[error("序列化失败 (类型: {type_name}, 原因: {reason})")]
SerializationFailed { type_name: String, reason: String },
#[error("反序列化失败 (类型: {type_name}, 原因: {reason})")]
DeserializationFailed { type_name: String, reason: String },
#[error("IO 错误: {0}")]
Io(#[from] std::io::Error),
}
impl CacheError {
pub fn file_operation_failed(path: String, err: &io::Error) -> Self {
CacheError::FileOperationFailed {
path,
reason: err.to_string(),
}
}
}
#[derive(Error, Debug)]
pub enum IndicatorError {
#[error("参数无效 (指标: {indicator}, 参数: {parameter}, 值: {value})")]
InvalidParameter {
indicator: String,
parameter: String,
value: String,
},
#[error("数据不足 (指标: {indicator}, 最小需求: {min_required}, 实际: {actual})")]
InsufficientData {
indicator: String,
min_required: usize,
actual: usize,
},
#[error("计算溢出 (指标: {indicator}, 原因: {reason})")]
Overflow { indicator: String, reason: String },
#[error("除零错误 (指标: {indicator}, 上下文: {context})")]
DivisionByZero { indicator: String, context: String },
}
impl IndicatorError {
pub fn insufficient_data(indicator: String, min_required: usize, actual: usize) -> Self {
IndicatorError::InsufficientData {
indicator,
min_required,
actual,
}
}
pub fn invalid_parameter(indicator: String, parameter: String, value: String) -> Self {
IndicatorError::InvalidParameter {
indicator,
parameter,
value,
}
}
}
pub trait ErrorContext<T> {
fn with_context(self, context: &str) -> Result<T>;
fn map_tcp(self) -> Result<T>;
fn map_validation(self) -> Result<T>;
}
impl<T, E> ErrorContext<T> for std::result::Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn with_context(self, context: &str) -> Result<T> {
let static_msg: &'static str = Box::leak(context.to_string().into_boxed_str());
self.map_err(|_e| Error::Custom(static_msg))
}
fn map_tcp(self) -> Result<T> {
self.map_err(|e| TcpError::Io(io::Error::new(io::ErrorKind::Other, e)).into())
}
fn map_validation(self) -> Result<T> {
self.map_err(|e| ValidationError::Warning(e.to_string()).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = Error::Custom("测试错误");
assert_eq!(err.to_string(), "测试错误");
}
#[test]
fn test_tcp_error_display() {
let err = TcpError::timeout(std::time::Duration::from_secs(5));
assert!(err.to_string().contains("连接超时"));
}
#[test]
fn test_validation_error_display() {
let err = ValidationError::insufficient_data(10, 5);
assert!(err.to_string().contains("数据不足"));
assert!(err.to_string().contains("10"));
assert!(err.to_string().contains("5"));
}
#[test]
fn test_indicator_error_display() {
let err = IndicatorError::insufficient_data("SMA".to_string(), 20, 10);
assert!(err.to_string().contains("SMA"));
assert!(err.to_string().contains("20"));
assert!(err.to_string().contains("10"));
}
#[test]
fn test_error_conversion() {
let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "连接被拒绝");
let tcp_err = TcpError::from(io_err);
assert!(matches!(tcp_err, TcpError::Io(_)));
}
#[test]
fn test_error_chain() {
let err = Error::Tcp(TcpError::timeout(std::time::Duration::from_secs(5)));
assert!(err.to_string().contains("TCP 错误"));
}
}