1use std::fmt;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct XtlError {
20 pub code: String,
21 pub message: String,
22}
23
24impl XtlError {
25 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
26 XtlError {
27 code: code.into(),
28 message: message.into(),
29 }
30 }
31}
32
33impl fmt::Display for XtlError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 write!(f, "[{}] {}", self.code, self.message)
36 }
37}
38
39impl std::error::Error for XtlError {}
40
41pub fn is_xtl_error(err: &anyhow::Error) -> Option<&XtlError> {
45 err.downcast_ref::<XtlError>()
46}
47
48pub mod code {
53 pub const SOURCE_SHEET_MISSING: &str = "xl3/source/sheet-missing";
54 pub const SOURCE_NO_HEADER: &str = "xl3/source/no-header";
55 pub const SOURCE_DUPLICATE_COLUMN: &str = "xl3/source/duplicate-column";
56 pub const EVAL_DIV_BY_ZERO: &str = "xl3/eval/div-by-zero";
57 pub const EVAL_UNSUPPORTED_SYNTAX: &str = "xl3/eval/unsupported-syntax";
58 pub const EVAL_UNKNOWN_NAME: &str = "xl3/expression/unknown-name";
59 pub const EVAL_ARITY_MISMATCH: &str = "xl3/eval/arity-mismatch";
60 pub const EVAL_OPERAND_COERCION: &str = "xl3/eval/operand-coercion";
61 pub const DIRECTIVE_BAD_JOIN: &str = "xl3/directive/bad-join";
62 pub const XLOOKUP_BARE_BRACKET: &str = "xl3/xlookup/bare-bracket";
63 pub const XLOOKUP_SOURCE_MISMATCH: &str = "xl3/xlookup/source-mismatch";
64 pub const TEMPLATE_NO_SHEETS: &str = "xl3/template/no-visible-sheets";
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use anyhow::Result;
71
72 fn fails_with_code() -> Result<()> {
73 Err(XtlError::new(code::EVAL_DIV_BY_ZERO, "test message").into())
74 }
75
76 #[test]
77 fn downcasts_through_anyhow() {
78 let err = fails_with_code().unwrap_err();
79 let xtl = is_xtl_error(&err).expect("expected XtlError");
80 assert_eq!(xtl.code, "xl3/eval/div-by-zero");
81 assert_eq!(xtl.message, "test message");
82 }
83
84 #[test]
85 fn non_xtl_error_returns_none() {
86 let err: anyhow::Error = anyhow::anyhow!("plain anyhow");
87 assert!(is_xtl_error(&err).is_none());
88 }
89
90 #[test]
91 fn display_uses_bracket_code() {
92 let e = XtlError::new(code::SOURCE_SHEET_MISSING, "foo");
93 assert_eq!(format!("{e}"), "[xl3/source/sheet-missing] foo");
94 }
95}