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 OperationEvidenceRoots,
116 OperationEvidenceTextBytes,
117 DependencyClosureOutputItems,
119 ArithmeticOverflow,
120}
121
122impl ResourceExhaustionReason {
123 pub const fn as_str(self) -> &'static str {
124 match self {
125 Self::Admission => "admission",
126 Self::RetainedMemory => "retained_memory",
127 Self::ScratchMemory => "scratch_memory",
128 Self::WorkUnits => "work_units",
129 Self::Deadline => "deadline",
130 Self::GraphVertices => "graph_vertices",
131 Self::GraphEdges => "graph_edges",
132 Self::MaterializationCells => "materialization_cells",
133 Self::OperationEvidenceRoots => "operation_evidence_roots",
134 Self::OperationEvidenceTextBytes => "operation_evidence_text_bytes",
135 Self::DependencyClosureOutputItems => "dependency_closure_output_items",
136 Self::ArithmeticOverflow => "arithmetic_overflow",
137 }
138 }
139}
140
141#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
143#[derive(Debug, Clone, PartialEq, Eq, Hash)]
144pub struct ResourceExhaustionDetail {
145 pub reason: ResourceExhaustionReason,
146 pub limit: u64,
147 pub observed: u64,
148 pub request_id: Option<u64>,
149}
150
151#[non_exhaustive]
153#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
154#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
155pub enum PreparationStaleReason {
156 Graph,
157 Authority,
158 Staged,
159 Symbols,
160 Semantic,
161 Provider,
162}
163
164impl PreparationStaleReason {
165 pub const fn as_str(self) -> &'static str {
166 match self {
167 Self::Graph => "graph",
168 Self::Authority => "authority",
169 Self::Staged => "staged",
170 Self::Symbols => "symbols",
171 Self::Semantic => "semantic",
172 Self::Provider => "provider",
173 }
174 }
175}
176
177#[non_exhaustive]
179#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
180#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
181pub enum PlanStaleReason {
182 Engine,
183 Provider,
184 Semantic,
185 Budget,
186 Staged,
187 Symbols,
188 Authority,
189 SpanGeneration,
190 Graph,
191}
192
193impl PlanStaleReason {
194 pub const fn as_str(self) -> &'static str {
195 match self {
196 Self::Engine => "engine",
197 Self::Provider => "provider",
198 Self::Semantic => "semantic",
199 Self::Budget => "budget",
200 Self::Staged => "staged",
201 Self::Symbols => "symbols",
202 Self::Authority => "authority",
203 Self::SpanGeneration => "span_generation",
204 Self::Graph => "graph",
205 }
206 }
207}
208
209#[non_exhaustive]
213#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
214#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
215pub enum ExcelErrorExtra {
216 #[default]
218 None,
219
220 RecalcJobActive,
222
223 FunctionPanicked {
225 function: String,
226 message: Option<String>,
227 },
228
229 EnginePoisoned,
231
232 Spill {
234 expected_rows: u32,
235 expected_cols: u32,
236 },
237
238 Resource {
241 detail: Box<ResourceExhaustionDetail>,
242 },
243
244 PreparationStale {
245 reason: PreparationStaleReason,
246 },
247
248 PlanStale {
249 reason: PlanStaleReason,
250 },
251 }
254
255#[derive(Clone, PartialEq, Eq)]
266pub struct ExcelError {
267 pub kind: ExcelErrorKind,
268 payload: Option<Box<ExcelErrorPayload>>,
270}
271
272#[derive(Clone, PartialEq, Eq, Default)]
273struct ExcelErrorPayload {
274 message: Option<String>,
275 context: Option<ErrorContext>,
276 extra: ExcelErrorExtra,
277}
278
279impl fmt::Debug for ExcelError {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 f.debug_struct("ExcelError")
283 .field("kind", &self.kind)
284 .field("message", &self.message())
285 .field("context", &self.context())
286 .field("extra", self.extra())
287 .finish()
288 }
289}
290
291impl std::hash::Hash for ExcelError {
292 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
293 self.kind.hash(state);
294 self.message().hash(state);
295 self.context().hash(state);
296 self.extra().hash(state);
297 }
298}
299
300#[cfg(feature = "serde")]
301impl Serialize for ExcelError {
302 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
303 use serde::ser::SerializeStruct;
304 let mut fields = serializer.serialize_struct("ExcelError", 4)?;
305 fields.serialize_field("kind", &self.kind)?;
306 fields.serialize_field("message", &self.message())?;
307 fields.serialize_field("context", &self.context())?;
308 fields.serialize_field("extra", self.extra())?;
309 fields.end()
310 }
311}
312
313#[cfg(feature = "serde")]
314impl<'de> Deserialize<'de> for ExcelError {
315 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
316 #[derive(Deserialize)]
317 #[serde(rename = "ExcelError")]
318 struct Fields {
319 kind: ExcelErrorKind,
320 message: Option<String>,
321 context: Option<ErrorContext>,
322 extra: ExcelErrorExtra,
323 }
324 let fields = Fields::deserialize(deserializer)?;
325 let mut error = ExcelError::new(fields.kind);
326 error.set_message(fields.message);
327 error.set_context(fields.context);
328 error.set_extra(fields.extra);
329 Ok(error)
330 }
331}
332
333impl From<ExcelErrorKind> for ExcelError {
336 fn from(kind: ExcelErrorKind) -> Self {
337 Self {
338 kind,
339 payload: None,
340 }
341 }
342}
343
344impl ExcelError {
345 pub fn from_function_panic(function: &str, payload: &(dyn std::any::Any + Send)) -> Self {
347 let message = payload
348 .downcast_ref::<&str>()
349 .map(|s| (*s).to_owned())
350 .or_else(|| payload.downcast_ref::<String>().cloned());
351 Self::new(ExcelErrorKind::Value)
352 .with_message("Function callback panicked")
353 .with_extra(ExcelErrorExtra::FunctionPanicked {
354 function: function.to_owned(),
355 message,
356 })
357 }
358
359 pub fn new(kind: ExcelErrorKind) -> Self {
361 kind.into()
362 }
363
364 pub fn message(&self) -> Option<&str> {
366 self.payload
367 .as_ref()
368 .and_then(|payload| payload.message.as_deref())
369 }
370
371 pub fn context(&self) -> Option<&ErrorContext> {
373 self.payload
374 .as_ref()
375 .and_then(|payload| payload.context.as_ref())
376 }
377
378 pub fn extra(&self) -> &ExcelErrorExtra {
380 self.payload
381 .as_ref()
382 .map_or(&ExcelErrorExtra::None, |payload| &payload.extra)
383 }
384
385 fn payload_mut(&mut self) -> &mut ExcelErrorPayload {
386 self.payload.get_or_insert_default()
387 }
388
389 fn discard_empty_payload(&mut self) {
390 if self.payload.as_ref().is_some_and(|payload| {
391 payload.message.is_none()
392 && payload.context.is_none()
393 && payload.extra == ExcelErrorExtra::None
394 }) {
395 self.payload = None;
396 }
397 }
398
399 pub fn set_message(&mut self, message: Option<String>) {
401 if message.is_some() || self.payload.is_some() {
402 self.payload_mut().message = message;
403 self.discard_empty_payload();
404 }
405 }
406
407 pub fn set_context(&mut self, context: Option<ErrorContext>) {
409 if context.is_some() || self.payload.is_some() {
410 self.payload_mut().context = context;
411 self.discard_empty_payload();
412 }
413 }
414
415 pub fn set_extra(&mut self, extra: ExcelErrorExtra) {
417 if extra != ExcelErrorExtra::None || self.payload.is_some() {
418 self.payload_mut().extra = extra;
419 self.discard_empty_payload();
420 }
421 }
422
423 pub fn with_message<S: Into<String>>(mut self, msg: S) -> Self {
425 self.set_message(Some(msg.into()));
426 self
427 }
428
429 pub fn with_location(mut self, row: u32, col: u32) -> Self {
431 self.set_context(Some(ErrorContext {
432 row: Some(row),
433 col: Some(col),
434 origin_row: None,
435 origin_col: None,
436 origin_sheet: None,
437 }));
438 self
439 }
440
441 pub fn with_origin(mut self, sheet: Option<String>, row: u32, col: u32) -> Self {
443 let ctx = self.payload_mut().context.get_or_insert_default();
444 ctx.origin_sheet = sheet;
445 ctx.origin_row = Some(row);
446 ctx.origin_col = Some(col);
447 self
448 }
449
450 pub fn with_extra(mut self, extra: ExcelErrorExtra) -> Self {
452 self.set_extra(extra);
453 self
454 }
455
456 pub fn from_error_string(s: &str) -> Self {
457 match ExcelErrorKind::try_parse(s) {
458 Some(kind) => Self::new(kind),
459 None => {
460 Self::new(ExcelErrorKind::Error).with_message(format!("Unknown error code: {s}"))
461 }
462 }
463 }
464
465 pub fn new_value() -> Self {
466 Self::new(ExcelErrorKind::Value)
467 }
468
469 pub fn new_name() -> Self {
470 Self::new(ExcelErrorKind::Name)
471 }
472
473 pub fn new_div() -> Self {
474 Self::new(ExcelErrorKind::Div)
475 }
476
477 pub fn new_ref() -> Self {
478 Self::new(ExcelErrorKind::Ref)
479 }
480
481 pub fn new_circ() -> Self {
482 Self::new(ExcelErrorKind::Circ)
483 }
484
485 pub fn new_num() -> Self {
486 Self::new(ExcelErrorKind::Num)
487 }
488
489 pub fn new_na() -> Self {
490 Self::new(ExcelErrorKind::Na)
491 }
492}
493
494impl fmt::Display for ExcelError {
497 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
498 write!(f, "{}", self.kind)?;
500
501 if let Some(msg) = self.message() {
503 write!(f, ": {msg}")?;
504 }
505
506 if let Some(ctx) = self.context() {
508 if let (Some(r), Some(c)) = (ctx.row, ctx.col) {
509 write!(f, " (row {r}, col {c})")?;
510 }
511
512 if let (Some(or), Some(oc)) = (ctx.origin_row, ctx.origin_col) {
514 if ctx.row != Some(or) || ctx.col != Some(oc) {
515 if let Some(ref sheet) = ctx.origin_sheet {
516 write!(f, " [origin: {sheet}!R{or}C{oc}]")?;
517 } else {
518 write!(f, " [origin: R{or}C{oc}]")?;
519 }
520 }
521 }
522 }
523
524 match self.extra() {
526 ExcelErrorExtra::None
527 | ExcelErrorExtra::RecalcJobActive
528 | ExcelErrorExtra::FunctionPanicked { .. }
529 | ExcelErrorExtra::EnginePoisoned => {}
530 ExcelErrorExtra::Spill {
531 expected_rows,
532 expected_cols,
533 } => {
534 write!(f, " [spill {expected_rows}×{expected_cols}]")?;
535 }
536 ExcelErrorExtra::Resource { detail } => {
537 write!(
538 f,
539 " [resource {} {}/{}]",
540 detail.reason.as_str(),
541 detail.observed,
542 detail.limit
543 )?;
544 }
545 ExcelErrorExtra::PreparationStale { reason } => {
546 write!(f, " [preparation stale {}]", reason.as_str())?;
547 }
548 ExcelErrorExtra::PlanStale { reason } => {
549 write!(f, " [plan stale {}]", reason.as_str())?;
550 }
551 }
552
553 Ok(())
554 }
555}
556
557impl Error for ExcelError {}
558impl From<ExcelError> for String {
559 fn from(error: ExcelError) -> Self {
560 format!("{error}")
561 }
562}
563impl From<ExcelError> for LiteralValue {
564 fn from(error: ExcelError) -> Self {
565 LiteralValue::Error(error)
566 }
567}
568
569impl PartialEq<str> for ExcelErrorKind {
570 fn eq(&self, other: &str) -> bool {
571 format!("{self}") == other
572 }
573}
574
575impl PartialEq<&str> for ExcelError {
576 fn eq(&self, other: &&str) -> bool {
577 self.kind.to_string() == *other
578 }
579}
580
581impl PartialEq<str> for ExcelError {
582 fn eq(&self, other: &str) -> bool {
583 self.kind.to_string() == other
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590
591 #[test]
592 fn parse_known_error_kind() {
593 assert_eq!(ExcelErrorKind::parse("#DIV/0!"), ExcelErrorKind::Div);
594 assert_eq!(ExcelErrorKind::parse("#n/a"), ExcelErrorKind::Na);
595 }
596
597 #[test]
598 fn parse_unknown_error_kind_falls_back() {
599 assert_eq!(ExcelErrorKind::parse("#BOGUS!"), ExcelErrorKind::Error);
600 let err = ExcelError::from_error_string("#BOGUS!");
601 assert_eq!(err.kind, ExcelErrorKind::Error);
602 assert!(err.message().unwrap_or_default().contains("#BOGUS!"));
603 }
604
605 #[test]
606 fn preparation_stale_reason_has_stable_snake_case_names() {
607 assert_eq!(PreparationStaleReason::Graph.as_str(), "graph");
608 assert_eq!(PreparationStaleReason::Authority.as_str(), "authority");
609 assert_eq!(PreparationStaleReason::Staged.as_str(), "staged");
610 assert_eq!(PreparationStaleReason::Symbols.as_str(), "symbols");
611 assert_eq!(PreparationStaleReason::Semantic.as_str(), "semantic");
612 assert_eq!(PreparationStaleReason::Provider.as_str(), "provider");
613 }
614
615 #[test]
616 fn plan_stale_reason_has_stable_snake_case_names() {
617 assert_eq!(PlanStaleReason::Engine.as_str(), "engine");
618 assert_eq!(PlanStaleReason::Provider.as_str(), "provider");
619 assert_eq!(PlanStaleReason::Semantic.as_str(), "semantic");
620 assert_eq!(PlanStaleReason::Budget.as_str(), "budget");
621 assert_eq!(PlanStaleReason::Staged.as_str(), "staged");
622 assert_eq!(PlanStaleReason::Symbols.as_str(), "symbols");
623 assert_eq!(PlanStaleReason::Authority.as_str(), "authority");
624 assert_eq!(PlanStaleReason::SpanGeneration.as_str(), "span_generation");
625 assert_eq!(PlanStaleReason::Graph.as_str(), "graph");
626 }
627}
628
629#[cfg(test)]
630mod compact_tests {
631 use super::*;
632 use std::hash::{Hash, Hasher};
633
634 #[test]
635 fn compact_layouts() {
636 assert!(size_of::<ExcelError>() <= 16);
637 assert!(size_of::<LiteralValue>() <= 32);
638 assert!(size_of::<Result<LiteralValue, ExcelError>>() <= 40);
639 }
640
641 #[test]
642 fn empty_errors_do_not_have_a_payload() {
643 let mut error = ExcelError::new_value().with_extra(ExcelErrorExtra::None);
644 error.set_message(None);
645 error.set_context(None);
646 assert!(error.payload.is_none());
647 error.set_message(Some(String::new()));
648 assert!(error.payload.is_some());
649 error.set_context(Some(ErrorContext::default()));
650 error.set_extra(ExcelErrorExtra::Spill {
651 expected_rows: 2,
652 expected_cols: 3,
653 });
654 error.set_message(None);
655 error.set_context(None);
656 assert!(error.payload.is_some());
657 error.set_extra(ExcelErrorExtra::None);
658 assert!(error.payload.is_none());
659 assert_eq!(error, ExcelError::new_value());
660 }
661
662 #[test]
663 fn logical_debug_hash_equality_and_display_are_unchanged() {
664 #[derive(Debug, Hash)]
665 struct ExcelError {
666 kind: ExcelErrorKind,
667 message: Option<String>,
668 context: Option<ErrorContext>,
669 extra: ExcelErrorExtra,
670 }
671 fn hash(value: &impl Hash) -> u64 {
672 let mut state = std::collections::hash_map::DefaultHasher::new();
673 value.hash(&mut state);
674 state.finish()
675 }
676 for error in [
677 super::ExcelError::new_div(),
678 super::ExcelError::new_value().with_message("explanation"),
679 super::ExcelError::new(ExcelErrorKind::Spill)
680 .with_message("blocked")
681 .with_location(2, 3)
682 .with_origin(Some("Sheet1".into()), 4, 5)
683 .with_extra(ExcelErrorExtra::Spill {
684 expected_rows: 6,
685 expected_cols: 7,
686 }),
687 ] {
688 let old = ExcelError {
689 kind: error.kind,
690 message: error.message().map(str::to_owned),
691 context: error.context().cloned(),
692 extra: error.extra().clone(),
693 };
694 assert_eq!(format!("{error:?}"), format!("{old:?}"));
695 assert_eq!(hash(&error), hash(&old));
696 assert_eq!(error, error.clone());
697 }
698 let error = super::ExcelError::new(ExcelErrorKind::Spill)
699 .with_message("blocked")
700 .with_location(2, 3)
701 .with_origin(Some("Sheet1".into()), 4, 5)
702 .with_extra(ExcelErrorExtra::Spill {
703 expected_rows: 6,
704 expected_cols: 7,
705 });
706 assert_eq!(
707 error.to_string(),
708 "#SPILL!: blocked (row 2, col 3) [origin: Sheet1!R4C5] [spill 6×7]"
709 );
710 assert_ne!(error, error.clone().with_message("different"));
711 assert_ne!(error, error.clone().with_location(1, 1));
712 assert_ne!(error, error.clone().with_extra(ExcelErrorExtra::None));
713 }
714}