Skip to main content

easydoc_core/
error.rs

1//! easydoc-rust 统一错误类型。
2//!
3//! 对标 easyexcel-rust 的单枚举错误模式。
4//!
5//! 对应 Java: 各类 checked/unchecked exception 合并为单一 Rust 错误类型。
6
7use thiserror::Error;
8
9/// 库统一错误枚举。
10///
11/// Java `EasyExcel` 将错误分散在七个 `RuntimeException` 子类中;
12/// 本枚举将它们合并为一个惯用的 Rust 类型。
13///
14/// 对应 Java: `com.alibaba.excel.exception.ExcelAnalysisException`、
15/// `ExcelGenerateException`、`ExcelDataConvertException` 等
16#[derive(Debug, Error)]
17pub enum DocError {
18    /// I/O 错误,包装 `std::io::Error`。
19    ///
20    /// 对应 Java: `IOException`
21    #[error("I/O error: {0}")]
22    Io(#[from] std::io::Error),
23
24    /// ZIP 包错误(来自 docx-rs 打包)。
25    ///
26    /// 对应 Java: `ExcelAnalysisException` 中的 ZIP 相关错误
27    #[error("ZIP error: {0}")]
28    Zip(String),
29
30    /// 无效或不支持的文档格式。
31    ///
32    /// 对应 Java: `ExcelAnalysisException("The supplied file was not supported")`
33    #[error("Format error: {0}")]
34    Format(String),
35
36    /// 模板占位符无法解析或处理。
37    ///
38    /// 对应 Java: `ExcelAnalysisException` 中的模板相关错误(easyexcel-template)
39    #[error("Template error at placeholder '{placeholder}': {message}")]
40    Template {
41        /// 导致错误的占位符标记。
42        placeholder: String,
43        /// 人类可读的描述。
44        message: String,
45    },
46
47    /// 单元格或字段值无法与目标类型互转。
48    ///
49    /// 对应 Java: `com.alibaba.excel.exception.ExcelDataConvertException`
50    #[error("Conversion error: field '{field}', value '{value}': {message}")]
51    Conversion {
52        /// 字段或列名。
53        field: String,
54        /// 转换失败的值。
55        value: String,
56        /// 人类可读的描述。
57        message: String,
58    },
59
60    /// 请求的操作不被当前格式或配置支持。
61    ///
62    /// 对应 Java: `UnsupportedOperationException`
63    #[error("Unsupported operation: {0}")]
64    Unsupported(String),
65
66    /// 通用文档级错误。
67    ///
68    /// 对应 Java: `ExcelAnalysisException` / `ExcelGenerateException`
69    #[error("Document error: {0}")]
70    Document(String),
71}
72
73/// easydoc-rust 标准 `Result` 类型别名。
74pub type Result<T> = std::result::Result<T, DocError>;
75
76impl From<zip::result::ZipError> for DocError {
77    fn from(e: zip::result::ZipError) -> Self {
78        Self::Zip(e.to_string())
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn error_display_io() {
88        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
89        let err = DocError::Io(io_err);
90        assert!(format!("{err}").contains("I/O error"));
91    }
92
93    #[test]
94    fn error_display_zip() {
95        let err = DocError::Zip("corrupt".into());
96        assert!(format!("{err}").contains("ZIP error"));
97    }
98
99    #[test]
100    fn error_display_format() {
101        let err = DocError::Format("bad xml".into());
102        assert!(format!("{err}").contains("Format error"));
103    }
104
105    #[test]
106    fn error_display_template() {
107        let err = DocError::Template {
108            placeholder: "name".into(),
109            message: "not found".into(),
110        };
111        let s = format!("{err}");
112        assert!(s.contains("name"));
113        assert!(s.contains("not found"));
114    }
115
116    #[test]
117    fn error_display_conversion() {
118        let err = DocError::Conversion {
119            field: "age".into(),
120            value: "abc".into(),
121            message: "not a number".into(),
122        };
123        let s = format!("{err}");
124        assert!(s.contains("age"));
125        assert!(s.contains("abc"));
126    }
127
128    #[test]
129    fn error_display_unsupported() {
130        let err = DocError::Unsupported("macro".into());
131        assert!(format!("{err}").contains("Unsupported operation"));
132    }
133
134    #[test]
135    fn error_display_document() {
136        let err = DocError::Document("corrupted".into());
137        assert!(format!("{err}").contains("Document error"));
138    }
139
140    #[test]
141    fn error_from_zip_error() {
142        let zip_err = zip::result::ZipError::FileNotFound;
143        let err: DocError = zip_err.into();
144        assert!(matches!(err, DocError::Zip(_)));
145    }
146
147    #[test]
148    fn error_from_io_error() {
149        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
150        let err: DocError = io_err.into();
151        assert!(matches!(err, DocError::Io(_)));
152    }
153
154    #[test]
155    fn error_debug() {
156        let err = DocError::Document("test".into());
157        let dbg = format!("{err:?}");
158        assert!(dbg.contains("Document"));
159    }
160}