1use thiserror::Error;
2
3#[derive(Debug, Error)]
4pub enum Error {
5 #[error("Usage error. ({0}) Invalid input. → Check the command syntax.")]
6 Usage(String),
7 #[error(
8 "Cannot reach DuckDuckGo. ({0}) Network request failed. → Check connectivity, set --proxy, or increase --timeout."
9 )]
10 Network(String),
11 #[error(
12 "Unexpected DuckDuckGo response. ({0}) Remote response was not usable. → Retry later or file an issue if this persists."
13 )]
14 Remote(String),
15 #[error(
16 "Unexpected DuckDuckGo HTML structure. ({0}) Selectors may have changed. → Update duckduckgo-cli or file an issue with -vv output and the query used."
17 )]
18 Parse(String),
19 #[error(
20 "Search blocked by DuckDuckGo. ({0}) Anti-bot rate limit exceeded. → Wait 60s, route through --proxy, or reduce concurrency."
21 )]
22 Blocked(String),
23 #[error(
24 "Local I/O error. ({0}) Could not read or write local files. → Check permissions and available disk space."
25 )]
26 Io(String),
27}
28
29pub type Result<T> = std::result::Result<T, Error>;
30
31impl Error {
32 #[must_use]
33 pub fn exit_code(&self) -> i32 {
34 match self {
35 Self::Usage(_) => 2,
36 Self::Network(_) | Self::Remote(_) => 3,
37 Self::Parse(_) => 4,
38 Self::Blocked(_) => 5,
39 Self::Io(_) => 6,
40 }
41 }
42}
43
44impl From<std::io::Error> for Error {
45 fn from(value: std::io::Error) -> Self {
46 Self::Io(value.to_string())
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::Error;
53
54 #[test]
55 fn exit_codes_match_spec() {
56 assert_eq!(Error::Usage("x".to_owned()).exit_code(), 2);
57 assert_eq!(Error::Network("x".to_owned()).exit_code(), 3);
58 assert_eq!(Error::Remote("x".to_owned()).exit_code(), 3);
59 assert_eq!(Error::Parse("x".to_owned()).exit_code(), 4);
60 assert_eq!(Error::Blocked("x".to_owned()).exit_code(), 5);
61 assert_eq!(Error::Io("x".to_owned()).exit_code(), 6);
62 }
63
64 #[test]
65 fn display_includes_context_and_guidance() {
66 let message = Error::Blocked("http_202".to_owned()).to_string();
67 assert!(message.contains("Search blocked"));
68 assert!(message.contains("http_202"));
69 assert!(message.contains("Wait 60s"));
70 }
71}