1use thiserror::Error;
7
8#[derive(Error, Debug)]
10pub enum Error {
11 #[error("invalid cell reference: {0}")]
13 InvalidCellReference(String),
14
15 #[error("invalid row number: {0}")]
17 InvalidRowNumber(u32),
18
19 #[error("invalid column number: {0}")]
21 InvalidColumnNumber(u32),
22
23 #[error("sheet '{name}' does not exist")]
25 SheetNotFound { name: String },
26
27 #[error("sheet '{name}' already exists")]
29 SheetAlreadyExists { name: String },
30
31 #[error("invalid sheet name: {0}")]
33 InvalidSheetName(String),
34
35 #[error("I/O error: {0}")]
37 Io(#[from] std::io::Error),
38
39 #[error("ZIP error: {0}")]
41 Zip(String),
42
43 #[error("XML parse error: {0}")]
45 XmlParse(String),
46
47 #[error("XML deserialization error: {0}")]
49 XmlDeserialize(String),
50
51 #[error("column width {width} exceeds maximum {max}")]
53 ColumnWidthExceeded { width: f64, max: f64 },
54
55 #[error("row height {height} exceeds maximum {max}")]
57 RowHeightExceeded { height: f64, max: f64 },
58
59 #[error("cell value too long: {length} characters (max {max})")]
61 CellValueTooLong { length: usize, max: usize },
62
63 #[error("style not found: {id}")]
65 StyleNotFound { id: u32 },
66
67 #[error("cell styles exceeded maximum ({max})")]
69 CellStylesExceeded { max: usize },
70
71 #[error("row {row} has already been written (must write rows in ascending order)")]
73 StreamRowAlreadyWritten { row: u32 },
74
75 #[error("stream writer already finished")]
77 StreamAlreadyFinished,
78
79 #[error("cannot set column width after rows have been written")]
81 StreamColumnsAfterRows,
82
83 #[error("merge cell range '{new}' overlaps with existing range '{existing}'")]
85 MergeCellOverlap { new: String, existing: String },
86
87 #[error("merge cell range '{0}' not found")]
89 MergeCellNotFound(String),
90
91 #[error("invalid defined name: {0}")]
93 InvalidDefinedName(String),
94
95 #[error("defined name '{name}' not found")]
97 DefinedNameNotFound { name: String },
98
99 #[error("circular reference detected at {cell}")]
101 CircularReference { cell: String },
102
103 #[error("unknown function: {name}")]
105 UnknownFunction { name: String },
106
107 #[error("function {name} expects {expected} arguments, got {got}")]
109 WrongArgCount {
110 name: String,
111 expected: String,
112 got: usize,
113 },
114
115 #[error("formula evaluation error: {0}")]
117 FormulaError(String),
118
119 #[error("pivot table '{name}' not found")]
121 PivotTableNotFound { name: String },
122
123 #[error("pivot table '{name}' already exists")]
125 PivotTableAlreadyExists { name: String },
126
127 #[error("table '{name}' not found")]
129 TableNotFound { name: String },
130
131 #[error("table '{name}' already exists")]
133 TableAlreadyExists { name: String },
134
135 #[error("invalid source range: {0}")]
137 InvalidSourceRange(String),
138
139 #[error("slicer '{name}' not found")]
141 SlicerNotFound { name: String },
142
143 #[error("slicer '{name}' already exists")]
145 SlicerAlreadyExists { name: String },
146
147 #[error("column '{column}' not found in table '{table}'")]
149 TableColumnNotFound { table: String, column: String },
150
151 #[error("unsupported image format: {format}")]
153 UnsupportedImageFormat { format: String },
154
155 #[error("file is encrypted, password required")]
157 FileEncrypted,
158
159 #[error("incorrect password")]
161 IncorrectPassword,
162
163 #[error("unsupported encryption method: {0}")]
165 UnsupportedEncryption(String),
166
167 #[error("outline level {level} exceeds maximum {max}")]
169 OutlineLevelExceeded { level: u8, max: u8 },
170
171 #[error("invalid merge cell reference: {0}")]
173 InvalidMergeCellReference(String),
174
175 #[error("invalid reference: {reference}")]
177 InvalidReference { reference: String },
178
179 #[error("invalid argument: {0}")]
181 InvalidArgument(String),
182
183 #[error("unsupported file extension: {0}")]
185 UnsupportedFileExtension(String),
186
187 #[error("ZIP decompressed size {size} bytes exceeds limit of {limit} bytes")]
189 ZipSizeExceeded { size: u64, limit: u64 },
190
191 #[error("ZIP entry count {count} exceeds limit of {limit}")]
193 ZipEntryCountExceeded { count: usize, limit: usize },
194
195 #[error("threaded comment '{id}' not found")]
197 ThreadedCommentNotFound { id: String },
198
199 #[error("no chart found at cell '{cell}' on sheet '{sheet}'")]
201 ChartNotFound { sheet: String, cell: String },
202
203 #[error("no picture found at cell '{cell}' on sheet '{sheet}'")]
205 PictureNotFound { sheet: String, cell: String },
206
207 #[error("internal error: {0}")]
209 Internal(String),
210}
211
212pub type Result<T> = std::result::Result<T, Error>;
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_error_display_invalid_cell_reference() {
221 let err = Error::InvalidCellReference("XYZ0".to_string());
222 assert_eq!(err.to_string(), "invalid cell reference: XYZ0");
223 }
224
225 #[test]
226 fn test_error_display_sheet_not_found() {
227 let err = Error::SheetNotFound {
228 name: "Missing".to_string(),
229 };
230 assert_eq!(err.to_string(), "sheet 'Missing' does not exist");
231 }
232
233 #[test]
234 fn test_error_display_sheet_already_exists() {
235 let err = Error::SheetAlreadyExists {
236 name: "Sheet1".to_string(),
237 };
238 assert_eq!(err.to_string(), "sheet 'Sheet1' already exists");
239 }
240
241 #[test]
242 fn test_error_display_invalid_sheet_name() {
243 let err = Error::InvalidSheetName("bad[name".to_string());
244 assert_eq!(err.to_string(), "invalid sheet name: bad[name");
245 }
246
247 #[test]
248 fn test_error_display_invalid_row_number() {
249 let err = Error::InvalidRowNumber(0);
250 assert_eq!(err.to_string(), "invalid row number: 0");
251 }
252
253 #[test]
254 fn test_error_display_invalid_column_number() {
255 let err = Error::InvalidColumnNumber(99999);
256 assert_eq!(err.to_string(), "invalid column number: 99999");
257 }
258
259 #[test]
260 fn test_error_display_io() {
261 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
262 let err = Error::Io(io_err);
263 assert_eq!(err.to_string(), "I/O error: gone");
264 }
265
266 #[test]
267 fn test_error_display_zip() {
268 let err = Error::Zip("corrupted archive".to_string());
269 assert_eq!(err.to_string(), "ZIP error: corrupted archive");
270 }
271
272 #[test]
273 fn test_error_display_xml_parse() {
274 let err = Error::XmlParse("unexpected EOF".to_string());
275 assert_eq!(err.to_string(), "XML parse error: unexpected EOF");
276 }
277
278 #[test]
279 fn test_error_display_xml_deserialize() {
280 let err = Error::XmlDeserialize("missing attribute".to_string());
281 assert_eq!(
282 err.to_string(),
283 "XML deserialization error: missing attribute"
284 );
285 }
286
287 #[test]
288 fn test_error_display_cell_value_too_long() {
289 let err = Error::CellValueTooLong {
290 length: 40000,
291 max: 32767,
292 };
293 assert_eq!(
294 err.to_string(),
295 "cell value too long: 40000 characters (max 32767)"
296 );
297 }
298
299 #[test]
300 fn test_error_display_outline_level_exceeded() {
301 let err = Error::OutlineLevelExceeded { level: 8, max: 7 };
302 assert_eq!(err.to_string(), "outline level 8 exceeds maximum 7");
303 }
304
305 #[test]
306 fn test_error_display_invalid_merge_cell_reference() {
307 let err = Error::InvalidMergeCellReference("bad ref".to_string());
308 assert_eq!(err.to_string(), "invalid merge cell reference: bad ref");
309 }
310
311 #[test]
312 fn test_error_display_unsupported_file_extension() {
313 let err = Error::UnsupportedFileExtension("csv".to_string());
314 assert_eq!(err.to_string(), "unsupported file extension: csv");
315 }
316
317 #[test]
318 fn test_error_display_internal() {
319 let err = Error::Internal("something went wrong".to_string());
320 assert_eq!(err.to_string(), "internal error: something went wrong");
321 }
322
323 #[test]
324 fn test_from_io_error() {
325 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
326 let err: Error = io_err.into();
327 assert!(matches!(err, Error::Io(_)));
328 }
329
330 #[test]
331 fn test_error_is_send_and_sync() {
332 fn assert_send<T: Send>() {}
333 fn assert_sync<T: Sync>() {}
334 assert_send::<Error>();
335 assert_sync::<Error>();
336 }
337}