Skip to main content

easypdf_core/
error.rs

1//! `easypdf-rust` 的错误类型。
2//!
3//! 提供核心 `PdfError` 枚举和便捷的 `Result` 类型别名。
4
5use std::io;
6
7/// `easypdf-rust` 的核心错误类型。
8///
9/// 涵盖 I/O、解析、加密和不支持的功能错误。
10#[derive(Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum PdfError {
13    /// 包装标准 I/O 错误。
14    #[error("I/O error: {0}")]
15    Io(#[from] io::Error),
16
17    /// PDF 无法解析或包含格式错误的数据。
18    #[error("PDF parse error: {0}")]
19    Parse(String),
20
21    /// 页索引超出范围。
22    #[error("Invalid page index: {0}")]
23    InvalidPage(usize),
24
25    /// 请求的功能尚未实现或不被引擎支持。
26    #[error("Unsupported feature: {0}")]
27    UnsupportedFeature(String),
28
29    /// PDF 已加密且未提供密码或密码错误。
30    #[error("Encryption error: {0}")]
31    Encryption(String),
32
33    /// 配置的资源限制被超出。
34    #[error("Resource limit exceeded for {resource}: actual {actual}, limit {limit}")]
35    ResourceLimitExceeded {
36        /// 资源名称。
37        resource: &'static str,
38        /// 配置的限制值。
39        limit: u64,
40        /// 实际观测值。
41        actual: u64,
42    },
43
44    /// 安全守卫拒绝了输入(解压炸弹、SSRF 等)。
45    #[error("security violation: {0}")]
46    SecurityViolation(String),
47
48    /// 数字签名操作失败。
49    #[error("signature error: {0}")]
50    Signature(String),
51
52    /// 其他错误的兜底变体。
53    #[error("{0}")]
54    Other(String),
55}
56
57impl PdfError {
58    /// 返回稳定的机器可读错误码。
59    #[must_use]
60    pub const fn code(&self) -> PdfErrorCode {
61        match self {
62            Self::Io(_) => PdfErrorCode::Io,
63            Self::Parse(_) => PdfErrorCode::Parse,
64            Self::InvalidPage(_) => PdfErrorCode::InvalidPage,
65            Self::UnsupportedFeature(_) => PdfErrorCode::UnsupportedFeature,
66            Self::Encryption(_) => PdfErrorCode::Encryption,
67            Self::ResourceLimitExceeded { .. } => PdfErrorCode::ResourceLimitExceeded,
68            Self::SecurityViolation(_) => PdfErrorCode::SecurityViolation,
69            Self::Signature(_) => PdfErrorCode::Signature,
70            Self::Other(_) => PdfErrorCode::Other,
71        }
72    }
73}
74
75/// 稳定的机器可读 PDF 错误分类。
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
77#[non_exhaustive]
78pub enum PdfErrorCode {
79    /// 文件或流 I/O 失败。
80    Io,
81    /// 格式错误或不支持的 PDF 语法。
82    Parse,
83    /// 无效的页面选择。
84    InvalidPage,
85    /// 所选后端未实现该功能。
86    UnsupportedFeature,
87    /// 加密或密码失败。
88    Encryption,
89    /// 配置的资源限制被超出。
90    ResourceLimitExceeded,
91    /// 安全守卫拒绝了输入。
92    SecurityViolation,
93    /// 数字签名失败。
94    Signature,
95    /// 未分类的失败。
96    Other,
97}
98
99/// 使用 [`PdfError`] 作为错误变体的便捷 `Result` 类型。
100pub type Result<T, E = PdfError> = std::result::Result<T, E>;
101
102#[cfg(test)]
103#[allow(clippy::uninlined_format_args, clippy::float_cmp)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn io_error_display() {
109        let io_err = io::Error::new(io::ErrorKind::NotFound, "file missing");
110        let err = PdfError::Io(io_err);
111        assert!(format!("{}", err).contains("I/O error"));
112        assert!(format!("{}", err).contains("file missing"));
113    }
114
115    #[test]
116    fn io_error_from_conversion() {
117        let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
118        let err: PdfError = io_err.into();
119        assert!(matches!(err, PdfError::Io(_)));
120    }
121
122    #[test]
123    fn parse_error_display() {
124        let err = PdfError::Parse("bad header".to_string());
125        assert_eq!(format!("{}", err), "PDF parse error: bad header");
126    }
127
128    #[test]
129    fn invalid_page_display() {
130        let err = PdfError::InvalidPage(42);
131        assert_eq!(format!("{}", err), "Invalid page index: 42");
132    }
133
134    #[test]
135    fn unsupported_feature_display() {
136        let err = PdfError::UnsupportedFeature("encryption".to_string());
137        assert_eq!(format!("{}", err), "Unsupported feature: encryption");
138    }
139
140    #[test]
141    fn encryption_error_display() {
142        let err = PdfError::Encryption("wrong password".to_string());
143        assert_eq!(format!("{}", err), "Encryption error: wrong password");
144    }
145
146    #[test]
147    fn resource_limit_exceeded_display() {
148        let err = PdfError::ResourceLimitExceeded {
149            resource: "input_bytes",
150            limit: 1024,
151            actual: 2048,
152        };
153        let msg = format!("{}", err);
154        assert!(msg.contains("input_bytes"));
155        assert!(msg.contains("1024"));
156        assert!(msg.contains("2048"));
157    }
158
159    #[test]
160    fn security_violation_display() {
161        let err = PdfError::SecurityViolation("SSRF blocked".to_string());
162        assert_eq!(format!("{}", err), "security violation: SSRF blocked");
163    }
164
165    #[test]
166    fn signature_error_display() {
167        let err = PdfError::Signature("invalid cert".to_string());
168        assert_eq!(format!("{}", err), "signature error: invalid cert");
169    }
170
171    #[test]
172    fn other_error_display() {
173        let err = PdfError::Other("something".to_string());
174        assert_eq!(format!("{}", err), "something");
175    }
176
177    // --- error code tests ---
178
179    #[test]
180    fn code_io() {
181        let err = PdfError::Io(io::Error::other("x"));
182        assert_eq!(err.code(), PdfErrorCode::Io);
183    }
184
185    #[test]
186    fn code_parse() {
187        let err = PdfError::Parse("x".to_string());
188        assert_eq!(err.code(), PdfErrorCode::Parse);
189    }
190
191    #[test]
192    fn code_invalid_page() {
193        let err = PdfError::InvalidPage(0);
194        assert_eq!(err.code(), PdfErrorCode::InvalidPage);
195    }
196
197    #[test]
198    fn code_unsupported_feature() {
199        let err = PdfError::UnsupportedFeature("x".to_string());
200        assert_eq!(err.code(), PdfErrorCode::UnsupportedFeature);
201    }
202
203    #[test]
204    fn code_encryption() {
205        let err = PdfError::Encryption("x".to_string());
206        assert_eq!(err.code(), PdfErrorCode::Encryption);
207    }
208
209    #[test]
210    fn code_resource_limit_exceeded() {
211        let err = PdfError::ResourceLimitExceeded {
212            resource: "pages",
213            limit: 100,
214            actual: 200,
215        };
216        assert_eq!(err.code(), PdfErrorCode::ResourceLimitExceeded);
217    }
218
219    #[test]
220    fn code_security_violation() {
221        let err = PdfError::SecurityViolation("x".to_string());
222        assert_eq!(err.code(), PdfErrorCode::SecurityViolation);
223    }
224
225    #[test]
226    fn code_signature() {
227        let err = PdfError::Signature("x".to_string());
228        assert_eq!(err.code(), PdfErrorCode::Signature);
229    }
230
231    #[test]
232    fn code_other() {
233        let err = PdfError::Other("x".to_string());
234        assert_eq!(err.code(), PdfErrorCode::Other);
235    }
236
237    // --- PdfErrorCode tests ---
238
239    #[test]
240    fn error_code_clone() {
241        let code = PdfErrorCode::Io;
242        let cloned = code;
243        assert_eq!(code, cloned);
244    }
245
246    #[test]
247    fn error_code_debug() {
248        assert_eq!(format!("{:?}", PdfErrorCode::Parse), "Parse");
249        assert_eq!(format!("{:?}", PdfErrorCode::Io), "Io");
250    }
251
252    #[test]
253    fn error_code_eq() {
254        assert_eq!(PdfErrorCode::Io, PdfErrorCode::Io);
255        assert_ne!(PdfErrorCode::Io, PdfErrorCode::Parse);
256    }
257
258    #[test]
259    fn error_code_hash() {
260        use std::collections::HashSet;
261        let mut set = HashSet::new();
262        set.insert(PdfErrorCode::Io);
263        set.insert(PdfErrorCode::Io);
264        set.insert(PdfErrorCode::Parse);
265        assert_eq!(set.len(), 2);
266    }
267
268    #[test]
269    fn debug_format_all_variants() {
270        let errors = vec![
271            PdfError::Parse("p".to_string()),
272            PdfError::InvalidPage(0),
273            PdfError::UnsupportedFeature("u".to_string()),
274            PdfError::Encryption("e".to_string()),
275            PdfError::SecurityViolation("s".to_string()),
276            PdfError::Signature("s".to_string()),
277            PdfError::Other("o".to_string()),
278        ];
279        for err in errors {
280            let _ = format!("{:?}", err);
281        }
282    }
283}