Skip to main content

anybox/
error.rs

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        /// each box that answered, with what it couldn't do
33        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    /// one of each error with every field it has filled in, for testing how code handles them with no box
166    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            // access denied on Windows, an I/O error on Linux and macOS
238            Error::Io(std::io::Error::from_raw_os_error(5)),
239        ]
240    }
241}
242
243#[cfg(test)]
244mod tests;