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