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}