Skip to main content

sheetkit_core/
error.rs

1//! Error types for the SheetKit core library.
2//!
3//! Provides a comprehensive [`Error`] enum covering all failure modes
4//! encountered when reading, writing, and manipulating Excel workbooks.
5
6use thiserror::Error;
7
8/// The top-level error type for SheetKit.
9#[derive(Error, Debug)]
10pub enum Error {
11    /// The given string is not a valid A1-style cell reference.
12    #[error("invalid cell reference: {0}")]
13    InvalidCellReference(String),
14
15    /// The row number is out of the allowed range (1..=1_048_576).
16    #[error("invalid row number: {0}")]
17    InvalidRowNumber(u32),
18
19    /// The column number is out of the allowed range (1..=16_384).
20    #[error("invalid column number: {0}")]
21    InvalidColumnNumber(u32),
22
23    /// No sheet with the given name exists in the workbook.
24    #[error("sheet '{name}' does not exist")]
25    SheetNotFound { name: String },
26
27    /// A sheet with the given name already exists.
28    #[error("sheet '{name}' already exists")]
29    SheetAlreadyExists { name: String },
30
31    /// The sheet name violates Excel naming rules.
32    #[error("invalid sheet name: {0}")]
33    InvalidSheetName(String),
34
35    /// An underlying I/O error.
36    #[error("I/O error: {0}")]
37    Io(#[from] std::io::Error),
38
39    /// An error originating from the ZIP layer.
40    #[error("ZIP error: {0}")]
41    Zip(String),
42
43    /// An error encountered while parsing XML.
44    #[error("XML parse error: {0}")]
45    XmlParse(String),
46
47    /// An error encountered while deserializing XML into typed structures.
48    #[error("XML deserialization error: {0}")]
49    XmlDeserialize(String),
50
51    /// Column width exceeds the allowed maximum (255).
52    #[error("column width {width} exceeds maximum {max}")]
53    ColumnWidthExceeded { width: f64, max: f64 },
54
55    /// Row height exceeds the allowed maximum (409).
56    #[error("row height {height} exceeds maximum {max}")]
57    RowHeightExceeded { height: f64, max: f64 },
58
59    /// A cell value exceeds the maximum character limit.
60    #[error("cell value too long: {length} characters (max {max})")]
61    CellValueTooLong { length: usize, max: usize },
62
63    /// The style ID was not found in the stylesheet.
64    #[error("style not found: {id}")]
65    StyleNotFound { id: u32 },
66
67    /// Too many cell styles have been registered.
68    #[error("cell styles exceeded maximum ({max})")]
69    CellStylesExceeded { max: usize },
70
71    /// A row has already been written; rows must be written in ascending order.
72    #[error("row {row} has already been written (must write rows in ascending order)")]
73    StreamRowAlreadyWritten { row: u32 },
74
75    /// The stream writer has already been finished.
76    #[error("stream writer already finished")]
77    StreamAlreadyFinished,
78
79    /// Column widths cannot be set after rows have been written.
80    #[error("cannot set column width after rows have been written")]
81    StreamColumnsAfterRows,
82
83    /// Merge cell ranges overlap.
84    #[error("merge cell range '{new}' overlaps with existing range '{existing}'")]
85    MergeCellOverlap { new: String, existing: String },
86
87    /// The specified merge cell range was not found.
88    #[error("merge cell range '{0}' not found")]
89    MergeCellNotFound(String),
90
91    /// The defined name is invalid.
92    #[error("invalid defined name: {0}")]
93    InvalidDefinedName(String),
94
95    /// The specified defined name was not found.
96    #[error("defined name '{name}' not found")]
97    DefinedNameNotFound { name: String },
98
99    /// A circular reference was detected during formula evaluation.
100    #[error("circular reference detected at {cell}")]
101    CircularReference { cell: String },
102
103    /// The formula references an unknown function.
104    #[error("unknown function: {name}")]
105    UnknownFunction { name: String },
106
107    /// A function received the wrong number of arguments.
108    #[error("function {name} expects {expected} arguments, got {got}")]
109    WrongArgCount {
110        name: String,
111        expected: String,
112        got: usize,
113    },
114
115    /// A general formula evaluation error.
116    #[error("formula evaluation error: {0}")]
117    FormulaError(String),
118
119    /// The specified pivot table was not found.
120    #[error("pivot table '{name}' not found")]
121    PivotTableNotFound { name: String },
122
123    /// A pivot table with the given name already exists.
124    #[error("pivot table '{name}' already exists")]
125    PivotTableAlreadyExists { name: String },
126
127    /// The source data range for a pivot table is invalid.
128    #[error("invalid source range: {0}")]
129    InvalidSourceRange(String),
130
131    /// The image format is not supported.
132    #[error("unsupported image format: {format}")]
133    UnsupportedImageFormat { format: String },
134
135    /// The file is encrypted and requires a password to open.
136    #[error("file is encrypted, password required")]
137    FileEncrypted,
138
139    /// The provided password is incorrect.
140    #[error("incorrect password")]
141    IncorrectPassword,
142
143    /// The encryption method is not supported.
144    #[error("unsupported encryption method: {0}")]
145    UnsupportedEncryption(String),
146
147    /// The outline level exceeds the allowed maximum (7).
148    #[error("outline level {level} exceeds maximum {max}")]
149    OutlineLevelExceeded { level: u8, max: u8 },
150
151    /// A merge cell reference format is invalid.
152    #[error("invalid merge cell reference: {0}")]
153    InvalidMergeCellReference(String),
154
155    /// An internal or otherwise unclassified error.
156    #[error("internal error: {0}")]
157    Internal(String),
158}
159
160/// A convenience alias used throughout the crate.
161pub type Result<T> = std::result::Result<T, Error>;
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn test_error_display_invalid_cell_reference() {
169        let err = Error::InvalidCellReference("XYZ0".to_string());
170        assert_eq!(err.to_string(), "invalid cell reference: XYZ0");
171    }
172
173    #[test]
174    fn test_error_display_sheet_not_found() {
175        let err = Error::SheetNotFound {
176            name: "Missing".to_string(),
177        };
178        assert_eq!(err.to_string(), "sheet 'Missing' does not exist");
179    }
180
181    #[test]
182    fn test_error_display_sheet_already_exists() {
183        let err = Error::SheetAlreadyExists {
184            name: "Sheet1".to_string(),
185        };
186        assert_eq!(err.to_string(), "sheet 'Sheet1' already exists");
187    }
188
189    #[test]
190    fn test_error_display_invalid_sheet_name() {
191        let err = Error::InvalidSheetName("bad[name".to_string());
192        assert_eq!(err.to_string(), "invalid sheet name: bad[name");
193    }
194
195    #[test]
196    fn test_error_display_invalid_row_number() {
197        let err = Error::InvalidRowNumber(0);
198        assert_eq!(err.to_string(), "invalid row number: 0");
199    }
200
201    #[test]
202    fn test_error_display_invalid_column_number() {
203        let err = Error::InvalidColumnNumber(99999);
204        assert_eq!(err.to_string(), "invalid column number: 99999");
205    }
206
207    #[test]
208    fn test_error_display_io() {
209        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
210        let err = Error::Io(io_err);
211        assert_eq!(err.to_string(), "I/O error: gone");
212    }
213
214    #[test]
215    fn test_error_display_zip() {
216        let err = Error::Zip("corrupted archive".to_string());
217        assert_eq!(err.to_string(), "ZIP error: corrupted archive");
218    }
219
220    #[test]
221    fn test_error_display_xml_parse() {
222        let err = Error::XmlParse("unexpected EOF".to_string());
223        assert_eq!(err.to_string(), "XML parse error: unexpected EOF");
224    }
225
226    #[test]
227    fn test_error_display_xml_deserialize() {
228        let err = Error::XmlDeserialize("missing attribute".to_string());
229        assert_eq!(
230            err.to_string(),
231            "XML deserialization error: missing attribute"
232        );
233    }
234
235    #[test]
236    fn test_error_display_cell_value_too_long() {
237        let err = Error::CellValueTooLong {
238            length: 40000,
239            max: 32767,
240        };
241        assert_eq!(
242            err.to_string(),
243            "cell value too long: 40000 characters (max 32767)"
244        );
245    }
246
247    #[test]
248    fn test_error_display_outline_level_exceeded() {
249        let err = Error::OutlineLevelExceeded { level: 8, max: 7 };
250        assert_eq!(err.to_string(), "outline level 8 exceeds maximum 7");
251    }
252
253    #[test]
254    fn test_error_display_invalid_merge_cell_reference() {
255        let err = Error::InvalidMergeCellReference("bad ref".to_string());
256        assert_eq!(err.to_string(), "invalid merge cell reference: bad ref");
257    }
258
259    #[test]
260    fn test_error_display_internal() {
261        let err = Error::Internal("something went wrong".to_string());
262        assert_eq!(err.to_string(), "internal error: something went wrong");
263    }
264
265    #[test]
266    fn test_from_io_error() {
267        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
268        let err: Error = io_err.into();
269        assert!(matches!(err, Error::Io(_)));
270    }
271
272    #[test]
273    fn test_error_is_send_and_sync() {
274        fn assert_send<T: Send>() {}
275        fn assert_sync<T: Sync>() {}
276        assert_send::<Error>();
277        assert_sync::<Error>();
278    }
279}