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#[derive(Clone, PartialEq, Eq)]
247pub struct ExcelError {
248 pub kind: ExcelErrorKind,
249 payload: Option<Box<ExcelErrorPayload>>,
251}
252
253#[derive(Clone, PartialEq, Eq, Default)]
254struct ExcelErrorPayload {
255 message: Option<String>,
256 context: Option<ErrorContext>,
257 extra: ExcelErrorExtra,
258}
259
260impl fmt::Debug for ExcelError {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 f.debug_struct("ExcelError")
264 .field("kind", &self.kind)
265 .field("message", &self.message())
266 .field("context", &self.context())
267 .field("extra", self.extra())
268 .finish()
269 }
270}
271
272impl std::hash::Hash for ExcelError {
273 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
274 self.kind.hash(state);
275 self.message().hash(state);
276 self.context().hash(state);
277 self.extra().hash(state);
278 }
279}
280
281#[cfg(feature = "serde")]
282impl Serialize for ExcelError {
283 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
284 use serde::ser::SerializeStruct;
285 let mut fields = serializer.serialize_struct("ExcelError", 4)?;
286 fields.serialize_field("kind", &self.kind)?;
287 fields.serialize_field("message", &self.message())?;
288 fields.serialize_field("context", &self.context())?;
289 fields.serialize_field("extra", self.extra())?;
290 fields.end()
291 }
292}
293
294#[cfg(feature = "serde")]
295impl<'de> Deserialize<'de> for ExcelError {
296 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
297 #[derive(Deserialize)]
298 #[serde(rename = "ExcelError")]
299 struct Fields {
300 kind: ExcelErrorKind,
301 message: Option<String>,
302 context: Option<ErrorContext>,
303 extra: ExcelErrorExtra,
304 }
305 let fields = Fields::deserialize(deserializer)?;
306 let mut error = ExcelError::new(fields.kind);
307 error.set_message(fields.message);
308 error.set_context(fields.context);
309 error.set_extra(fields.extra);
310 Ok(error)
311 }
312}
313
314impl From<ExcelErrorKind> for ExcelError {
317 fn from(kind: ExcelErrorKind) -> Self {
318 Self {
319 kind,
320 payload: None,
321 }
322 }
323}
324
325impl ExcelError {
326 pub fn new(kind: ExcelErrorKind) -> Self {
328 kind.into()
329 }
330
331 pub fn message(&self) -> Option<&str> {
333 self.payload
334 .as_ref()
335 .and_then(|payload| payload.message.as_deref())
336 }
337
338 pub fn context(&self) -> Option<&ErrorContext> {
340 self.payload
341 .as_ref()
342 .and_then(|payload| payload.context.as_ref())
343 }
344
345 pub fn extra(&self) -> &ExcelErrorExtra {
347 self.payload
348 .as_ref()
349 .map_or(&ExcelErrorExtra::None, |payload| &payload.extra)
350 }
351
352 fn payload_mut(&mut self) -> &mut ExcelErrorPayload {
353 self.payload.get_or_insert_default()
354 }
355
356 fn discard_empty_payload(&mut self) {
357 if self.payload.as_ref().is_some_and(|payload| {
358 payload.message.is_none()
359 && payload.context.is_none()
360 && payload.extra == ExcelErrorExtra::None
361 }) {
362 self.payload = None;
363 }
364 }
365
366 pub fn set_message(&mut self, message: Option<String>) {
368 if message.is_some() || self.payload.is_some() {
369 self.payload_mut().message = message;
370 self.discard_empty_payload();
371 }
372 }
373
374 pub fn set_context(&mut self, context: Option<ErrorContext>) {
376 if context.is_some() || self.payload.is_some() {
377 self.payload_mut().context = context;
378 self.discard_empty_payload();
379 }
380 }
381
382 pub fn set_extra(&mut self, extra: ExcelErrorExtra) {
384 if extra != ExcelErrorExtra::None || self.payload.is_some() {
385 self.payload_mut().extra = extra;
386 self.discard_empty_payload();
387 }
388 }
389
390 pub fn with_message<S: Into<String>>(mut self, msg: S) -> Self {
392 self.set_message(Some(msg.into()));
393 self
394 }
395
396 pub fn with_location(mut self, row: u32, col: u32) -> Self {
398 self.set_context(Some(ErrorContext {
399 row: Some(row),
400 col: Some(col),
401 origin_row: None,
402 origin_col: None,
403 origin_sheet: None,
404 }));
405 self
406 }
407
408 pub fn with_origin(mut self, sheet: Option<String>, row: u32, col: u32) -> Self {
410 let ctx = self.payload_mut().context.get_or_insert_default();
411 ctx.origin_sheet = sheet;
412 ctx.origin_row = Some(row);
413 ctx.origin_col = Some(col);
414 self
415 }
416
417 pub fn with_extra(mut self, extra: ExcelErrorExtra) -> Self {
419 self.set_extra(extra);
420 self
421 }
422
423 pub fn from_error_string(s: &str) -> Self {
424 match ExcelErrorKind::try_parse(s) {
425 Some(kind) => Self::new(kind),
426 None => {
427 Self::new(ExcelErrorKind::Error).with_message(format!("Unknown error code: {s}"))
428 }
429 }
430 }
431
432 pub fn new_value() -> Self {
433 Self::new(ExcelErrorKind::Value)
434 }
435
436 pub fn new_name() -> Self {
437 Self::new(ExcelErrorKind::Name)
438 }
439
440 pub fn new_div() -> Self {
441 Self::new(ExcelErrorKind::Div)
442 }
443
444 pub fn new_ref() -> Self {
445 Self::new(ExcelErrorKind::Ref)
446 }
447
448 pub fn new_circ() -> Self {
449 Self::new(ExcelErrorKind::Circ)
450 }
451
452 pub fn new_num() -> Self {
453 Self::new(ExcelErrorKind::Num)
454 }
455
456 pub fn new_na() -> Self {
457 Self::new(ExcelErrorKind::Na)
458 }
459}
460
461impl fmt::Display for ExcelError {
464 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465 write!(f, "{}", self.kind)?;
467
468 if let Some(msg) = self.message() {
470 write!(f, ": {msg}")?;
471 }
472
473 if let Some(ctx) = self.context() {
475 if let (Some(r), Some(c)) = (ctx.row, ctx.col) {
476 write!(f, " (row {r}, col {c})")?;
477 }
478
479 if let (Some(or), Some(oc)) = (ctx.origin_row, ctx.origin_col) {
481 if ctx.row != Some(or) || ctx.col != Some(oc) {
482 if let Some(ref sheet) = ctx.origin_sheet {
483 write!(f, " [origin: {sheet}!R{or}C{oc}]")?;
484 } else {
485 write!(f, " [origin: R{or}C{oc}]")?;
486 }
487 }
488 }
489 }
490
491 match self.extra() {
493 ExcelErrorExtra::None => {}
494 ExcelErrorExtra::Spill {
495 expected_rows,
496 expected_cols,
497 } => {
498 write!(f, " [spill {expected_rows}×{expected_cols}]")?;
499 }
500 ExcelErrorExtra::Resource { detail } => {
501 write!(
502 f,
503 " [resource {} {}/{}]",
504 detail.reason.as_str(),
505 detail.observed,
506 detail.limit
507 )?;
508 }
509 ExcelErrorExtra::PreparationStale { reason } => {
510 write!(f, " [preparation stale {}]", reason.as_str())?;
511 }
512 ExcelErrorExtra::PlanStale { reason } => {
513 write!(f, " [plan stale {}]", reason.as_str())?;
514 }
515 }
516
517 Ok(())
518 }
519}
520
521impl Error for ExcelError {}
522impl From<ExcelError> for String {
523 fn from(error: ExcelError) -> Self {
524 format!("{error}")
525 }
526}
527impl From<ExcelError> for LiteralValue {
528 fn from(error: ExcelError) -> Self {
529 LiteralValue::Error(error)
530 }
531}
532
533impl PartialEq<str> for ExcelErrorKind {
534 fn eq(&self, other: &str) -> bool {
535 format!("{self}") == other
536 }
537}
538
539impl PartialEq<&str> for ExcelError {
540 fn eq(&self, other: &&str) -> bool {
541 self.kind.to_string() == *other
542 }
543}
544
545impl PartialEq<str> for ExcelError {
546 fn eq(&self, other: &str) -> bool {
547 self.kind.to_string() == other
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554
555 #[test]
556 fn parse_known_error_kind() {
557 assert_eq!(ExcelErrorKind::parse("#DIV/0!"), ExcelErrorKind::Div);
558 assert_eq!(ExcelErrorKind::parse("#n/a"), ExcelErrorKind::Na);
559 }
560
561 #[test]
562 fn parse_unknown_error_kind_falls_back() {
563 assert_eq!(ExcelErrorKind::parse("#BOGUS!"), ExcelErrorKind::Error);
564 let err = ExcelError::from_error_string("#BOGUS!");
565 assert_eq!(err.kind, ExcelErrorKind::Error);
566 assert!(err.message().unwrap_or_default().contains("#BOGUS!"));
567 }
568
569 #[test]
570 fn preparation_stale_reason_has_stable_snake_case_names() {
571 assert_eq!(PreparationStaleReason::Graph.as_str(), "graph");
572 assert_eq!(PreparationStaleReason::Authority.as_str(), "authority");
573 assert_eq!(PreparationStaleReason::Staged.as_str(), "staged");
574 assert_eq!(PreparationStaleReason::Symbols.as_str(), "symbols");
575 assert_eq!(PreparationStaleReason::Semantic.as_str(), "semantic");
576 assert_eq!(PreparationStaleReason::Provider.as_str(), "provider");
577 }
578
579 #[test]
580 fn plan_stale_reason_has_stable_snake_case_names() {
581 assert_eq!(PlanStaleReason::Engine.as_str(), "engine");
582 assert_eq!(PlanStaleReason::Provider.as_str(), "provider");
583 assert_eq!(PlanStaleReason::Semantic.as_str(), "semantic");
584 assert_eq!(PlanStaleReason::Budget.as_str(), "budget");
585 assert_eq!(PlanStaleReason::Staged.as_str(), "staged");
586 assert_eq!(PlanStaleReason::Symbols.as_str(), "symbols");
587 assert_eq!(PlanStaleReason::Authority.as_str(), "authority");
588 assert_eq!(PlanStaleReason::SpanGeneration.as_str(), "span_generation");
589 assert_eq!(PlanStaleReason::Graph.as_str(), "graph");
590 }
591}
592
593#[cfg(test)]
594mod compact_tests {
595 use super::*;
596 use std::hash::{Hash, Hasher};
597
598 #[test]
599 fn compact_layouts() {
600 assert!(size_of::<ExcelError>() <= 16);
601 assert!(size_of::<LiteralValue>() <= 32);
602 assert!(size_of::<Result<LiteralValue, ExcelError>>() <= 40);
603 }
604
605 #[test]
606 fn empty_errors_do_not_have_a_payload() {
607 let mut error = ExcelError::new_value().with_extra(ExcelErrorExtra::None);
608 error.set_message(None);
609 error.set_context(None);
610 assert!(error.payload.is_none());
611 error.set_message(Some(String::new()));
612 assert!(error.payload.is_some());
613 error.set_context(Some(ErrorContext::default()));
614 error.set_extra(ExcelErrorExtra::Spill {
615 expected_rows: 2,
616 expected_cols: 3,
617 });
618 error.set_message(None);
619 error.set_context(None);
620 assert!(error.payload.is_some());
621 error.set_extra(ExcelErrorExtra::None);
622 assert!(error.payload.is_none());
623 assert_eq!(error, ExcelError::new_value());
624 }
625
626 #[test]
627 fn logical_debug_hash_equality_and_display_are_unchanged() {
628 #[derive(Debug, Hash)]
629 struct ExcelError {
630 kind: ExcelErrorKind,
631 message: Option<String>,
632 context: Option<ErrorContext>,
633 extra: ExcelErrorExtra,
634 }
635 fn hash(value: &impl Hash) -> u64 {
636 let mut state = std::collections::hash_map::DefaultHasher::new();
637 value.hash(&mut state);
638 state.finish()
639 }
640 for error in [
641 super::ExcelError::new_div(),
642 super::ExcelError::new_value().with_message("explanation"),
643 super::ExcelError::new(ExcelErrorKind::Spill)
644 .with_message("blocked")
645 .with_location(2, 3)
646 .with_origin(Some("Sheet1".into()), 4, 5)
647 .with_extra(ExcelErrorExtra::Spill {
648 expected_rows: 6,
649 expected_cols: 7,
650 }),
651 ] {
652 let old = ExcelError {
653 kind: error.kind,
654 message: error.message().map(str::to_owned),
655 context: error.context().cloned(),
656 extra: error.extra().clone(),
657 };
658 assert_eq!(format!("{error:?}"), format!("{old:?}"));
659 assert_eq!(hash(&error), hash(&old));
660 assert_eq!(error, error.clone());
661 }
662 let error = super::ExcelError::new(ExcelErrorKind::Spill)
663 .with_message("blocked")
664 .with_location(2, 3)
665 .with_origin(Some("Sheet1".into()), 4, 5)
666 .with_extra(ExcelErrorExtra::Spill {
667 expected_rows: 6,
668 expected_cols: 7,
669 });
670 assert_eq!(
671 error.to_string(),
672 "#SPILL!: blocked (row 2, col 3) [origin: Sheet1!R4C5] [spill 6×7]"
673 );
674 assert_ne!(error, error.clone().with_message("different"));
675 assert_ne!(error, error.clone().with_location(1, 1));
676 assert_ne!(error, error.clone().with_extra(ExcelErrorExtra::None));
677 }
678}