Skip to main content

midi_io/
error.rs

1use crate::CodecError;
2use crate::SysExError;
3use crate::ValueError;
4
5#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
6#[non_exhaustive]
7pub enum Error {
8    #[error(transparent)]
9    Codec(#[from] CodecError),
10
11    #[error(transparent)]
12    Value(#[from] ValueError),
13
14    #[error(transparent)]
15    SysEx(#[from] SysExError),
16
17    #[cfg(feature = "io")]
18    #[error(transparent)]
19    Io(#[from] IoError),
20}
21
22#[cfg(feature = "io")]
23#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
24#[non_exhaustive]
25pub enum IoError {
26    #[error("port not found")]
27    PortNotFound,
28    #[error("port disconnected")]
29    PortDisconnected,
30    #[error("port already connected")]
31    AlreadyConnected,
32
33    #[error("invalid name: {0}")]
34    InvalidName(#[from] NameError),
35
36    #[error("platform backend thread terminated unexpectedly")]
37    BackendThreadDied,
38    #[error("command channel full - backend thread is not processing commands")]
39    BackendCommandChannelFull,
40
41    #[error(transparent)]
42    Platform(#[from] PlatformError),
43
44    #[error("inbound stream overflow - {dropped} message(s) dropped")]
45    InboundOverflow { dropped: usize },
46}
47
48#[cfg(feature = "io")]
49#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
50#[non_exhaustive]
51pub enum PlatformError {
52    #[error("client initialization failed: backend error code {0}")]
53    ClientInit(i32),
54    #[error("client initialization failed: IO thread initialization failed")]
55    ThreadInit,
56    #[error("connect failed: backend error code {0}")]
57    Connect(i32),
58    #[error("send failed: backend error code {0}")]
59    Send(i32),
60    #[error("send failed: MIDI encoder produced no event for valid input")]
61    Encode,
62    #[error("virtual port creation failed: backend error code {0}")]
63    VirtualPortCreate(i32),
64}
65
66#[cfg(feature = "io")]
67#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
68#[non_exhaustive]
69pub enum NameError {
70    #[error("name contains NUL byte")]
71    ContainsNul(#[from] std::ffi::NulError),
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::ParseError;
78
79    #[cfg(feature = "io")]
80    fn nul_error() -> std::ffi::NulError {
81        std::ffi::CString::new("a\0b").unwrap_err()
82    }
83
84    #[test]
85    fn error_is_clone_and_partial_eq() {
86        let s1: Error = CodecError::SysexTooLong { len: 100, max: 50 }.into();
87        let s2: Error = CodecError::SysexTooLong { len: 100, max: 50 }.into();
88        assert_eq!(s1.clone(), s2);
89
90        let p1: Error = CodecError::Parse {
91            reason: ParseError::Empty,
92            bytes: vec![],
93        }
94        .into();
95        let p2: Error = CodecError::Parse {
96            reason: ParseError::Empty,
97            bytes: vec![],
98        }
99        .into();
100        assert_eq!(p1.clone(), p2);
101
102        let u1: Error = CodecError::Unparseable(crate::RawMidiMessage::from_slice(&[0xF4])).into();
103        let u2: Error = CodecError::Unparseable(crate::RawMidiMessage::from_slice(&[0xF4])).into();
104        assert_eq!(u1.clone(), u2);
105
106        #[cfg(all(feature = "io", any(target_os = "macos", target_os = "ios")))]
107        {
108            let e1: Error = IoError::InvalidName(NameError::ContainsNul(nul_error())).into();
109            let e2: Error = IoError::InvalidName(NameError::ContainsNul(nul_error())).into();
110            assert_eq!(e1.clone(), e2);
111
112            let c1: Error = IoError::Platform(PlatformError::Send(-1)).into();
113            let c2: Error = IoError::Platform(PlatformError::Send(-1)).into();
114            assert_eq!(c1.clone(), c2);
115        }
116    }
117
118    #[cfg(feature = "io")]
119    #[test]
120    fn name_error_display() {
121        assert_eq!(
122            format!("{}", NameError::ContainsNul(nul_error())),
123            "name contains NUL byte"
124        );
125    }
126
127    #[test]
128    fn error_display() {
129        let sysex: Error = CodecError::SysexTooLong { len: 100, max: 50 }.into();
130        assert_eq!(format!("{sysex}"), "sysex too long: 100 bytes (max 50)");
131
132        let parse: Error = CodecError::Parse {
133            reason: ParseError::Empty,
134            bytes: vec![],
135        }
136        .into();
137        assert_eq!(
138            format!("{parse}"),
139            "failed to parse MIDI message: empty message (bytes: [])"
140        );
141
142        let unparseable: Error =
143            CodecError::Unparseable(crate::RawMidiMessage::from_slice(&[0x90, 0x3c])).into();
144        assert_eq!(
145            format!("{unparseable}"),
146            "unparseable MIDI message: [90, 3c]"
147        );
148
149        #[cfg(feature = "io")]
150        {
151            let invalid: Error = IoError::InvalidName(NameError::ContainsNul(nul_error())).into();
152            assert_eq!(format!("{invalid}"), "invalid name: name contains NUL byte");
153
154            let overflow: Error = IoError::InboundOverflow { dropped: 42 }.into();
155            assert_eq!(
156                format!("{overflow}"),
157                "inbound stream overflow - 42 message(s) dropped"
158            );
159        }
160    }
161
162    #[test]
163    fn unparseable_returns_offending_bytes() {
164        let raw = crate::RawMidiMessage::from_slice(&[0xF4, 0x05]);
165        let err = crate::MidiMessage::try_from(raw).unwrap_err();
166        let CodecError::Unparseable(returned) = err else {
167            panic!("expected Unparseable, got {err:?}");
168        };
169        assert_eq!(&*returned, &[0xF4, 0x05]);
170    }
171}