1use thiserror::Error;
14
15#[derive(Debug, Error)]
22pub enum CargoFreshError {
23 #[error("`cargo install --list` failed: {source}")]
25 CargoListFailed {
26 #[source]
27 source: anyhow::Error,
28 },
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum Hint {
36 CargoListFailed,
38 NetworkConnectTimeout,
40 InvalidGlob,
42}
43
44impl Hint {
45 pub fn locale_key(self) -> &'static str {
47 match self {
48 Hint::CargoListFailed => "hint_cargo_list_failed",
49 Hint::NetworkConnectTimeout => "hint_network_connect_timeout",
50 Hint::InvalidGlob => "hint_invalid_glob",
51 }
52 }
53}
54
55pub fn hint_for(err: &anyhow::Error) -> Option<Hint> {
68 for cause in err.chain() {
69 if cause.downcast_ref::<CargoFreshError>().is_some() {
70 return Some(Hint::CargoListFailed);
72 }
73 if let Some(re) = cause.downcast_ref::<reqwest::Error>() {
74 if re.is_connect() || re.is_timeout() {
75 return Some(Hint::NetworkConnectTimeout);
76 }
77 }
78 if cause.downcast_ref::<globset::Error>().is_some() {
79 return Some(Hint::InvalidGlob);
80 }
81 }
82 None
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn hint_for_cargo_list_failed() {
91 let err: anyhow::Error = CargoFreshError::CargoListFailed {
92 source: anyhow::anyhow!("exit code 101"),
93 }
94 .into();
95 assert_eq!(hint_for(&err), Some(Hint::CargoListFailed));
96 }
97
98 #[test]
99 fn hint_for_wrapped_cargo_fresh_error() {
100 let err = anyhow::Error::from(CargoFreshError::CargoListFailed {
102 source: anyhow::anyhow!("exit code 101"),
103 })
104 .context("while listing installed packages");
105 assert_eq!(hint_for(&err), Some(Hint::CargoListFailed));
106 }
107
108 #[test]
109 fn hint_for_unrelated_error_returns_none() {
110 let err = anyhow::anyhow!("something else entirely");
111 assert!(hint_for(&err).is_none());
112 }
113
114 #[test]
115 fn hint_for_invalid_glob() {
116 let mut packages = Vec::new();
119 let err = crate::package::filter_packages(&mut packages, "[unclosed")
120 .expect_err("unclosed bracket should be an invalid glob");
121 assert_eq!(hint_for(&err), Some(Hint::InvalidGlob));
122 }
123
124 #[test]
125 fn every_hint_has_bilingual_text() {
126 use crate::locale::texts::{get_chinese_text, get_english_text};
129 for hint in [
130 Hint::CargoListFailed,
131 Hint::NetworkConnectTimeout,
132 Hint::InvalidGlob,
133 ] {
134 let key = hint.locale_key();
135 assert!(!get_english_text(key).is_empty(), "missing EN for {key}");
136 assert!(!get_chinese_text(key).is_empty(), "missing ZH for {key}");
137 }
138 }
139}