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