1use std::borrow::Cow;
2
3use crate::capability::{Action, Family, FerrumApi, Firmware, StableId};
4
5pub type Result<T, E = Error> = std::result::Result<T, E>;
6
7#[derive(Debug, thiserror::Error)]
8#[non_exhaustive]
9pub enum Error {
10 #[error("no box answered{}, check it's plugged in and give it a few seconds if you just plugged it in. without a port, anybox asks CH343 ports (1A86:55D3) and YSC V2 left ports (1A86:FE0C with serial TOWMCULEFT), so if your box sits behind another chip, name its port with Connect::port", tried_text(.tried))]
11 #[non_exhaustive]
12 NotFound { tried: Vec<String> },
13
14 #[error("this device was closed, open the box again if you need it")]
15 Closed,
16
17 #[error(
18 "{port} is already open in another program or another Device in this one, close it there first"
19 )]
20 #[non_exhaustive]
21 PortBusy { port: String },
22
23 #[error(
24 "you're not allowed to open {port}, on Linux that usually means adding yourself to the dialout group"
25 )]
26 #[non_exhaustive]
27 PermissionDenied { port: String },
28
29 #[error("{}, so plug in a box that can, or ask Connect::can for less", incapable_text(.boxes))]
30 #[non_exhaustive]
31 NoBoxCan {
32 boxes: Vec<(crate::connect::Found, Vec<Action>)>,
34 },
35
36 #[error("the box on {port} isn't a {expected}{}, pick the right family or leave it out and let anybox find it. if you just plugged it in, give it a couple seconds to start first", found_text(.found))]
37 #[non_exhaustive]
38 WrongFamily {
39 port: String,
40 expected: Family,
41 found: Option<Family>,
42 },
43
44 #[error("the box on {port} is {}, not {expected}, so use Connect::auto().id(..) and anybox finds it on whatever port it's on", found_id_text(.found))]
45 #[non_exhaustive]
46 WrongBox {
47 port: String,
48 expected: StableId,
49 found: Option<StableId>,
50 },
51
52 #[error("the Ferrum on {port} is on {found}, not {expected}{}", ferrum_api_hint(*.expected))]
53 #[non_exhaustive]
54 WrongFerrumApi {
55 port: String,
56 expected: FerrumApi,
57 found: FerrumApi,
58 },
59
60 #[error(
61 "this {family} runs firmware {found} and anybox needs {needed} or newer, so update the box first"
62 )]
63 #[non_exhaustive]
64 FirmwareTooOld {
65 family: Family,
66 found: Firmware,
67 needed: Firmware,
68 },
69
70 #[error(
71 "this {family} runs firmware {found}, which is newer than this anybox knows, so update anybox"
72 )]
73 #[non_exhaustive]
74 LibraryTooOld { family: Family, found: Firmware },
75
76 #[error("{family} can't {action}, {reason}")]
77 #[non_exhaustive]
78 Unsupported {
79 action: Action,
80 family: Family,
81 reason: Cow<'static, str>,
82 },
83
84 #[error("can't {action} like that, {detail}")]
85 #[non_exhaustive]
86 InvalidArgument {
87 action: Action,
88 detail: Cow<'static, str>,
89 },
90
91 #[error("the box refused to {action}, {detail}")]
92 #[non_exhaustive]
93 Rejected { action: Action, detail: String },
94
95 #[error(
96 "the box didn't answer in time when asked to {action}, check it's still plugged in and nothing else is talking to it"
97 )]
98 #[non_exhaustive]
99 Timeout { action: Action },
100
101 #[error(
102 "the box is disconnected and anybox is already trying to reconnect, so try again once you get Reconnected"
103 )]
104 Disconnected,
105
106 #[error(
107 "couldn't {action} because the box reconnected or reset in the middle of it. anybox already let go of it, so do it again if you still want it"
108 )]
109 #[non_exhaustive]
110 Interrupted { action: Action },
111
112 #[error(transparent)]
113 Io(#[from] std::io::Error),
114}
115
116fn incapable_text(boxes: &[(crate::connect::Found, Vec<Action>)]) -> String {
117 let each: Vec<String> = boxes
118 .iter()
119 .map(|(found, missing)| {
120 let missing: Vec<String> = missing.iter().map(ToString::to_string).collect();
121 format!(
122 "the {} on {} can't {}",
123 found.info.family,
124 found.port,
125 missing.join(" or ")
126 )
127 })
128 .collect();
129 each.join(", ")
130}
131
132fn tried_text(tried: &[String]) -> String {
133 if tried.is_empty() {
134 String::new()
135 } else {
136 format!(" on {}", tried.join(", "))
137 }
138}
139
140fn found_id_text(found: &Option<StableId>) -> String {
141 match found {
142 Some(id) => id.to_string(),
143 None => "a box with no id".into(),
144 }
145}
146
147fn found_text(found: &Option<Family>) -> String {
148 match found {
149 Some(family) => format!(", it's a {family}"),
150 None => String::new(),
151 }
152}
153
154fn ferrum_api_hint(expected: FerrumApi) -> &'static str {
155 match expected {
156 FerrumApi::Software => {
157 ", so start the Ferrum app and open its port, or ask for .ferrum_api(FerrumApi::Legacy) if the box's own port is really what you want"
158 }
159 _ => ", so open the box's own port (the CP210x one) instead",
160 }
161}
162
163#[cfg(feature = "mock")]
164impl Error {
165 pub fn examples() -> Vec<Error> {
167 use crate::capability::{Capabilities, Info};
168 let mut makcu = Info::new(Family::MakcuV4);
169 makcu.port = "COM5".into();
170 let found = crate::connect::Found {
171 port: "COM5".into(),
172 info: makcu,
173 capabilities: Capabilities::new(Vec::new()),
174 };
175 vec![
176 Error::NotFound {
177 tried: vec!["COM3".into(), "COM4".into()],
178 },
179 Error::Closed,
180 Error::PortBusy {
181 port: "COM3".into(),
182 },
183 Error::PermissionDenied {
184 port: "/dev/ttyACM0".into(),
185 },
186 Error::NoBoxCan {
187 boxes: vec![(found, vec![Action::KeyTap, Action::LockKey])],
188 },
189 Error::WrongFamily {
190 port: "COM3".into(),
191 expected: Family::Ferrum,
192 found: Some(Family::MakcuV4),
193 },
194 Error::WrongBox {
195 port: "COM3".into(),
196 expected: StableId::UsbSerial("ABC123".into()),
197 found: Some(StableId::Mac([1, 2, 3, 4, 5, 6])),
198 },
199 Error::WrongFerrumApi {
200 port: "COM3".into(),
201 expected: FerrumApi::Software,
202 found: FerrumApi::Legacy,
203 },
204 Error::FirmwareTooOld {
205 family: Family::Medius,
206 found: Firmware::Protocol(8),
207 needed: Firmware::Version {
208 major: 3,
209 minor: 4,
210 patch: 0,
211 },
212 },
213 Error::LibraryTooOld {
214 family: Family::MakcuV4,
215 found: Firmware::Text("V9.9".into()),
216 },
217 Error::Unsupported {
218 action: Action::KeyPress,
219 family: Family::Medius,
220 reason: "no keyboard is plugged into it".into(),
221 },
222 Error::InvalidArgument {
223 action: Action::ScaleAxis,
224 detail: "300 is out of range, a scale goes from -255 to 255".into(),
225 },
226 Error::Rejected {
227 action: Action::Move,
228 detail: "its queue is full".into(),
229 },
230 Error::Timeout {
231 action: Action::LockAxis,
232 },
233 Error::Disconnected,
234 Error::Interrupted {
235 action: Action::Click,
236 },
237 Error::Io(std::io::Error::from_raw_os_error(5)),
239 ]
240 }
241}
242
243#[cfg(test)]
244mod tests;