Skip to main content

lex_core/lex/wire/
error.rs

1//! Error types for the wire codec's reverse direction.
2
3/// Errors the reverse codec can surface when converting a `WireNode` back
4/// to a lex-core [`crate::lex::ast::ContentItem`].
5///
6/// Forward conversion (`to_wire_*`) is total — every lex-core AST shape
7/// has a defined mapping to a wire form. Reverse conversion is fallible
8/// because the wire input may be malformed or carry an unknown variant.
9#[derive(Debug, Clone, PartialEq)]
10pub enum FromWireError {
11    /// The wire node carried an unknown structural placeholder (an
12    /// `Unknown` variant added in a future wire version, or a kind the
13    /// host's `WIRE_VERSION` does not recognise).
14    UnsupportedKind { kind: String },
15    /// A required field was missing or had the wrong shape — for
16    /// example, a `WireNode::Annotation` whose `params` was not a
17    /// JSON object.
18    MalformedField { field: &'static str, detail: String },
19    /// A nested `serde_json::from_value` conversion failed when
20    /// destructuring an opaque field (e.g., `params` blob).
21    DeserialisationFailed(String),
22}
23
24impl std::fmt::Display for FromWireError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            FromWireError::UnsupportedKind { kind } => {
28                write!(f, "wire AST: unsupported kind `{kind}`")
29            }
30            FromWireError::MalformedField { field, detail } => {
31                write!(f, "wire AST: malformed field `{field}`: {detail}")
32            }
33            FromWireError::DeserialisationFailed(msg) => {
34                write!(f, "wire AST: deserialisation failed: {msg}")
35            }
36        }
37    }
38}
39
40impl std::error::Error for FromWireError {}
41
42#[cfg(test)]
43mod tests {
44    //! `FromWireError` is what propagates out of the reverse codec
45    //! when the wire input is malformed; its `Display` impl is what
46    //! callers (handler dispatch, debug logs, panics in tests) end
47    //! up showing humans, so pin the surface text down.
48    use super::*;
49
50    #[test]
51    fn display_unsupported_kind_includes_kind_string() {
52        let err = FromWireError::UnsupportedKind {
53            kind: "lex.future.shape".into(),
54        };
55        let msg = err.to_string();
56        assert!(msg.contains("unsupported kind"), "got: {msg}");
57        assert!(msg.contains("lex.future.shape"), "got: {msg}");
58    }
59
60    #[test]
61    fn display_malformed_field_includes_field_and_detail() {
62        let err = FromWireError::MalformedField {
63            field: "params",
64            detail: "expected object".into(),
65        };
66        let msg = err.to_string();
67        assert!(msg.contains("malformed field"), "got: {msg}");
68        assert!(msg.contains("params"), "got: {msg}");
69        assert!(msg.contains("expected object"), "got: {msg}");
70    }
71
72    #[test]
73    fn display_deserialisation_failed_includes_detail() {
74        let err = FromWireError::DeserialisationFailed("invalid type: integer `1`".into());
75        let msg = err.to_string();
76        assert!(msg.contains("deserialisation failed"), "got: {msg}");
77        assert!(msg.contains("invalid type"), "got: {msg}");
78    }
79
80    /// `FromWireError` participates in `std::error::Error`. Confirm it
81    /// can be boxed and re-formatted through that trait — the path
82    /// dispatch code uses when bubbling up handler errors.
83    #[test]
84    fn implements_std_error_and_round_trips_via_dyn() {
85        let err: Box<dyn std::error::Error> =
86            Box::new(FromWireError::UnsupportedKind { kind: "x".into() });
87        assert!(err.to_string().contains("unsupported kind"));
88    }
89
90    /// `Clone` and `PartialEq` are part of the public surface — tests
91    /// that triage error-bearing return paths rely on them.
92    #[test]
93    fn clone_and_equality_hold() {
94        let a = FromWireError::MalformedField {
95            field: "f",
96            detail: "d".into(),
97        };
98        let b = a.clone();
99        assert_eq!(a, b);
100        let c = FromWireError::MalformedField {
101            field: "f",
102            detail: "other".into(),
103        };
104        assert_ne!(a, c);
105    }
106}