Skip to main content

xls_rs/
error.rs

1//! Enhanced error types with context information
2
3use std::fmt;
4use thiserror::Error;
5
6/// Error with file and location context
7#[derive(Error, Debug)]
8pub struct XlsRsError {
9    pub kind: ErrorKind,
10    pub context: ErrorContext,
11}
12
13impl fmt::Display for XlsRsError {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        write!(f, "{}", self.kind)?;
16        if let Some(file) = &self.context.file {
17            write!(f, " in file '{}'", file)?;
18        }
19        if let Some(row) = self.context.row {
20            write!(f, " at row {}", row + 1)?; // 1-indexed for users
21        }
22        if let Some(col) = self.context.column {
23            write!(f, ", column {}", col + 1)?;
24        }
25        if let Some(cell) = &self.context.cell_ref {
26            write!(f, " (cell {})", cell)?;
27        }
28        Ok(())
29    }
30}
31
32
33/// Error context with location information
34#[derive(Debug, Default, Clone)]
35pub struct ErrorContext {
36    /// File path
37    pub file: Option<String>,
38    /// Row number (0-indexed)
39    pub row: Option<usize>,
40    /// Column number (0-indexed)
41    pub column: Option<usize>,
42    /// Cell reference (e.g., "A1")
43    pub cell_ref: Option<String>,
44    /// Column name
45    pub column_name: Option<String>,
46}
47
48impl ErrorContext {
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    pub fn with_file(mut self, file: &str) -> Self {
54        self.file = Some(file.to_string());
55        self
56    }
57
58    pub fn with_row(mut self, row: usize) -> Self {
59        self.row = Some(row);
60        self
61    }
62
63    pub fn with_column(mut self, col: usize) -> Self {
64        self.column = Some(col);
65        self
66    }
67
68    pub fn with_cell_ref(mut self, cell: &str) -> Self {
69        self.cell_ref = Some(cell.to_string());
70        self
71    }
72
73    pub fn with_column_name(mut self, name: &str) -> Self {
74        self.column_name = Some(name.to_string());
75        self
76    }
77}
78
79/// Types of errors
80#[derive(Error, Debug)]
81pub enum ErrorKind {
82    #[error("Column '{0}' not found")]
83    ColumnNotFound(String),
84
85    #[error("Invalid cell reference '{0}'")]
86    InvalidCellRef(String),
87
88    #[error("Invalid value '{0}' - expected {1}")]
89    InvalidValue(String, String),
90
91    #[error("Type conversion failed: cannot convert '{0}' to {1}")]
92    TypeConversion(String, String),
93
94    #[error("Division by zero")]
95    DivisionByZero,
96
97    #[error("Invalid formula: {0}")]
98    InvalidFormula(String),
99
100    #[error("File not found: {0}")]
101    FileNotFound(String),
102
103    #[error("Unsupported file format: {0}")]
104    UnsupportedFormat(String),
105
106    #[error("Parse error: {0}")]
107    ParseError(String),
108
109    #[error("Invalid date format: {0}")]
110    InvalidDateFormat(String),
111
112    #[error("Invalid regex pattern: {0}")]
113    InvalidRegex(String),
114
115    #[error("IO error: {0}")]
116    IoError(String),
117
118    #[error("{0}")]
119    Other(String),
120}
121
122impl XlsRsError {
123    pub fn column_not_found(name: &str) -> Self {
124        Self {
125            kind: ErrorKind::ColumnNotFound(name.to_string()),
126            context: ErrorContext::new(),
127        }
128    }
129
130    pub fn invalid_value(value: &str, expected: &str) -> Self {
131        Self {
132            kind: ErrorKind::InvalidValue(value.to_string(), expected.to_string()),
133            context: ErrorContext::new(),
134        }
135    }
136
137    pub fn type_conversion(value: &str, target_type: &str) -> Self {
138        Self {
139            kind: ErrorKind::TypeConversion(value.to_string(), target_type.to_string()),
140            context: ErrorContext::new(),
141        }
142    }
143
144    pub fn with_context(mut self, context: ErrorContext) -> Self {
145        self.context = context;
146        self
147    }
148}
149
150/// Result type alias for xls-rs operations
151pub type XlsRsResult<T> = Result<T, XlsRsError>;
152
153/// Extension trait for adding context to anyhow errors
154pub trait ResultExt<T> {
155    fn with_file_context(self, file: &str) -> anyhow::Result<T>;
156    fn with_row_context(self, file: &str, row: usize) -> anyhow::Result<T>;
157    fn with_cell_context(self, file: &str, row: usize, col: usize) -> anyhow::Result<T>;
158}
159
160impl<T, E: std::error::Error + Send + Sync + 'static> ResultExt<T> for Result<T, E> {
161    fn with_file_context(self, file: &str) -> anyhow::Result<T> {
162        self.map_err(|e| anyhow::anyhow!("{} in file '{}'", e, file))
163    }
164
165    fn with_row_context(self, file: &str, row: usize) -> anyhow::Result<T> {
166        self.map_err(|e| anyhow::anyhow!("{} in file '{}' at row {}", e, file, row + 1))
167    }
168
169    fn with_cell_context(self, file: &str, row: usize, col: usize) -> anyhow::Result<T> {
170        self.map_err(|e| {
171            anyhow::anyhow!(
172                "{} in file '{}' at row {}, column {}",
173                e,
174                file,
175                row + 1,
176                col + 1
177            )
178        })
179    }
180}