formualizer_common/
error.rs

1//! Excel-style error representation that is both ergonomic **now**
2//! *and* flexible enough to grow new, data-rich variants later.
3//!
4//! - **`ExcelErrorKind`** : the canonical set of Excel error codes  
5//! - **`ErrorContext`**   : lightweight, sheet-agnostic location info  
6//! - **`ExcelErrorExtra`**: per-kind “extension slot” (e.g. `Spill`)  
7//! - **`ExcelError`**     : one struct that glues the three together
8//!
9//! When a future error needs its own payload, just add another variant
10//! to `ExcelErrorExtra`; existing code does not break.
11
12use std::{error::Error, fmt};
13
14use crate::LiteralValue;
15
16/// All recognised Excel error codes.
17///
18/// **Note:** names are CamelCase (idiomatic Rust) while `Display`
19/// renders them exactly as Excel shows them (`#DIV/0!`, …).
20#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
21pub enum ExcelErrorKind {
22    Null,
23    Ref,
24    Name,
25    Value,
26    Div,
27    Na,
28    Num,
29    Error,
30    NImpl,
31    Spill,
32    Calc,
33    Circ,
34    Cancelled,
35}
36
37impl fmt::Display for ExcelErrorKind {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.write_str(match self {
40            Self::Null => "#NULL!",
41            Self::Ref => "#REF!",
42            Self::Name => "#NAME?",
43            Self::Value => "#VALUE!",
44            Self::Div => "#DIV/0!",
45            Self::Na => "#N/A",
46            Self::Num => "#NUM!",
47            Self::Error => "#ERROR!",
48            Self::NImpl => "#N/IMPL!",
49            Self::Spill => "#SPILL!",
50            Self::Calc => "#CALC!",
51            Self::Circ => "#CIRC!",
52            Self::Cancelled => "#CANCELLED!",
53        })
54    }
55}
56
57impl ExcelErrorKind {
58    pub fn parse(s: &str) -> Self {
59        match s.trim().to_ascii_lowercase().as_str() {
60            "#null!" => Self::Null,
61            "#ref!" => Self::Ref,
62            "#name?" => Self::Name,
63            "#value!" => Self::Value,
64            "#div/0!" => Self::Div,
65            "#n/a" => Self::Na,
66            "#num!" => Self::Num,
67            "#error!" => Self::Error,
68            "#n/impl!" => Self::NImpl,
69            "#spill!" => Self::Spill,
70            "#calc!" => Self::Calc,
71            "#circ!" => Self::Circ,
72            "#cancelled!" => Self::Cancelled,
73            _ => panic!("Unknown error kind '{s}'"),
74        }
75    }
76}
77
78/// Generic, lightweight metadata that *any* error may carry.
79///
80/// Keep this minimal—anything only one error kind needs belongs in
81/// `ExcelErrorExtra`.
82#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
83pub struct ErrorContext {
84    pub row: Option<u32>,
85    pub col: Option<u32>,
86    // Origin location where the error first occurred (if different from row/col)
87    pub origin_row: Option<u32>,
88    pub origin_col: Option<u32>,
89    pub origin_sheet: Option<String>,
90}
91
92/// Kind-specific payloads (“extension slot”).
93///
94/// Only variants that need extra data get it—rest stay at `None`.
95#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
96pub enum ExcelErrorExtra {
97    /// No additional payload (the vast majority of errors).
98    #[default]
99    None,
100
101    /// `#SPILL!` – information about the intended spill size.
102    Spill {
103        expected_rows: u32,
104        expected_cols: u32,
105    },
106    // --- Add future custom payloads below -------------------------------
107    // AnotherKind { … },
108}
109
110/// The single struct your API passes around.
111///
112/// It combines:
113/// * **kind**   – the mandatory Excel error code
114/// * **message**– optional human explanation
115/// * **context**– generic location†
116/// * **extra**  – optional, kind-specific data
117///
118/// † If you *never* need row/col you can build the value with
119///   `ExcelError::from(kind)`, which sets `context = None`.
120#[derive(Debug, Clone, PartialEq, Eq, Hash)]
121pub struct ExcelError {
122    pub kind: ExcelErrorKind,
123    pub message: Option<String>,
124    pub context: Option<ErrorContext>,
125    pub extra: ExcelErrorExtra,
126}
127
128/* ───────────────────── Constructors & helpers ─────────────────────── */
129
130impl From<ExcelErrorKind> for ExcelError {
131    fn from(kind: ExcelErrorKind) -> Self {
132        Self {
133            kind,
134            message: None,
135            context: None,
136            extra: ExcelErrorExtra::None,
137        }
138    }
139}
140
141impl ExcelError {
142    /// Basic constructor (no message, no location, no extra).
143    pub fn new(kind: ExcelErrorKind) -> Self {
144        kind.into()
145    }
146
147    /// Attach a human-readable explanation.
148    pub fn with_message<S: Into<String>>(mut self, msg: S) -> Self {
149        self.message = Some(msg.into());
150        self
151    }
152
153    /// Attach generic row/column coordinates.
154    pub fn with_location(mut self, row: u32, col: u32) -> Self {
155        self.context = Some(ErrorContext {
156            row: Some(row),
157            col: Some(col),
158            origin_row: None,
159            origin_col: None,
160            origin_sheet: None,
161        });
162        self
163    }
164
165    /// Attach origin location where the error first occurred.
166    pub fn with_origin(mut self, sheet: Option<String>, row: u32, col: u32) -> Self {
167        if let Some(ref mut ctx) = self.context {
168            ctx.origin_sheet = sheet;
169            ctx.origin_row = Some(row);
170            ctx.origin_col = Some(col);
171        } else {
172            self.context = Some(ErrorContext {
173                row: None,
174                col: None,
175                origin_row: Some(row),
176                origin_col: Some(col),
177                origin_sheet: sheet,
178            });
179        }
180        self
181    }
182
183    /// Attach kind-specific extra data.
184    pub fn with_extra(mut self, extra: ExcelErrorExtra) -> Self {
185        self.extra = extra;
186        self
187    }
188
189    pub fn from_error_string(s: &str) -> Self {
190        let kind = ExcelErrorKind::parse(s);
191        Self::new(kind)
192    }
193
194    pub fn new_value() -> Self {
195        Self::new(ExcelErrorKind::Value)
196    }
197
198    pub fn new_name() -> Self {
199        Self::new(ExcelErrorKind::Name)
200    }
201
202    pub fn new_div() -> Self {
203        Self::new(ExcelErrorKind::Div)
204    }
205
206    pub fn new_ref() -> Self {
207        Self::new(ExcelErrorKind::Ref)
208    }
209
210    pub fn new_circ() -> Self {
211        Self::new(ExcelErrorKind::Circ)
212    }
213
214    pub fn new_num() -> Self {
215        Self::new(ExcelErrorKind::Num)
216    }
217
218    pub fn new_na() -> Self {
219        Self::new(ExcelErrorKind::Na)
220    }
221}
222
223/* ───────────────────────── Display / Error ────────────────────────── */
224
225impl fmt::Display for ExcelError {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        // Start with the canonical code:
228        write!(f, "{}", self.kind)?;
229
230        // Optional human message.
231        if let Some(ref msg) = self.message {
232            write!(f, ": {msg}")?;
233        }
234
235        // Optional row/col context.
236        if let Some(ref ctx) = self.context {
237            if let (Some(r), Some(c)) = (ctx.row, ctx.col) {
238                write!(f, " (row {r}, col {c})")?;
239            }
240
241            // Show origin if different from the evaluation location
242            if let (Some(or), Some(oc)) = (ctx.origin_row, ctx.origin_col) {
243                if ctx.origin_row != ctx.row || ctx.origin_col != ctx.col {
244                    if let Some(ref sheet) = ctx.origin_sheet {
245                        write!(f, " [origin: {sheet}!R{or}C{oc}]")?;
246                    } else {
247                        write!(f, " [origin: R{or}C{oc}]")?;
248                    }
249                }
250            }
251        }
252
253        // Optional kind-specific payload - keep it terse for logs.
254        match &self.extra {
255            ExcelErrorExtra::None => {}
256            ExcelErrorExtra::Spill {
257                expected_rows,
258                expected_cols,
259            } => {
260                write!(f, " [spill {expected_rows}×{expected_cols}]")?;
261            }
262        }
263
264        Ok(())
265    }
266}
267
268impl Error for ExcelError {}
269impl From<ExcelError> for String {
270    fn from(error: ExcelError) -> Self {
271        format!("{error}")
272    }
273}
274impl From<ExcelError> for LiteralValue {
275    fn from(error: ExcelError) -> Self {
276        LiteralValue::Error(error)
277    }
278}
279
280impl PartialEq<str> for ExcelErrorKind {
281    fn eq(&self, other: &str) -> bool {
282        format!("{self}") == other
283    }
284}
285
286impl PartialEq<&str> for ExcelError {
287    fn eq(&self, other: &&str) -> bool {
288        self.kind.to_string() == *other
289    }
290}
291
292impl PartialEq<str> for ExcelError {
293    fn eq(&self, other: &str) -> bool {
294        self.kind.to_string() == other
295    }
296}