Skip to main content

rightkit_docs/
error.rs

1//! Typed errors with stable snake_case wire codes (the `{ code, message }` envelope `rightkit-pdf`
2//! uses for `PdfEditError`).
3
4use serde::ser::SerializeStruct;
5use serde::{Serialize, Serializer};
6
7/// Everything `read` and `write` can refuse. Every variant has a stable [`DocsError::code`].
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum DocsError {
11    /// The bytes are not a DOCX, XLSX or PDF (legacy OLE `.doc`/`.xls`, PPTX, XLSB, plain text...).
12    #[error("unsupported format: {0}")]
13    UnsupportedFormat(String),
14    /// A macro-enabled package (`.docm`, `.xlsm`, `.dotm`, `.xltm`, or any package holding a VBA
15    /// project). Refused, never opened, never run.
16    #[error("macro-enabled documents are refused ({0})")]
17    MacroEnabled(String),
18    /// Password-protected: an encrypted OOXML container, a zip-level encrypted entry, or an
19    /// encrypted PDF. This crate does not decrypt.
20    #[error("the document is encrypted")]
21    Encrypted,
22    /// The input exceeds the compressed-size cap.
23    #[error("input is {actual} bytes, over the {limit} byte limit")]
24    TooLarge { limit: u64, actual: u64 },
25    /// A zip-bomb guard fired: entry count, decompressed size, or compression ratio.
26    #[error("archive limit exceeded: {0}")]
27    ArchiveLimit(String),
28    /// The structure is not a valid package or part, or it contradicts itself (duplicate entries,
29    /// unsafe entry names, truncated archive, broken XML).
30    #[error("malformed document: {0}")]
31    Malformed(String),
32    /// A DOCTYPE or entity declaration. XML external entities are not supported and the part is
33    /// refused rather than parsed.
34    #[error("forbidden XML construct in {part}: {what}")]
35    ForbiddenXml { part: String, what: &'static str },
36    /// An embedded OLE object or ActiveX control part. Such parts run or embed foreign code, so
37    /// the package is refused: they are never extracted, parsed or followed.
38    #[error("active content refused in {part}: {what}")]
39    ActiveContent { part: String, what: &'static str },
40    /// The target differs from the file the edit was based on.
41    #[error("the target changed since it was read")]
42    SourceChanged,
43    /// `create` over an existing file without `overwrite`.
44    #[error("the target already exists")]
45    TargetExists,
46    /// An edit op on a target that does not exist.
47    #[error("the target does not exist")]
48    TargetMissing,
49    /// A named sheet, page, range or text was not found.
50    #[error("{kind} not found: {name}")]
51    NotFound { kind: &'static str, name: String },
52    /// The op itself is invalid (bad cell address, empty find string, too-long string...).
53    #[error("invalid operation: {0}")]
54    InvalidOp(String),
55    /// A formula the policy refuses: external workbook link, DDE, network fetch function.
56    #[error("formula refused: {0}")]
57    UnsafeFormula(String),
58    /// The op does not apply to this format.
59    #[error("{op} does not apply to a {format} document")]
60    WrongFormat {
61        op: &'static str,
62        format: &'static str,
63    },
64    /// A `rightkit-pdf` refusal; the PDF wire code is in the message.
65    #[error("pdf: {0}")]
66    Pdf(#[from] rightkit_pdf::model::PdfEditError),
67    /// PDF text extraction failed.
68    #[error("pdf text extraction failed: {0}")]
69    PdfExtract(String),
70    /// Reading the source or writing the target failed. Nothing was published.
71    #[error("i/o: {0}")]
72    Io(String),
73    /// The replacement was written but a step after it failed (directory sync); treat the file as
74    /// saved, durability across power loss as not established.
75    #[error("saved, but a step after the replacement failed: {0}")]
76    SavedWithWarning(String),
77}
78
79impl DocsError {
80    /// Stable wire code.
81    pub fn code(&self) -> &'static str {
82        match self {
83            DocsError::UnsupportedFormat(_) => "unsupported_format",
84            DocsError::MacroEnabled(_) => "macro_enabled",
85            DocsError::Encrypted => "encrypted",
86            DocsError::TooLarge { .. } => "too_large",
87            DocsError::ArchiveLimit(_) => "archive_limit",
88            DocsError::Malformed(_) => "malformed",
89            DocsError::ForbiddenXml { .. } => "forbidden_xml",
90            DocsError::ActiveContent { .. } => "active_content",
91            DocsError::SourceChanged => "source_changed",
92            DocsError::TargetExists => "target_exists",
93            DocsError::TargetMissing => "target_missing",
94            DocsError::NotFound { .. } => "not_found",
95            DocsError::InvalidOp(_) => "invalid_op",
96            DocsError::UnsafeFormula(_) => "unsafe_formula",
97            DocsError::WrongFormat { .. } => "wrong_format",
98            DocsError::Pdf(rightkit_pdf::model::PdfEditError::EncryptedDocument) => "encrypted",
99            DocsError::Pdf(_) => "pdf_edit_failed",
100            DocsError::PdfExtract(_) => "pdf_extract_failed",
101            DocsError::Io(_) => "io",
102            DocsError::SavedWithWarning(_) => "saved_with_warning",
103        }
104    }
105}
106
107impl Serialize for DocsError {
108    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
109        let mut state = serializer.serialize_struct("DocsError", 2)?;
110        state.serialize_field("code", self.code())?;
111        state.serialize_field("message", &self.to_string())?;
112        state.end()
113    }
114}
115
116pub type Result<T> = std::result::Result<T, DocsError>;