Skip to main content

conduit_core/
error.rs

1//! Error types for the conduit IPC layer.
2//!
3//! [`Error`] covers every failure mode for the custom protocol
4//! transport: authentication, serialisation, and binary framing.
5
6use std::fmt;
7
8/// Unified error type for all conduit operations.
9#[derive(Debug)]
10pub enum Error {
11    /// The client failed token authentication.
12    AuthFailed,
13    /// JSON serialisation / deserialisation error.
14    Serialize(sonic_rs::Error),
15    /// An unrecognised command name was received.
16    UnknownCommand(String),
17    /// A binary frame could not be decoded.
18    DecodeFailed,
19    /// A payload exceeds the maximum encodable size (u32::MAX bytes).
20    PayloadTooLarge(usize),
21    /// A reliable channel's byte budget has been reached and the frame was
22    /// rejected (no data was lost).
23    ChannelFull,
24    /// A command handler returned an application-level error.
25    Handler(String),
26    /// A named channel does not exist.
27    UnknownChannel(String),
28}
29
30impl fmt::Display for Error {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Self::AuthFailed => f.write_str("authentication failed"),
34            Self::Serialize(e) => write!(f, "serialization error: {e}"),
35            Self::UnknownCommand(name) => write!(f, "unknown command: {name}"),
36            Self::DecodeFailed => f.write_str("binary decode failed"),
37            Self::PayloadTooLarge(len) => {
38                write!(f, "payload too large: {len} bytes exceeds u32::MAX")
39            }
40            Self::ChannelFull => f.write_str("channel full: byte limit reached"),
41            Self::Handler(msg) => write!(f, "handler error: {msg}"),
42            Self::UnknownChannel(name) => write!(f, "unknown channel: {name}"),
43        }
44    }
45}
46
47impl std::error::Error for Error {
48    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
49        match self {
50            Self::Serialize(e) => Some(e),
51            _ => None,
52        }
53    }
54}
55
56impl From<sonic_rs::Error> for Error {
57    fn from(e: sonic_rs::Error) -> Self {
58        Self::Serialize(e)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn display_auth_failed() {
68        let err = Error::AuthFailed;
69        assert_eq!(err.to_string(), "authentication failed");
70    }
71
72    #[test]
73    fn display_unknown_command() {
74        let err = Error::UnknownCommand("foo".into());
75        assert_eq!(err.to_string(), "unknown command: foo");
76    }
77
78    #[test]
79    fn from_sonic_rs() {
80        let json_err = sonic_rs::from_str::<String>("not json").unwrap_err();
81        let err: Error = json_err.into();
82        assert!(matches!(err, Error::Serialize(_)));
83    }
84
85    #[test]
86    fn error_source_none_variants() {
87        assert!(std::error::Error::source(&Error::AuthFailed).is_none());
88        assert!(std::error::Error::source(&Error::DecodeFailed).is_none());
89        assert!(std::error::Error::source(&Error::PayloadTooLarge(0)).is_none());
90    }
91
92    #[test]
93    fn display_payload_too_large() {
94        let err = Error::PayloadTooLarge(5_000_000_000);
95        assert_eq!(
96            err.to_string(),
97            "payload too large: 5000000000 bytes exceeds u32::MAX"
98        );
99    }
100
101    #[test]
102    fn display_decode_failed() {
103        let err = Error::DecodeFailed;
104        assert_eq!(err.to_string(), "binary decode failed");
105    }
106
107    #[test]
108    fn display_channel_full() {
109        let err = Error::ChannelFull;
110        assert_eq!(err.to_string(), "channel full: byte limit reached");
111    }
112
113    #[test]
114    fn error_source_channel_full() {
115        assert!(std::error::Error::source(&Error::ChannelFull).is_none());
116    }
117
118    #[test]
119    fn display_handler_error() {
120        let err = Error::Handler("division by zero".into());
121        assert_eq!(err.to_string(), "handler error: division by zero");
122    }
123
124    #[test]
125    fn error_source_handler() {
126        assert!(std::error::Error::source(&Error::Handler("x".into())).is_none());
127    }
128
129    #[test]
130    fn display_unknown_channel() {
131        let err = Error::UnknownChannel("telemetry".into());
132        assert_eq!(err.to_string(), "unknown channel: telemetry");
133    }
134
135    #[test]
136    fn error_source_unknown_channel() {
137        assert!(std::error::Error::source(&Error::UnknownChannel("x".into())).is_none());
138    }
139}