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("backend not ready")]
39    NotReady,
40    #[error("command channel full - backend thread is not processing commands")]
41    BackendCommandChannelFull,
42
43    #[error(transparent)]
44    Platform(#[from] PlatformError),
45
46    #[error("inbound stream overflow - {dropped} message(s) dropped")]
47    InboundOverflow { dropped: usize },
48
49    #[error("unsupported on this platform")]
50    Unsupported,
51
52    #[error("another endpoint already holds that unique ID")]
53    UniqueIdTaken,
54
55    #[error("MIDI access denied")]
56    PermissionDenied,
57
58    #[error("MIDI error: {0}")]
59    Web(String),
60}
61
62#[cfg(feature = "io")]
63#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
64#[non_exhaustive]
65pub enum PlatformError {
66    #[error("client initialization failed: backend error code {0}")]
67    ClientInit(i32),
68    #[error("client initialization failed: IO thread initialization failed")]
69    ThreadInit,
70    #[error("connect failed: backend error code {0}")]
71    Connect(i32),
72    #[error("send failed: backend error code {0}")]
73    Send(i32),
74    #[error("send failed: MIDI encoder produced no event for valid input")]
75    Encode,
76    #[error("virtual port creation failed: backend error code {0}")]
77    VirtualPortCreate(i32),
78}
79
80#[cfg(feature = "io")]
81#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
82#[non_exhaustive]
83pub enum NameError {
84    #[error("name contains NUL byte")]
85    ContainsNul(#[from] std::ffi::NulError),
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::ParseError;
92
93    #[cfg(feature = "io")]
94    fn nul_error() -> std::ffi::NulError {
95        std::ffi::CString::new("a\0b").unwrap_err()
96    }
97
98    #[test]
99    fn error_is_clone_and_partial_eq() {
100        let s1: Error = CodecError::SysexTooLong { len: 100, max: 50 }.into();
101        let s2: Error = CodecError::SysexTooLong { len: 100, max: 50 }.into();
102        assert_eq!(s1.clone(), s2);
103
104        let p1: Error = CodecError::Parse {
105            reason: ParseError::Empty,
106            bytes: vec![],
107        }
108        .into();
109        let p2: Error = CodecError::Parse {
110            reason: ParseError::Empty,
111            bytes: vec![],
112        }
113        .into();
114        assert_eq!(p1.clone(), p2);
115
116        let u1: Error = CodecError::Unparseable(crate::RawMidiMessage::from_slice(&[0xF4])).into();
117        let u2: Error = CodecError::Unparseable(crate::RawMidiMessage::from_slice(&[0xF4])).into();
118        assert_eq!(u1.clone(), u2);
119
120        #[cfg(all(feature = "io", any(target_os = "macos", target_os = "ios")))]
121        {
122            let e1: Error = IoError::InvalidName(NameError::ContainsNul(nul_error())).into();
123            let e2: Error = IoError::InvalidName(NameError::ContainsNul(nul_error())).into();
124            assert_eq!(e1.clone(), e2);
125
126            let c1: Error = IoError::Platform(PlatformError::Send(-1)).into();
127            let c2: Error = IoError::Platform(PlatformError::Send(-1)).into();
128            assert_eq!(c1.clone(), c2);
129        }
130    }
131
132    #[cfg(feature = "io")]
133    #[test]
134    fn name_error_display() {
135        assert_eq!(
136            format!("{}", NameError::ContainsNul(nul_error())),
137            "name contains NUL byte"
138        );
139    }
140
141    #[test]
142    fn error_display() {
143        let sysex: Error = CodecError::SysexTooLong { len: 100, max: 50 }.into();
144        assert_eq!(format!("{sysex}"), "sysex too long: 100 bytes (max 50)");
145
146        let parse: Error = CodecError::Parse {
147            reason: ParseError::Empty,
148            bytes: vec![],
149        }
150        .into();
151        assert_eq!(
152            format!("{parse}"),
153            "failed to parse MIDI message: empty message (bytes: [])"
154        );
155
156        let unparseable: Error =
157            CodecError::Unparseable(crate::RawMidiMessage::from_slice(&[0x90, 0x3c])).into();
158        assert_eq!(
159            format!("{unparseable}"),
160            "unparseable MIDI message: [90, 3c]"
161        );
162
163        #[cfg(feature = "io")]
164        {
165            let invalid: Error = IoError::InvalidName(NameError::ContainsNul(nul_error())).into();
166            assert_eq!(format!("{invalid}"), "invalid name: name contains NUL byte");
167
168            let overflow: Error = IoError::InboundOverflow { dropped: 42 }.into();
169            assert_eq!(
170                format!("{overflow}"),
171                "inbound stream overflow - 42 message(s) dropped"
172            );
173        }
174    }
175
176    #[test]
177    fn unparseable_returns_offending_bytes() {
178        let raw = crate::RawMidiMessage::from_slice(&[0xF4, 0x05]);
179        let err = crate::MidiMessage::try_from(raw).unwrap_err();
180        let CodecError::Unparseable(returned) = err else {
181            panic!("expected Unparseable, got {err:?}");
182        };
183        assert_eq!(&*returned, &[0xF4, 0x05]);
184    }
185}