1use crate::core::engine::EngineError;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[non_exhaustive]
12pub enum ObjectKind {
13 Sheet,
15 Table,
18 TableColumn,
20 PivotTable,
22 PivotField,
24 Chart,
26 VbaModule,
28}
29
30impl ObjectKind {
31 pub fn as_str(self) -> &'static str {
33 match self {
34 ObjectKind::Sheet => "sheet",
35 ObjectKind::Table => "table",
36 ObjectKind::TableColumn => "table column",
37 ObjectKind::PivotTable => "pivot table",
38 ObjectKind::PivotField => "pivot field",
39 ObjectKind::Chart => "chart",
40 ObjectKind::VbaModule => "VBA module",
41 }
42 }
43}
44
45impl std::fmt::Display for ObjectKind {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.write_str(self.as_str())
48 }
49}
50
51#[derive(Debug, Clone, PartialEq)]
56#[non_exhaustive]
57pub enum Error {
58 NotFound {
60 kind: ObjectKind,
62 name: String,
64 available: Vec<String>,
67 },
68 AlreadyExists {
70 kind: ObjectKind,
72 name: String,
74 },
75 NameTaken {
79 kind: ObjectKind,
81 name: String,
83 },
84 InvalidName {
86 kind: ObjectKind,
88 name: String,
90 reason: String,
92 },
93 OutOfBounds {
95 what: &'static str,
97 index: usize,
99 len: usize,
101 },
102 InvalidRange(String),
104 EmptyWorkbook,
106 LastSheetInWorkbook,
108 DocumentModuleExists,
110 InvalidArgument(String),
117 Xlsx(String),
119 Vba(String),
121 Eval(EngineError),
123}
124
125impl Error {
126 pub fn not_found(kind: ObjectKind, name: impl Into<String>) -> Self {
128 Error::NotFound {
129 kind,
130 name: name.into(),
131 available: Vec::new(),
132 }
133 }
134
135 pub fn not_found_among(
137 kind: ObjectKind,
138 name: impl Into<String>,
139 available: Vec<String>,
140 ) -> Self {
141 Error::NotFound {
142 kind,
143 name: name.into(),
144 available,
145 }
146 }
147}
148
149impl std::fmt::Display for Error {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 Error::NotFound {
153 kind,
154 name,
155 available,
156 } => {
157 write!(f, "{kind} '{name}' not found")?;
158 if !available.is_empty() {
159 write!(f, ". Available {kind}s: {}", available.join(", "))?;
160 }
161 Ok(())
162 }
163 Error::AlreadyExists { kind, name } => write!(f, "{kind} '{name}' already exists"),
164 Error::NameTaken { kind, name } => {
165 write!(f, "{kind} name '{name}' is already taken")
166 }
167 Error::InvalidName { kind, name, reason } => {
168 write!(f, "invalid {kind} name '{name}': {reason}")
169 }
170 Error::OutOfBounds { what, index, len } => {
171 write!(f, "{what} index {index} is out of bounds (sheet has {len})")
172 }
173 Error::InvalidRange(msg) => write!(f, "invalid range: {msg}"),
174 Error::EmptyWorkbook => f.write_str("workbook contains no sheets"),
175 Error::LastSheetInWorkbook => {
176 f.write_str("cannot delete the only sheet in the workbook")
177 }
178 Error::DocumentModuleExists => {
179 f.write_str("that sheet already has a bound document module")
180 }
181 Error::InvalidArgument(msg) => f.write_str(msg),
182 Error::Xlsx(msg) => write!(f, "xlsx error: {msg}"),
183 Error::Vba(msg) => write!(f, "VBA error: {msg}"),
184 Error::Eval(err) => write!(f, "evaluation error: {err}"),
185 }
186 }
187}
188
189impl std::error::Error for Error {
190 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
191 match self {
192 Error::Eval(err) => Some(err),
193 _ => None,
194 }
195 }
196}
197
198impl From<EngineError> for Error {
199 fn from(err: EngineError) -> Self {
200 Error::Eval(err)
201 }
202}
203
204pub type Result<T> = std::result::Result<T, Error>;
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn display_reads_naturally() {
213 let e = Error::not_found(ObjectKind::PivotTable, "Sales");
214 assert_eq!(e.to_string(), "pivot table 'Sales' not found");
215
216 let e = Error::NameTaken {
217 kind: ObjectKind::Sheet,
218 name: "Data".into(),
219 };
220 assert_eq!(e.to_string(), "sheet name 'Data' is already taken");
221 }
222
223 #[test]
224 fn is_a_std_error() {
225 fn assert_std_error<E: std::error::Error>(_: &E) {}
226 assert_std_error(&Error::EmptyWorkbook);
227 let boxed: Box<dyn std::error::Error> = Box::new(Error::EmptyWorkbook);
228 assert_eq!(boxed.to_string(), "workbook contains no sheets");
229 }
230
231 #[test]
232 fn callers_can_match_on_kind_without_parsing_text() {
233 let e = Error::not_found(ObjectKind::Table, "Q1");
234 assert!(matches!(
235 e,
236 Error::NotFound {
237 kind: ObjectKind::Table,
238 ..
239 }
240 ));
241 }
242}