use std::{error::Error, fmt};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::LiteralValue;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ExcelErrorKind {
Null,
Ref,
Name,
Value,
Div,
Na,
Num,
Error,
NImpl,
Spill,
Calc,
Circ,
Cancelled,
}
impl fmt::Display for ExcelErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Null => "#NULL!",
Self::Ref => "#REF!",
Self::Name => "#NAME?",
Self::Value => "#VALUE!",
Self::Div => "#DIV/0!",
Self::Na => "#N/A",
Self::Num => "#NUM!",
Self::Error => "#ERROR!",
Self::NImpl => "#N/IMPL!",
Self::Spill => "#SPILL!",
Self::Calc => "#CALC!",
Self::Circ => "#CIRC!",
Self::Cancelled => "#CANCELLED!",
})
}
}
impl ExcelErrorKind {
pub fn try_parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"#null!" => Some(Self::Null),
"#ref!" => Some(Self::Ref),
"#name?" => Some(Self::Name),
"#value!" => Some(Self::Value),
"#div/0!" => Some(Self::Div),
"#n/a" => Some(Self::Na),
"#num!" => Some(Self::Num),
"#error!" => Some(Self::Error),
"#n/impl!" => Some(Self::NImpl),
"#spill!" => Some(Self::Spill),
"#calc!" => Some(Self::Calc),
"#circ!" => Some(Self::Circ),
"#cancelled!" => Some(Self::Cancelled),
_ => None,
}
}
pub fn parse(s: &str) -> Self {
Self::try_parse(s).unwrap_or(Self::Error)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct ErrorContext {
pub row: Option<u32>,
pub col: Option<u32>,
pub origin_row: Option<u32>,
pub origin_col: Option<u32>,
pub origin_sheet: Option<String>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub enum ExcelErrorExtra {
#[default]
None,
Spill {
expected_rows: u32,
expected_cols: u32,
},
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExcelError {
pub kind: ExcelErrorKind,
pub message: Option<String>,
pub context: Option<ErrorContext>,
pub extra: ExcelErrorExtra,
}
impl From<ExcelErrorKind> for ExcelError {
fn from(kind: ExcelErrorKind) -> Self {
Self {
kind,
message: None,
context: None,
extra: ExcelErrorExtra::None,
}
}
}
impl ExcelError {
pub fn new(kind: ExcelErrorKind) -> Self {
kind.into()
}
pub fn with_message<S: Into<String>>(mut self, msg: S) -> Self {
self.message = Some(msg.into());
self
}
pub fn with_location(mut self, row: u32, col: u32) -> Self {
self.context = Some(ErrorContext {
row: Some(row),
col: Some(col),
origin_row: None,
origin_col: None,
origin_sheet: None,
});
self
}
pub fn with_origin(mut self, sheet: Option<String>, row: u32, col: u32) -> Self {
if let Some(ref mut ctx) = self.context {
ctx.origin_sheet = sheet;
ctx.origin_row = Some(row);
ctx.origin_col = Some(col);
} else {
self.context = Some(ErrorContext {
row: None,
col: None,
origin_row: Some(row),
origin_col: Some(col),
origin_sheet: sheet,
});
}
self
}
pub fn with_extra(mut self, extra: ExcelErrorExtra) -> Self {
self.extra = extra;
self
}
pub fn from_error_string(s: &str) -> Self {
match ExcelErrorKind::try_parse(s) {
Some(kind) => Self::new(kind),
None => {
Self::new(ExcelErrorKind::Error).with_message(format!("Unknown error code: {s}"))
}
}
}
pub fn new_value() -> Self {
Self::new(ExcelErrorKind::Value)
}
pub fn new_name() -> Self {
Self::new(ExcelErrorKind::Name)
}
pub fn new_div() -> Self {
Self::new(ExcelErrorKind::Div)
}
pub fn new_ref() -> Self {
Self::new(ExcelErrorKind::Ref)
}
pub fn new_circ() -> Self {
Self::new(ExcelErrorKind::Circ)
}
pub fn new_num() -> Self {
Self::new(ExcelErrorKind::Num)
}
pub fn new_na() -> Self {
Self::new(ExcelErrorKind::Na)
}
}
impl fmt::Display for ExcelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(ref msg) = self.message {
write!(f, ": {msg}")?;
}
if let Some(ref ctx) = self.context {
if let (Some(r), Some(c)) = (ctx.row, ctx.col) {
write!(f, " (row {r}, col {c})")?;
}
if let (Some(or), Some(oc)) = (ctx.origin_row, ctx.origin_col) {
if ctx.row != Some(or) || ctx.col != Some(oc) {
if let Some(ref sheet) = ctx.origin_sheet {
write!(f, " [origin: {sheet}!R{or}C{oc}]")?;
} else {
write!(f, " [origin: R{or}C{oc}]")?;
}
}
}
}
match &self.extra {
ExcelErrorExtra::None => {}
ExcelErrorExtra::Spill {
expected_rows,
expected_cols,
} => {
write!(f, " [spill {expected_rows}×{expected_cols}]")?;
}
}
Ok(())
}
}
impl Error for ExcelError {}
impl From<ExcelError> for String {
fn from(error: ExcelError) -> Self {
format!("{error}")
}
}
impl From<ExcelError> for LiteralValue {
fn from(error: ExcelError) -> Self {
LiteralValue::Error(error)
}
}
impl PartialEq<str> for ExcelErrorKind {
fn eq(&self, other: &str) -> bool {
format!("{self}") == other
}
}
impl PartialEq<&str> for ExcelError {
fn eq(&self, other: &&str) -> bool {
self.kind.to_string() == *other
}
}
impl PartialEq<str> for ExcelError {
fn eq(&self, other: &str) -> bool {
self.kind.to_string() == other
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_known_error_kind() {
assert_eq!(ExcelErrorKind::parse("#DIV/0!"), ExcelErrorKind::Div);
assert_eq!(ExcelErrorKind::parse("#n/a"), ExcelErrorKind::Na);
}
#[test]
fn parse_unknown_error_kind_falls_back() {
assert_eq!(ExcelErrorKind::parse("#BOGUS!"), ExcelErrorKind::Error);
let err = ExcelError::from_error_string("#BOGUS!");
assert_eq!(err.kind, ExcelErrorKind::Error);
assert!(err.message.unwrap_or_default().contains("#BOGUS!"));
}
}