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}