use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("invalid cell reference: {0}")]
InvalidCellReference(String),
#[error("invalid row number: {0}")]
InvalidRowNumber(u32),
#[error("invalid column number: {0}")]
InvalidColumnNumber(u32),
#[error("sheet '{name}' does not exist")]
SheetNotFound { name: String },
#[error("sheet '{name}' already exists")]
SheetAlreadyExists { name: String },
#[error("invalid sheet name: {0}")]
InvalidSheetName(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("ZIP error: {0}")]
Zip(String),
#[error("XML parse error: {0}")]
XmlParse(String),
#[error("XML deserialization error: {0}")]
XmlDeserialize(String),
#[error("column width {width} exceeds maximum {max}")]
ColumnWidthExceeded { width: f64, max: f64 },
#[error("row height {height} exceeds maximum {max}")]
RowHeightExceeded { height: f64, max: f64 },
#[error("cell value too long: {length} characters (max {max})")]
CellValueTooLong { length: usize, max: usize },
#[error("style not found: {id}")]
StyleNotFound { id: u32 },
#[error("cell styles exceeded maximum ({max})")]
CellStylesExceeded { max: usize },
#[error("row {row} has already been written (must write rows in ascending order)")]
StreamRowAlreadyWritten { row: u32 },
#[error("stream writer already finished")]
StreamAlreadyFinished,
#[error("cannot set column width after rows have been written")]
StreamColumnsAfterRows,
#[error("merge cell range '{new}' overlaps with existing range '{existing}'")]
MergeCellOverlap { new: String, existing: String },
#[error("merge cell range '{0}' not found")]
MergeCellNotFound(String),
#[error("invalid defined name: {0}")]
InvalidDefinedName(String),
#[error("defined name '{name}' not found")]
DefinedNameNotFound { name: String },
#[error("circular reference detected at {cell}")]
CircularReference { cell: String },
#[error("unknown function: {name}")]
UnknownFunction { name: String },
#[error("function {name} expects {expected} arguments, got {got}")]
WrongArgCount {
name: String,
expected: String,
got: usize,
},
#[error("formula evaluation error: {0}")]
FormulaError(String),
#[error("pivot table '{name}' not found")]
PivotTableNotFound { name: String },
#[error("pivot table '{name}' already exists")]
PivotTableAlreadyExists { name: String },
#[error("table '{name}' not found")]
TableNotFound { name: String },
#[error("table '{name}' already exists")]
TableAlreadyExists { name: String },
#[error("invalid source range: {0}")]
InvalidSourceRange(String),
#[error("slicer '{name}' not found")]
SlicerNotFound { name: String },
#[error("slicer '{name}' already exists")]
SlicerAlreadyExists { name: String },
#[error("column '{column}' not found in table '{table}'")]
TableColumnNotFound { table: String, column: String },
#[error("unsupported image format: {format}")]
UnsupportedImageFormat { format: String },
#[error("file is encrypted, password required")]
FileEncrypted,
#[error("incorrect password")]
IncorrectPassword,
#[error("unsupported encryption method: {0}")]
UnsupportedEncryption(String),
#[error("outline level {level} exceeds maximum {max}")]
OutlineLevelExceeded { level: u8, max: u8 },
#[error("invalid merge cell reference: {0}")]
InvalidMergeCellReference(String),
#[error("invalid reference: {reference}")]
InvalidReference { reference: String },
#[error("invalid argument: {0}")]
InvalidArgument(String),
#[error("unsupported file extension: {0}")]
UnsupportedFileExtension(String),
#[error("ZIP decompressed size {size} bytes exceeds limit of {limit} bytes")]
ZipSizeExceeded { size: u64, limit: u64 },
#[error("ZIP entry count {count} exceeds limit of {limit}")]
ZipEntryCountExceeded { count: usize, limit: usize },
#[error("threaded comment '{id}' not found")]
ThreadedCommentNotFound { id: String },
#[error("no chart found at cell '{cell}' on sheet '{sheet}'")]
ChartNotFound { sheet: String, cell: String },
#[error("no picture found at cell '{cell}' on sheet '{sheet}'")]
PictureNotFound { sheet: String, cell: String },
#[error("internal error: {0}")]
Internal(String),
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display_invalid_cell_reference() {
let err = Error::InvalidCellReference("XYZ0".to_string());
assert_eq!(err.to_string(), "invalid cell reference: XYZ0");
}
#[test]
fn test_error_display_sheet_not_found() {
let err = Error::SheetNotFound {
name: "Missing".to_string(),
};
assert_eq!(err.to_string(), "sheet 'Missing' does not exist");
}
#[test]
fn test_error_display_sheet_already_exists() {
let err = Error::SheetAlreadyExists {
name: "Sheet1".to_string(),
};
assert_eq!(err.to_string(), "sheet 'Sheet1' already exists");
}
#[test]
fn test_error_display_invalid_sheet_name() {
let err = Error::InvalidSheetName("bad[name".to_string());
assert_eq!(err.to_string(), "invalid sheet name: bad[name");
}
#[test]
fn test_error_display_invalid_row_number() {
let err = Error::InvalidRowNumber(0);
assert_eq!(err.to_string(), "invalid row number: 0");
}
#[test]
fn test_error_display_invalid_column_number() {
let err = Error::InvalidColumnNumber(99999);
assert_eq!(err.to_string(), "invalid column number: 99999");
}
#[test]
fn test_error_display_io() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
let err = Error::Io(io_err);
assert_eq!(err.to_string(), "I/O error: gone");
}
#[test]
fn test_error_display_zip() {
let err = Error::Zip("corrupted archive".to_string());
assert_eq!(err.to_string(), "ZIP error: corrupted archive");
}
#[test]
fn test_error_display_xml_parse() {
let err = Error::XmlParse("unexpected EOF".to_string());
assert_eq!(err.to_string(), "XML parse error: unexpected EOF");
}
#[test]
fn test_error_display_xml_deserialize() {
let err = Error::XmlDeserialize("missing attribute".to_string());
assert_eq!(
err.to_string(),
"XML deserialization error: missing attribute"
);
}
#[test]
fn test_error_display_cell_value_too_long() {
let err = Error::CellValueTooLong {
length: 40000,
max: 32767,
};
assert_eq!(
err.to_string(),
"cell value too long: 40000 characters (max 32767)"
);
}
#[test]
fn test_error_display_outline_level_exceeded() {
let err = Error::OutlineLevelExceeded { level: 8, max: 7 };
assert_eq!(err.to_string(), "outline level 8 exceeds maximum 7");
}
#[test]
fn test_error_display_invalid_merge_cell_reference() {
let err = Error::InvalidMergeCellReference("bad ref".to_string());
assert_eq!(err.to_string(), "invalid merge cell reference: bad ref");
}
#[test]
fn test_error_display_unsupported_file_extension() {
let err = Error::UnsupportedFileExtension("csv".to_string());
assert_eq!(err.to_string(), "unsupported file extension: csv");
}
#[test]
fn test_error_display_internal() {
let err = Error::Internal("something went wrong".to_string());
assert_eq!(err.to_string(), "internal error: something went wrong");
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
let err: Error = io_err.into();
assert!(matches!(err, Error::Io(_)));
}
#[test]
fn test_error_is_send_and_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<Error>();
assert_sync::<Error>();
}
}