1#![forbid(unsafe_code)]
6#![warn(missing_docs)]
7
8use thiserror::Error;
9
10#[derive(Debug, Error)]
12pub enum OfdError {
13 #[error("I/O error: {0}")]
15 Io(#[from] std::io::Error),
16
17 #[error("container error: {0}")]
19 Package(#[from] PackageError),
20
21 #[error("XML error: {0}")]
23 Xml(#[from] XmlError),
24
25 #[error("format error: {0}")]
27 Format(#[from] FormatError),
28
29 #[error("render error: {0}")]
31 Render(#[from] RenderError),
32
33 #[error("font error: {0}")]
35 Font(#[from] FontError),
36
37 #[error("signature error: {0}")]
39 Signature(#[from] SignatureError),
40
41 #[error("crypto error: {0}")]
43 Crypto(#[from] CryptoError),
44
45 #[error("conversion error: {0}")]
47 Convert(#[from] ConvertError),
48
49 #[error("edit error: {0}")]
51 Edit(#[from] EditError),
52
53 #[error("print error: {0}")]
55 Print(#[from] PrintError),
56
57 #[error("document is encrypted, password required")]
59 PasswordRequired,
60
61 #[error("compliance check failed: {0}")]
63 Compliance(String),
64
65 #[error("resource limit exceeded: {0}")]
67 ResourceLimit(String),
68}
69
70impl OfdError {
71 pub fn is_corruption(&self) -> bool {
73 matches!(
74 self,
75 OfdError::Format(_) | OfdError::Package(PackageError::Corrupted { .. })
76 )
77 }
78}
79
80pub type Result<T, E = OfdError> = std::result::Result<T, E>;
82
83#[derive(Debug, Error)]
85pub enum PackageError {
86 #[error("container structure is corrupted: {reason}")]
88 Corrupted {
89 reason: String,
91 },
92 #[error("container is missing required entry: {entry}")]
94 MissingEntry {
95 entry: String,
97 },
98 #[error("illegal entry path (possible path traversal): {path}")]
100 IllegalPath {
101 path: String,
103 },
104 #[error("decompressed size limit exceeded: {actual} bytes, limit {limit} bytes")]
106 DecompressionLimit {
107 actual: u64,
109 limit: u64,
111 },
112 #[error("entry count limit exceeded: {actual} > {limit}")]
114 EntryCountLimit {
115 actual: usize,
117 limit: usize,
119 },
120}
121
122#[derive(Debug, Error)]
124pub enum XmlError {
125 #[error("XML syntax error at line {line}, column {col}: {msg}")]
127 Syntax {
128 line: u64,
130 col: u64,
132 msg: String,
134 },
135 #[error("element <{element}> is missing required attribute {attr}")]
137 MissingAttribute {
138 element: String,
140 attr: String,
142 },
143 #[error("element <{element}> attribute {attr}={value:?} could not be parsed")]
145 InvalidAttribute {
146 element: String,
148 attr: String,
150 value: String,
152 },
153 #[error("unsupported extension element: {0}")]
155 UnsupportedExtension(String),
156}
157
158#[derive(Debug, Error)]
160pub enum FormatError {
161 #[error("not a valid OFD file (missing OFD.xml entry point)")]
163 NotOfd,
164 #[error("document body declares no root path")]
166 MissingRoot,
167 #[error("path object declares no drawing data")]
169 MissingPathData,
170 #[error("unsupported OFD version: {0}")]
172 UnsupportedVersion(String),
173 #[error("dangling reference: {kind} with id {id}")]
175 DanglingReference {
176 kind: &'static str,
178 id: String,
180 },
181 #[error("circular reference detected: {chain}")]
183 CircularReference {
184 chain: String,
186 },
187 #[error("page index out of range: {index} (document has {total} pages)")]
189 PageOutOfRange {
190 index: usize,
192 total: usize,
194 },
195 #[error("document declares no signatures")]
197 MissingSignatures,
198 #[error("parse recursion depth exceeded: {0}")]
200 MaxDepthExceeded(usize),
201}
202
203#[derive(Debug, Error)]
205pub enum RenderError {
206 #[error("backend {backend} does not support operation: {op}")]
208 UnsupportedOp {
209 backend: &'static str,
211 op: &'static str,
213 },
214 #[error("invalid output size: {w}x{h}")]
216 InvalidSize {
217 w: u32,
219 h: u32,
221 },
222 #[error("image decode failed on page {page}: {reason}")]
224 ImageDecode {
225 page: usize,
227 reason: String,
229 },
230 #[error("image encode failed on page {page}: {reason}")]
232 ImageEncode {
233 page: usize,
235 reason: String,
237 },
238 #[error("render aborted after exceeding budget: {0}")]
240 BudgetExceeded(String),
241}
242
243#[derive(Debug, Error)]
245pub enum FontError {
246 #[error("font parse failed: {0}")]
248 Parse(String),
249 #[error("font missing with no substitute available: {family}")]
251 Missing {
252 family: String,
254 },
255 #[error("font {family} is missing glyph U+{codepoint:04X}")]
257 GlyphMissing {
258 family: String,
260 codepoint: u32,
262 },
263}
264
265#[derive(Debug, Error)]
267pub enum SignatureError {
268 #[error("signature verification failed: {0}")]
270 Invalid(String),
271 #[error("document content has been tampered with; signed range mismatch")]
273 Tampered,
274 #[error("certificate chain validation failed: {0}")]
276 CertificateChain(String),
277 #[error("certificate has expired: {subject}")]
279 CertificateExpired {
280 subject: String,
282 },
283 #[error("unsupported signature format: {0}")]
285 UnsupportedFormat(String),
286 #[error("seal impression verification failed")]
288 SealMismatch,
289}
290
291#[derive(Debug, Error)]
293pub enum CryptoError {
294 #[error("cryptographic operation failed: {0}")]
296 Operation(String),
297 #[error("invalid key material: {0}")]
299 InvalidKey(String),
300 #[error("decryption failed: wrong password or corrupted data")]
302 DecryptFailed,
303 #[error("unsupported algorithm: {0}")]
305 UnsupportedAlgorithm(String),
306}
307
308#[derive(Debug, Error)]
310pub enum ConvertError {
311 #[error("unsupported source format: {0}")]
313 UnsupportedSource(String),
314 #[error("unsupported target format: {0}")]
316 UnsupportedTarget(String),
317 #[error("structure mapping failed on page {page}, object {object}: {reason}")]
319 MappingFailed {
320 page: usize,
322 object: String,
324 reason: String,
326 },
327 #[error(
329 "conversion quality below threshold: difference ratio {diff_ratio:.4} exceeds {threshold:.4}"
330 )]
331 QualityBelowThreshold {
332 diff_ratio: f64,
334 threshold: f64,
336 },
337 #[error("conversion was cancelled")]
339 Cancelled,
340}
341
342#[derive(Debug, Error)]
344pub enum EditError {
345 #[error("edit target not found: {0}")]
347 TargetNotFound(String),
348 #[error("operation denied by permission policy: {0}")]
350 PermissionDenied(String),
351 #[error(
353 "this operation would invalidate {count} existing signature(s) and needs explicit confirmation"
354 )]
355 WouldInvalidateSignature {
356 count: usize,
358 },
359 #[error("nothing to undo")]
361 NothingToUndo,
362}
363
364#[derive(Debug, Error)]
366pub enum PrintError {
367 #[error("cannot parse page selection: {spec}")]
369 InvalidSelection {
370 spec: String,
372 },
373 #[error("invalid page range {start}-{end}")]
375 InvalidRange {
376 start: usize,
378 end: usize,
380 },
381 #[error("page {page} is out of range: document has {count} pages")]
383 PageOutOfRange {
384 page: usize,
386 count: usize,
388 },
389 #[error("copies must be at least one, got {copies}")]
391 InvalidCopies {
392 copies: usize,
394 },
395 #[error("scale factor must be positive and finite, got {scale}")]
397 InvalidScale {
398 scale: f64,
400 },
401 #[error("paper size must be positive, got {w}x{h} mm")]
403 InvalidPaper {
404 w: f64,
406 h: f64,
408 },
409}