Skip to main content

datalogic_rs/error/
serde.rs

1//! Serde + `Display` rendering for [`Error`], plus the `From` impls for
2//! foreign parse errors. Split out so `mod.rs` stays focused on the struct
3//! and its constructors.
4
5use super::Error;
6use super::kind::ErrorKind;
7use serde::ser::{Serialize, SerializeMap, Serializer};
8use std::borrow::Cow;
9use std::fmt;
10
11impl fmt::Display for Error {
12    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13        // Render the kind first, then optionally the operator context.
14        write_kind_message(f, &self.kind)?;
15        if let Some(op) = self.operator() {
16            write!(f, " (in operator: {})", op)?;
17        }
18        Ok(())
19    }
20}
21
22/// Render the `ErrorKind` portion of an error message, without the operator
23/// suffix. Single source of truth for the kind → human-readable mapping; used
24/// by `Display for Error` (which then appends the operator context) and
25/// `Error::serialize` (via `KindDisplay`).
26fn write_kind_message(f: &mut fmt::Formatter<'_>, kind: &ErrorKind) -> fmt::Result {
27    match kind {
28        ErrorKind::InvalidOperator(op) => write!(f, "Invalid operator: {}", op),
29        ErrorKind::InvalidArguments(msg) => write!(f, "Invalid arguments: {}", msg),
30        ErrorKind::VariableNotFound(var) => write!(f, "Variable not found: {}", var),
31        ErrorKind::InvalidContextLevel(level) => write!(f, "Invalid context level: {}", level),
32        ErrorKind::TypeError(msg) => write!(f, "Type error: {}", msg),
33        ErrorKind::ArithmeticError(msg) => write!(f, "Arithmetic error: {}", msg),
34        ErrorKind::Custom(err) => write!(f, "{}", err),
35        ErrorKind::ParseError(msg) => write!(f, "Parse error: {}", msg),
36        ErrorKind::Thrown(val) => {
37            #[cfg(feature = "serde_json")]
38            {
39                let json = crate::serde_bridge::owned_to_serde(val);
40                write!(f, "Thrown: {}", json)
41            }
42            #[cfg(not(feature = "serde_json"))]
43            {
44                write!(f, "Thrown: {:?}", val)
45            }
46        }
47        ErrorKind::FormatError(msg) => write!(f, "Format error: {}", msg),
48        ErrorKind::IndexOutOfBounds { index, length } => write!(
49            f,
50            "Index {} out of bounds for array of length {}",
51            index, length
52        ),
53        ErrorKind::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
54    }
55}
56
57impl std::error::Error for Error {
58    /// Returns the wrapped source error, but only for [`ErrorKind::Custom`].
59    ///
60    /// All other [`ErrorKind`] variants carry a flat `Cow<'static, str>`
61    /// payload (or a structured value, in `Thrown` / `IndexOutOfBounds`)
62    /// rather than a typed cause, so they have no `dyn Error` to chain to.
63    /// To attach a typed source, wrap your error via [`Error::wrap`] —
64    /// that produces an `ErrorKind::Custom` whose `source()` returns
65    /// `Some(&original)` and whose `Display` matches the original.
66    ///
67    /// ```rust
68    /// use datalogic_rs::Error;
69    /// use std::error::Error as _;
70    ///
71    /// fn read_config() -> std::io::Result<String> {
72    ///     Err(std::io::Error::other("disk fell off the cliff"))
73    /// }
74    ///
75    /// let err = read_config().map_err(Error::wrap).unwrap_err();
76    /// // The original io::Error survives the wrap and can be walked.
77    /// let source = err.source().unwrap();
78    /// assert!(source.to_string().contains("disk"));
79    /// ```
80    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
81        match &self.kind {
82            ErrorKind::Custom(err) => Some(err.as_ref()),
83            _ => None,
84        }
85    }
86}
87
88#[cfg(feature = "serde_json")]
89#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
90impl From<serde_json::Error> for Error {
91    fn from(err: serde_json::Error) -> Self {
92        Error::new(ErrorKind::ParseError(Cow::Owned(err.to_string())))
93    }
94}
95
96impl From<datavalue::ParseError> for Error {
97    fn from(err: datavalue::ParseError) -> Self {
98        Error::new(ErrorKind::ParseError(Cow::Owned(err.to_string())))
99    }
100}
101
102impl From<ErrorKind> for Error {
103    #[inline]
104    fn from(kind: ErrorKind) -> Self {
105        Error::new(kind)
106    }
107}
108
109impl Serialize for Error {
110    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
111        // Shape:
112        // { "type": <tag>, "message": <Display>, ...kind-extras, "operator"?, "node_ids"? }
113        let mut map = serializer.serialize_map(None)?;
114        map.serialize_entry("type", self.tag())?;
115        // The Display impl appends "(in operator: ...)" when set; for the
116        // `message` field we want the kind portion only, so render kind
117        // without the operator suffix.
118        map.serialize_entry("message", &KindDisplay(&self.kind).to_string())?;
119        match &self.kind {
120            ErrorKind::VariableNotFound(name) => map.serialize_entry("variable", name)?,
121            ErrorKind::InvalidContextLevel(level) => map.serialize_entry("level", level)?,
122            ErrorKind::Thrown(value) => map.serialize_entry("thrown", value)?,
123            ErrorKind::IndexOutOfBounds { index, length } => {
124                map.serialize_entry("index", index)?;
125                map.serialize_entry("length", length)?;
126            }
127            _ => {}
128        }
129        if let Some(op) = self.operator() {
130            map.serialize_entry("operator", op)?;
131        }
132        let ids = self.node_ids();
133        if !ids.is_empty() {
134            map.serialize_entry("node_ids", ids)?;
135        }
136        map.end()
137    }
138}
139
140/// Render an [`ErrorKind`] without the operator suffix. Used by
141/// [`Error::serialize`] to populate the `message` field.
142struct KindDisplay<'a>(&'a ErrorKind);
143
144impl<'a> fmt::Display for KindDisplay<'a> {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        write_kind_message(f, self.0)
147    }
148}