mmdvm/
error.rs

1//! Shell-level error type for the tokio MMDVM shell.
2
3use mmdvm_core::{MmdvmError, ModemMode, NakReason};
4use thiserror::Error;
5
6/// Errors surfaced by the async shell.
7#[derive(Debug, Error)]
8#[non_exhaustive]
9pub enum ShellError {
10    /// The session task has exited — consumer handle received a
11    /// closed channel.
12    #[error("MMDVM session task has exited")]
13    SessionClosed,
14    /// A frame failed to encode/decode per the MMDVM codec.
15    #[error(transparent)]
16    Core(#[from] MmdvmError),
17    /// Transport-level I/O failure.
18    #[error("MMDVM transport I/O error: {0}")]
19    Io(#[from] std::io::Error),
20    /// The consumer submitted a voice frame but the modem's TX buffer
21    /// is full; the frame was dropped to protect the FIFO from
22    /// overflow.
23    #[error("MMDVM TX buffer full for mode {mode:?}; frame dropped")]
24    BufferFull {
25        /// The mode whose FIFO is saturated.
26        mode: ModemMode,
27    },
28    /// A modem `NAK` response was received.
29    #[error("modem rejected command 0x{command:02X}: {reason:?}")]
30    Nak {
31        /// The command byte that was rejected.
32        command: u8,
33        /// The rejection reason the modem reported.
34        reason: NakReason,
35    },
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn session_closed_display() {
44        let err = ShellError::SessionClosed;
45        assert_eq!(err.to_string(), "MMDVM session task has exited");
46    }
47
48    #[test]
49    fn buffer_full_reports_mode() {
50        let err = ShellError::BufferFull {
51            mode: ModemMode::DStar,
52        };
53        assert!(err.to_string().contains("DStar"));
54    }
55
56    #[test]
57    fn nak_formats_command_in_hex() {
58        let err = ShellError::Nak {
59            command: 0x10,
60            reason: NakReason::BufferFull,
61        };
62        assert!(err.to_string().contains("0x10"));
63        assert!(err.to_string().contains("BufferFull"));
64    }
65
66    #[test]
67    fn core_error_transparent() {
68        let core_err = MmdvmError::InvalidStartByte { got: 0xFF };
69        let shell_err: ShellError = core_err.into();
70        assert!(matches!(shell_err, ShellError::Core(_)));
71    }
72}