hermes_five/
errors.rs

1use std::str::Utf8Error;
2
3use log::error;
4use snafu::Snafu;
5
6pub use crate::errors::Error::*;
7use crate::io::{PinIdOrName, PinModeId};
8
9#[derive(Debug, Snafu)]
10#[snafu(visibility(pub))]
11pub enum Error {
12    /// Runtime error: Are you sure your code runs inside `#[hermes_five::runtime]`?
13    RuntimeError,
14    /// State error: incompatible type provided.
15    StateError,
16    /// Protocol error: {source}.
17    ProtocolError { source: ProtocolError },
18    /// Hardware error: {source}.
19    HardwareError { source: HardwareError },
20    /// Unknown error: {info}.
21    UnknownError { info: String },
22}
23
24impl From<std::io::Error> for Error {
25    fn from(error: std::io::Error) -> Self {
26        error!("std::io error {:?}", error);
27        let info = match error.kind() {
28            std::io::ErrorKind::NotFound => String::from("Board not found or already in use"),
29            std::io::ErrorKind::PermissionDenied => String::from("Board access denied"),
30            _ => error.to_string(),
31        };
32        Self::ProtocolError {
33            source: ProtocolError::IoException { info },
34        }
35    }
36}
37
38impl From<ProtocolError> for Error {
39    fn from(value: ProtocolError) -> Self {
40        Self::ProtocolError { source: value }
41    }
42}
43
44impl From<HardwareError> for Error {
45    fn from(value: HardwareError) -> Self {
46        Self::HardwareError { source: value }
47    }
48}
49
50impl From<Utf8Error> for Error {
51    fn from(value: Utf8Error) -> Self {
52        UnknownError {
53            info: value.to_string(),
54        }
55    }
56}
57
58#[derive(Debug, Snafu)]
59#[snafu(visibility(pub))]
60pub enum ProtocolError {
61    /// {info}
62    IoException { info: String },
63    /// Connection has not been initialized
64    NotInitialized,
65    /// Not enough bytes received - '{operation}' expected {expected} bytes, {received} received
66    MessageTooShort {
67        operation: &'static str,
68        expected: usize,
69        received: usize,
70    },
71    /// Unexpected data received
72    UnexpectedData,
73}
74
75#[derive(Debug, Snafu)]
76#[snafu(visibility(pub))]
77pub enum HardwareError {
78    /// Pin ({pin}) not compatible with mode ({mode}) - {context}
79    IncompatiblePin {
80        pin: u8,
81        mode: PinModeId,
82        context: &'static str,
83    },
84    /// Unknown pin {pin}
85    UnknownPin { pin: PinIdOrName },
86}
87
88#[cfg(test)]
89mod tests {
90    use std::io;
91
92    use crate::errors::HardwareError::{IncompatiblePin, UnknownPin};
93
94    use super::*;
95
96    #[test]
97    fn test_error_display() {
98        let runtime_error = RuntimeError;
99        assert_eq!(
100            format!("{}", runtime_error),
101            "Runtime error: Are you sure your code runs inside `#[hermes_five::runtime]`?"
102        );
103
104        let protocol_error = Error::from(ProtocolError::IoException {
105            info: "I/O error message".to_string(),
106        });
107        assert_eq!(
108            format!("{}", protocol_error),
109            "Protocol error: I/O error message."
110        );
111
112        let hardware_error = Error::from(IncompatiblePin {
113            pin: 1,
114            mode: PinModeId::SERVO,
115            context: "test context",
116        });
117        assert_eq!(
118            format!("{}", hardware_error),
119            "Hardware error: Pin (1) not compatible with mode (SERVO) - test context."
120        );
121
122        let unknown_error = UnknownError {
123            info: "Some unknown error".to_string(),
124        };
125        assert_eq!(
126            format!("{}", unknown_error),
127            "Unknown error: Some unknown error."
128        );
129    }
130
131    #[test]
132    fn test_from_io_error() {
133        let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
134        let error: Error = io_error.into();
135        assert_eq!(
136            format!("{}", error),
137            "Protocol error: Board not found or already in use."
138        );
139
140        let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "error");
141        let error: Error = io_error.into();
142        assert_eq!(format!("{}", error), "Protocol error: Board access denied.");
143    }
144
145    #[test]
146    fn test_from_protocol_error() {
147        let protocol_error = ProtocolError::NotInitialized;
148        let error: Error = protocol_error.into();
149        assert_eq!(
150            format!("{}", error),
151            "Protocol error: Connection has not been initialized."
152        );
153    }
154
155    #[test]
156    fn test_from_hardware_error() {
157        let hardware_error = UnknownPin {
158            pin: PinIdOrName::Id(42),
159        };
160        let error: Error = hardware_error.into();
161        assert_eq!(format!("{}", error), "Hardware error: Unknown pin 42.");
162    }
163
164    #[test]
165    fn test_from_utf8_error() {
166        #[allow(invalid_from_utf8)]
167        let utf8_error = std::str::from_utf8(&[0x80]).err().unwrap(); // Invalid UTF-8 sequence
168        let error: Error = utf8_error.into();
169        assert_eq!(
170            format!("{}", error),
171            "Unknown error: invalid utf-8 sequence of 1 bytes from index 0."
172        )
173    }
174}