Skip to main content

crabxl_core/
error.rs

1use std::{error::Error as StdError, fmt};
2
3/// A result with a context-rich spreadsheet error.
4pub type Result<T> = std::result::Result<T, Error>;
5
6/// Stable categories suitable for matching in Rust or language adapters.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum ErrorKind {
10    /// Input/output failed.
11    Io,
12    /// The ZIP container could not be read.
13    Archive,
14    /// XML syntax or namespace resolution failed.
15    Xml,
16    /// Spreadsheet content violates its format or coordinate rules.
17    InvalidData,
18    /// A feature is not implemented by the selected mode yet.
19    Unsupported,
20    /// A configured resource budget was exceeded.
21    LimitExceeded,
22    /// No visible sheet is available for workbook serialization.
23    NoVisibleSheet,
24    /// Managed operation allocations exceeded the selected memory allowance.
25    MemoryBudgetExceeded,
26    /// The requested worksheet does not exist.
27    SheetNotFound,
28    /// An operation conflicts with a resource or sequential-mode state.
29    InvalidState,
30}
31
32/// A spreadsheet error retaining the affected part, cell, and underlying cause.
33/// Details are allocated only on failure, keeping successful Result values small.
34pub struct Error {
35    details: Box<ErrorDetails>,
36}
37
38struct ErrorDetails {
39    kind: ErrorKind,
40    message: String,
41    part: Option<String>,
42    cell: Option<crate::CellAddress>,
43    source: Option<Box<dyn StdError + Send + Sync>>,
44}
45
46impl Error {
47    /// Construct an error without an underlying cause.
48    #[cold]
49    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
50        Self {
51            details: Box::new(ErrorDetails {
52                kind,
53                message: message.into(),
54                part: None,
55                cell: None,
56                source: None,
57            }),
58        }
59    }
60
61    /// Preserve a concrete cause without exposing format-specific types in core.
62    #[cold]
63    pub fn caused_by(
64        kind: ErrorKind,
65        message: impl Into<String>,
66        source: impl StdError + Send + Sync + 'static,
67    ) -> Self {
68        let mut error = Self::new(kind, message);
69        error.details.source = Some(Box::new(source));
70        error
71    }
72
73    /// Attach the affected archive part.
74    pub fn with_part(mut self, part: impl Into<String>) -> Self {
75        self.details.part = Some(part.into());
76        self
77    }
78
79    /// Attach the affected cell coordinate.
80    pub fn with_cell(mut self, cell: crate::CellAddress) -> Self {
81        self.details.cell = Some(cell);
82        self
83    }
84
85    /// Return the matchable error category.
86    pub fn kind(&self) -> ErrorKind {
87        self.details.kind
88    }
89    /// Return the affected archive part, when known.
90    pub fn part(&self) -> Option<&str> {
91        self.details.part.as_deref()
92    }
93    /// Return the affected cell, when known.
94    pub fn cell(&self) -> Option<crate::CellAddress> {
95        self.details.cell
96    }
97}
98
99impl fmt::Display for Error {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        write!(f, "{}", self.details.message)?;
102        if let Some(part) = &self.details.part {
103            write!(f, " [part: {part}]")?;
104        }
105        if let Some(cell) = self.details.cell {
106            write!(f, " [cell: {cell}]")?;
107        }
108        Ok(())
109    }
110}
111
112impl StdError for Error {
113    fn source(&self) -> Option<&(dyn StdError + 'static)> {
114        self.details
115            .source
116            .as_deref()
117            .map(|e| e as &(dyn StdError + 'static))
118    }
119}
120
121impl fmt::Debug for Error {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        f.debug_struct("Error")
124            .field("kind", &self.details.kind)
125            .field("message", &self.details.message)
126            .field("part", &self.details.part)
127            .field("cell", &self.details.cell)
128            .field("source", &self.details.source)
129            .finish()
130    }
131}