1use std::{error::Error, fmt};
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17use crate::LiteralValue;
18
19#[non_exhaustive]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
26pub enum ExcelErrorKind {
27 Null,
28 Ref,
29 Name,
30 Value,
31 Div,
32 Na,
33 Num,
34 Error,
35 NImpl,
36 Spill,
37 Calc,
38 Circ,
39 Cancelled,
40}
41
42impl fmt::Display for ExcelErrorKind {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.write_str(match self {
45 Self::Null => "#NULL!",
46 Self::Ref => "#REF!",
47 Self::Name => "#NAME?",
48 Self::Value => "#VALUE!",
49 Self::Div => "#DIV/0!",
50 Self::Na => "#N/A",
51 Self::Num => "#NUM!",
52 Self::Error => "#ERROR!",
53 Self::NImpl => "#N/IMPL!",
54 Self::Spill => "#SPILL!",
55 Self::Calc => "#CALC!",
56 Self::Circ => "#CIRC!",
57 Self::Cancelled => "#CANCELLED!",
58 })
59 }
60}
61
62impl ExcelErrorKind {
63 pub fn try_parse(s: &str) -> Option<Self> {
64 match s.trim().to_ascii_lowercase().as_str() {
65 "#null!" => Some(Self::Null),
66 "#ref!" => Some(Self::Ref),
67 "#name?" => Some(Self::Name),
68 "#value!" => Some(Self::Value),
69 "#div/0!" => Some(Self::Div),
70 "#n/a" => Some(Self::Na),
71 "#num!" => Some(Self::Num),
72 "#error!" => Some(Self::Error),
73 "#n/impl!" => Some(Self::NImpl),
74 "#spill!" => Some(Self::Spill),
75 "#calc!" => Some(Self::Calc),
76 "#circ!" => Some(Self::Circ),
77 "#cancelled!" => Some(Self::Cancelled),
78 _ => None,
79 }
80 }
81
82 pub fn parse(s: &str) -> Self {
83 Self::try_parse(s).unwrap_or(Self::Error)
84 }
85}
86
87#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
92#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
93pub struct ErrorContext {
94 pub row: Option<u32>,
95 pub col: Option<u32>,
96 pub origin_row: Option<u32>,
98 pub origin_col: Option<u32>,
99 pub origin_sheet: Option<String>,
100}
101
102#[non_exhaustive]
104#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
105#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
106pub enum ResourceExhaustionReason {
107 Admission,
108 RetainedMemory,
109 ScratchMemory,
110 WorkUnits,
111 Deadline,
112 GraphVertices,
113 GraphEdges,
114 MaterializationCells,
115 ArithmeticOverflow,
116}
117
118impl ResourceExhaustionReason {
119 pub const fn as_str(self) -> &'static str {
120 match self {
121 Self::Admission => "admission",
122 Self::RetainedMemory => "retained_memory",
123 Self::ScratchMemory => "scratch_memory",
124 Self::WorkUnits => "work_units",
125 Self::Deadline => "deadline",
126 Self::GraphVertices => "graph_vertices",
127 Self::GraphEdges => "graph_edges",
128 Self::MaterializationCells => "materialization_cells",
129 Self::ArithmeticOverflow => "arithmetic_overflow",
130 }
131 }
132}
133
134#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136#[derive(Debug, Clone, PartialEq, Eq, Hash)]
137pub struct ResourceExhaustionDetail {
138 pub reason: ResourceExhaustionReason,
139 pub limit: u64,
140 pub observed: u64,
141 pub request_id: Option<u64>,
142}
143
144#[non_exhaustive]
146#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
147#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
148pub enum PreparationStaleReason {
149 Graph,
150 Authority,
151 Staged,
152 Symbols,
153 Semantic,
154 Provider,
155}
156
157impl PreparationStaleReason {
158 pub const fn as_str(self) -> &'static str {
159 match self {
160 Self::Graph => "graph",
161 Self::Authority => "authority",
162 Self::Staged => "staged",
163 Self::Symbols => "symbols",
164 Self::Semantic => "semantic",
165 Self::Provider => "provider",
166 }
167 }
168}
169
170#[non_exhaustive]
172#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
173#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
174pub enum PlanStaleReason {
175 Engine,
176 Provider,
177 Semantic,
178 Budget,
179 Staged,
180 Symbols,
181 Authority,
182 SpanGeneration,
183 Graph,
184}
185
186impl PlanStaleReason {
187 pub const fn as_str(self) -> &'static str {
188 match self {
189 Self::Engine => "engine",
190 Self::Provider => "provider",
191 Self::Semantic => "semantic",
192 Self::Budget => "budget",
193 Self::Staged => "staged",
194 Self::Symbols => "symbols",
195 Self::Authority => "authority",
196 Self::SpanGeneration => "span_generation",
197 Self::Graph => "graph",
198 }
199 }
200}
201
202#[non_exhaustive]
206#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
207#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
208pub enum ExcelErrorExtra {
209 #[default]
211 None,
212
213 Spill {
215 expected_rows: u32,
216 expected_cols: u32,
217 },
218
219 Resource {
222 detail: Box<ResourceExhaustionDetail>,
223 },
224
225 PreparationStale {
226 reason: PreparationStaleReason,
227 },
228
229 PlanStale {
230 reason: PlanStaleReason,
231 },
232 }
235
236#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
247#[derive(Debug, Clone, PartialEq, Eq, Hash)]
248pub struct ExcelError {
249 pub kind: ExcelErrorKind,
250 pub message: Option<String>,
251 pub context: Option<ErrorContext>,
252 pub extra: ExcelErrorExtra,
253}
254
255impl From<ExcelErrorKind> for ExcelError {
258 fn from(kind: ExcelErrorKind) -> Self {
259 Self {
260 kind,
261 message: None,
262 context: None,
263 extra: ExcelErrorExtra::None,
264 }
265 }
266}
267
268impl ExcelError {
269 pub fn new(kind: ExcelErrorKind) -> Self {
271 kind.into()
272 }
273
274 pub fn with_message<S: Into<String>>(mut self, msg: S) -> Self {
276 self.message = Some(msg.into());
277 self
278 }
279
280 pub fn with_location(mut self, row: u32, col: u32) -> Self {
282 self.context = Some(ErrorContext {
283 row: Some(row),
284 col: Some(col),
285 origin_row: None,
286 origin_col: None,
287 origin_sheet: None,
288 });
289 self
290 }
291
292 pub fn with_origin(mut self, sheet: Option<String>, row: u32, col: u32) -> Self {
294 if let Some(ref mut ctx) = self.context {
295 ctx.origin_sheet = sheet;
296 ctx.origin_row = Some(row);
297 ctx.origin_col = Some(col);
298 } else {
299 self.context = Some(ErrorContext {
300 row: None,
301 col: None,
302 origin_row: Some(row),
303 origin_col: Some(col),
304 origin_sheet: sheet,
305 });
306 }
307 self
308 }
309
310 pub fn with_extra(mut self, extra: ExcelErrorExtra) -> Self {
312 self.extra = extra;
313 self
314 }
315
316 pub fn from_error_string(s: &str) -> Self {
317 match ExcelErrorKind::try_parse(s) {
318 Some(kind) => Self::new(kind),
319 None => {
320 Self::new(ExcelErrorKind::Error).with_message(format!("Unknown error code: {s}"))
321 }
322 }
323 }
324
325 pub fn new_value() -> Self {
326 Self::new(ExcelErrorKind::Value)
327 }
328
329 pub fn new_name() -> Self {
330 Self::new(ExcelErrorKind::Name)
331 }
332
333 pub fn new_div() -> Self {
334 Self::new(ExcelErrorKind::Div)
335 }
336
337 pub fn new_ref() -> Self {
338 Self::new(ExcelErrorKind::Ref)
339 }
340
341 pub fn new_circ() -> Self {
342 Self::new(ExcelErrorKind::Circ)
343 }
344
345 pub fn new_num() -> Self {
346 Self::new(ExcelErrorKind::Num)
347 }
348
349 pub fn new_na() -> Self {
350 Self::new(ExcelErrorKind::Na)
351 }
352}
353
354impl fmt::Display for ExcelError {
357 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358 write!(f, "{}", self.kind)?;
360
361 if let Some(ref msg) = self.message {
363 write!(f, ": {msg}")?;
364 }
365
366 if let Some(ref ctx) = self.context {
368 if let (Some(r), Some(c)) = (ctx.row, ctx.col) {
369 write!(f, " (row {r}, col {c})")?;
370 }
371
372 if let (Some(or), Some(oc)) = (ctx.origin_row, ctx.origin_col) {
374 if ctx.row != Some(or) || ctx.col != Some(oc) {
375 if let Some(ref sheet) = ctx.origin_sheet {
376 write!(f, " [origin: {sheet}!R{or}C{oc}]")?;
377 } else {
378 write!(f, " [origin: R{or}C{oc}]")?;
379 }
380 }
381 }
382 }
383
384 match &self.extra {
386 ExcelErrorExtra::None => {}
387 ExcelErrorExtra::Spill {
388 expected_rows,
389 expected_cols,
390 } => {
391 write!(f, " [spill {expected_rows}×{expected_cols}]")?;
392 }
393 ExcelErrorExtra::Resource { detail } => {
394 write!(
395 f,
396 " [resource {} {}/{}]",
397 detail.reason.as_str(),
398 detail.observed,
399 detail.limit
400 )?;
401 }
402 ExcelErrorExtra::PreparationStale { reason } => {
403 write!(f, " [preparation stale {}]", reason.as_str())?;
404 }
405 ExcelErrorExtra::PlanStale { reason } => {
406 write!(f, " [plan stale {}]", reason.as_str())?;
407 }
408 }
409
410 Ok(())
411 }
412}
413
414impl Error for ExcelError {}
415impl From<ExcelError> for String {
416 fn from(error: ExcelError) -> Self {
417 format!("{error}")
418 }
419}
420impl From<ExcelError> for LiteralValue {
421 fn from(error: ExcelError) -> Self {
422 LiteralValue::Error(error)
423 }
424}
425
426impl PartialEq<str> for ExcelErrorKind {
427 fn eq(&self, other: &str) -> bool {
428 format!("{self}") == other
429 }
430}
431
432impl PartialEq<&str> for ExcelError {
433 fn eq(&self, other: &&str) -> bool {
434 self.kind.to_string() == *other
435 }
436}
437
438impl PartialEq<str> for ExcelError {
439 fn eq(&self, other: &str) -> bool {
440 self.kind.to_string() == other
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn parse_known_error_kind() {
450 assert_eq!(ExcelErrorKind::parse("#DIV/0!"), ExcelErrorKind::Div);
451 assert_eq!(ExcelErrorKind::parse("#n/a"), ExcelErrorKind::Na);
452 }
453
454 #[test]
455 fn parse_unknown_error_kind_falls_back() {
456 assert_eq!(ExcelErrorKind::parse("#BOGUS!"), ExcelErrorKind::Error);
457 let err = ExcelError::from_error_string("#BOGUS!");
458 assert_eq!(err.kind, ExcelErrorKind::Error);
459 assert!(err.message.unwrap_or_default().contains("#BOGUS!"));
460 }
461
462 #[test]
463 fn preparation_stale_reason_has_stable_snake_case_names() {
464 assert_eq!(PreparationStaleReason::Graph.as_str(), "graph");
465 assert_eq!(PreparationStaleReason::Authority.as_str(), "authority");
466 assert_eq!(PreparationStaleReason::Staged.as_str(), "staged");
467 assert_eq!(PreparationStaleReason::Symbols.as_str(), "symbols");
468 assert_eq!(PreparationStaleReason::Semantic.as_str(), "semantic");
469 assert_eq!(PreparationStaleReason::Provider.as_str(), "provider");
470 }
471
472 #[test]
473 fn plan_stale_reason_has_stable_snake_case_names() {
474 assert_eq!(PlanStaleReason::Engine.as_str(), "engine");
475 assert_eq!(PlanStaleReason::Provider.as_str(), "provider");
476 assert_eq!(PlanStaleReason::Semantic.as_str(), "semantic");
477 assert_eq!(PlanStaleReason::Budget.as_str(), "budget");
478 assert_eq!(PlanStaleReason::Staged.as_str(), "staged");
479 assert_eq!(PlanStaleReason::Symbols.as_str(), "symbols");
480 assert_eq!(PlanStaleReason::Authority.as_str(), "authority");
481 assert_eq!(PlanStaleReason::SpanGeneration.as_str(), "span_generation");
482 assert_eq!(PlanStaleReason::Graph.as_str(), "graph");
483 }
484}