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    /// The target differs from the file the edit was based on.
37    #[error("the target changed since it was read")]
38    SourceChanged,
39    /// `create` over an existing file without `overwrite`.
40    #[error("the target already exists")]
41    TargetExists,
42    /// An edit op on a target that does not exist.
43    #[error("the target does not exist")]
44    TargetMissing,
45    /// A named sheet, page, range or text was not found.
46    #[error("{kind} not found: {name}")]
47    NotFound { kind: &'static str, name: String },
48    /// The op itself is invalid (bad cell address, empty find string, too-long string...).
49    #[error("invalid operation: {0}")]
50    InvalidOp(String),
51    /// A formula the policy refuses: external workbook link, DDE, network fetch function.
52    #[error("formula refused: {0}")]
53    UnsafeFormula(String),
54    /// The op does not apply to this format.
55    #[error("{op} does not apply to a {format} document")]
56    WrongFormat {
57        op: &'static str,
58        format: &'static str,
59    },
60    /// A `rightkit-pdf` refusal; the PDF wire code is in the message.
61    #[error("pdf: {0}")]
62    Pdf(#[from] rightkit_pdf::model::PdfEditError),
63    /// PDF text extraction failed.
64    #[error("pdf text extraction failed: {0}")]
65    PdfExtract(String),
66    /// Reading the source or writing the target failed. Nothing was published.
67    #[error("i/o: {0}")]
68    Io(String),
69    /// The replacement was written but a step after it failed (directory sync); treat the file as
70    /// saved, durability across power loss as not established.
71    #[error("saved, but a step after the replacement failed: {0}")]
72    SavedWithWarning(String),
73}
74
75impl DocsError {
76    /// Stable wire code.
77    pub fn code(&self) -> &'static str {
78        match self {
79            DocsError::UnsupportedFormat(_) => "unsupported_format",
80            DocsError::MacroEnabled(_) => "macro_enabled",
81            DocsError::Encrypted => "encrypted",
82            DocsError::TooLarge { .. } => "too_large",
83            DocsError::ArchiveLimit(_) => "archive_limit",
84            DocsError::Malformed(_) => "malformed",
85            DocsError::ForbiddenXml { .. } => "forbidden_xml",
86            DocsError::SourceChanged => "source_changed",
87            DocsError::TargetExists => "target_exists",
88            DocsError::TargetMissing => "target_missing",
89            DocsError::NotFound { .. } => "not_found",
90            DocsError::InvalidOp(_) => "invalid_op",
91            DocsError::UnsafeFormula(_) => "unsafe_formula",
92            DocsError::WrongFormat { .. } => "wrong_format",
93            DocsError::Pdf(rightkit_pdf::model::PdfEditError::EncryptedDocument) => "encrypted",
94            DocsError::Pdf(_) => "pdf_edit_failed",
95            DocsError::PdfExtract(_) => "pdf_extract_failed",
96            DocsError::Io(_) => "io",
97            DocsError::SavedWithWarning(_) => "saved_with_warning",
98        }
99    }
100}
101
102impl Serialize for DocsError {
103    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
104        let mut state = serializer.serialize_struct("DocsError", 2)?;
105        state.serialize_field("code", self.code())?;
106        state.serialize_field("message", &self.to_string())?;
107        state.end()
108    }
109}
110
111pub type Result<T> = std::result::Result<T, DocsError>;