1use std::io;
6
7#[derive(Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum PdfError {
13 #[error("I/O error: {0}")]
15 Io(#[from] io::Error),
16
17 #[error("PDF parse error: {0}")]
19 Parse(String),
20
21 #[error("Invalid page index: {0}")]
23 InvalidPage(usize),
24
25 #[error("Unsupported feature: {0}")]
27 UnsupportedFeature(String),
28
29 #[error("Encryption error: {0}")]
31 Encryption(String),
32
33 #[error("Resource limit exceeded for {resource}: actual {actual}, limit {limit}")]
35 ResourceLimitExceeded {
36 resource: &'static str,
38 limit: u64,
40 actual: u64,
42 },
43
44 #[error("security violation: {0}")]
46 SecurityViolation(String),
47
48 #[error("signature error: {0}")]
50 Signature(String),
51
52 #[error("{0}")]
54 Other(String),
55}
56
57impl PdfError {
58 #[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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
77#[non_exhaustive]
78pub enum PdfErrorCode {
79 Io,
81 Parse,
83 InvalidPage,
85 UnsupportedFeature,
87 Encryption,
89 ResourceLimitExceeded,
91 SecurityViolation,
93 Signature,
95 Other,
97}
98
99pub 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 #[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 #[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}