Skip to main content

conduit_core/
error.rs

1//! Error types for the conduit IPC layer.
2//!
3//! [`ConduitError`] 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 ConduitError {
11    /// The client failed token authentication.
12    AuthFailed,
13    /// JSON serialisation / deserialisation error.
14    Serialize(serde_json::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}
22
23impl fmt::Display for ConduitError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::AuthFailed => f.write_str("authentication failed"),
27            Self::Serialize(e) => write!(f, "serialization error: {e}"),
28            Self::UnknownCommand(name) => write!(f, "unknown command: {name}"),
29            Self::DecodeFailed => f.write_str("frame decode failed"),
30            Self::PayloadTooLarge(len) => {
31                write!(f, "payload too large: {len} bytes exceeds u32::MAX")
32            }
33        }
34    }
35}
36
37impl std::error::Error for ConduitError {
38    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
39        match self {
40            Self::Serialize(e) => Some(e),
41            _ => None,
42        }
43    }
44}
45
46impl From<serde_json::Error> for ConduitError {
47    fn from(e: serde_json::Error) -> Self {
48        Self::Serialize(e)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn display_auth_failed() {
58        let err = ConduitError::AuthFailed;
59        assert_eq!(err.to_string(), "authentication failed");
60    }
61
62    #[test]
63    fn display_unknown_command() {
64        let err = ConduitError::UnknownCommand("foo".into());
65        assert_eq!(err.to_string(), "unknown command: foo");
66    }
67
68    #[test]
69    fn from_serde_json() {
70        let json_err = serde_json::from_str::<String>("not json").unwrap_err();
71        let err: ConduitError = json_err.into();
72        assert!(matches!(err, ConduitError::Serialize(_)));
73    }
74
75    #[test]
76    fn error_source_none_variants() {
77        assert!(std::error::Error::source(&ConduitError::AuthFailed).is_none());
78        assert!(std::error::Error::source(&ConduitError::DecodeFailed).is_none());
79        assert!(std::error::Error::source(&ConduitError::PayloadTooLarge(0)).is_none());
80    }
81
82    #[test]
83    fn display_payload_too_large() {
84        let err = ConduitError::PayloadTooLarge(5_000_000_000);
85        assert_eq!(
86            err.to_string(),
87            "payload too large: 5000000000 bytes exceeds u32::MAX"
88        );
89    }
90}