wtg_cli/
error.rs

1use crossterm::style::Stylize;
2use http::StatusCode;
3use octocrab::Error as OctoError;
4use std::fmt;
5
6pub type WtgResult<T> = std::result::Result<T, WtgError>;
7
8#[derive(Debug, strum::EnumIs)]
9pub enum WtgError {
10    EmptyInput,
11    NotInGitRepo,
12    NotFound(String),
13    Unsupported(String),
14    Git(git2::Error),
15    GhConnectionLost,
16    GhRateLimit(OctoError),
17    GhSaml(OctoError),
18    GitHub(OctoError),
19    MultipleMatches(Vec<String>),
20    Io(std::io::Error),
21    Cli { message: String, code: i32 },
22    Timeout,
23    NotGitHubUrl(String),
24    MalformedGitHubUrl(String),
25    SecurityRejection(String),
26    GitHubClientFailed,
27}
28
29impl fmt::Display for WtgError {
30    #[allow(clippy::too_many_lines)]
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Self::NotInGitRepo => {
34                writeln!(
35                    f,
36                    "{}",
37                    "โŒ What the git are you asking me to do?".red().bold()
38                )?;
39                writeln!(f, "   {}", "This isn't even a git repository! ๐Ÿ˜ฑ".red())
40            }
41            Self::NotFound(input) => {
42                writeln!(
43                    f,
44                    "{}",
45                    "๐Ÿค” Couldn't find this anywhere - are you sure you didn't make it up?"
46                        .yellow()
47                        .bold()
48                )?;
49                writeln!(f)?;
50                writeln!(f, "   {}", "Tried:".yellow())?;
51                writeln!(f, "   {} Commit hash (local + remote)", "โŒ".red())?;
52                writeln!(f, "   {} GitHub issue/PR", "โŒ".red())?;
53                writeln!(f, "   {} File in repo", "โŒ".red())?;
54                writeln!(f, "   {} Git tag", "โŒ".red())?;
55                writeln!(f)?;
56                writeln!(f, "   {}: {}", "Input was".yellow(), input.as_str().cyan())
57            }
58            Self::Unsupported(operation) => {
59                writeln!(f, "{}", "๐Ÿšซ Can't do that here!".yellow().bold())?;
60                writeln!(f)?;
61                writeln!(
62                    f,
63                    "   {} is not supported with the current backend.",
64                    operation.as_str().cyan()
65                )
66            }
67            Self::Git(e) => write!(f, "Git error: {e}"),
68            Self::GhConnectionLost => {
69                writeln!(
70                    f,
71                    "{}",
72                    "๐Ÿ“ก Houston, we have a problem! Connection lost mid-flight!"
73                        .red()
74                        .bold()
75                )?;
76                writeln!(f)?;
77                writeln!(
78                    f,
79                    "   {}",
80                    "GitHub was there a second ago, now it's playing hide and seek. ๐Ÿ‘ป".red()
81                )?;
82                writeln!(
83                    f,
84                    "   {}",
85                    "Check your internet connection and try again!".yellow()
86                )
87            }
88            Self::GhRateLimit(_) => {
89                writeln!(
90                    f,
91                    "{}",
92                    "โฑ๏ธ  Whoa there, speed demon! GitHub says you're moving too fast."
93                        .yellow()
94                        .bold()
95                )?;
96                writeln!(f)?;
97                writeln!(
98                    f,
99                    "   {}",
100                    "You've hit the rate limit. Maybe take a coffee break? โ˜•".yellow()
101                )?;
102                writeln!(
103                    f,
104                    "   {}",
105                    "Or set a GITHUB_TOKEN to get higher limits.".yellow()
106                )
107            }
108            Self::GhSaml(_) => {
109                writeln!(
110                    f,
111                    "{}",
112                    "๐Ÿ” Halt! Who goes there? Your GitHub org wants to see some ID!"
113                        .red()
114                        .bold()
115                )?;
116                writeln!(f)?;
117                writeln!(
118                    f,
119                    "   {}",
120                    "Looks like SAML SSO is standing between you and your data. ๐Ÿšง".red()
121                )?;
122                writeln!(
123                    f,
124                    "   {}",
125                    "Try authenticating your GITHUB_TOKEN with SAML first!".red()
126                )
127            }
128            Self::GitHub(e) => write!(f, "GitHub error: {e}"),
129            Self::MultipleMatches(types) => {
130                writeln!(f, "{}", "๐Ÿ’ฅ OH MY, YOU BLEW ME UP!".red().bold())?;
131                writeln!(f)?;
132                writeln!(
133                    f,
134                    "   {}",
135                    "This matches EVERYTHING and I don't know what to do! ๐Ÿคฏ".red()
136                )?;
137                writeln!(f)?;
138                writeln!(f, "   {}", "Matches:".yellow())?;
139                for t in types {
140                    writeln!(f, "   {} {}", "โœ“".green(), t)?;
141                }
142                panic!("๐Ÿ’ฅ BOOM! You broke me!");
143            }
144            Self::Io(e) => write!(f, "I/O error: {e}"),
145            Self::Cli { message, .. } => write!(f, "{message}"),
146            Self::Timeout => {
147                writeln!(
148                    f,
149                    "{}",
150                    "โฐ Time's up! The internet took a nap.".red().bold()
151                )?;
152                writeln!(f)?;
153                writeln!(
154                    f,
155                    "   {}",
156                    "Did you forget to pay your internet bill? ๐Ÿ’ธ".red()
157                )
158            }
159            Self::NotGitHubUrl(url) => {
160                writeln!(
161                    f,
162                    "{}",
163                    "๐Ÿคจ That's a URL alright, but it's not GitHub!"
164                        .yellow()
165                        .bold()
166                )?;
167                writeln!(f)?;
168                writeln!(f, "   {}: {}", "You gave me".yellow(), url.clone().cyan())?;
169                writeln!(f)?;
170                writeln!(f, "   {}", "I only speak GitHub URLs, buddy! ๐Ÿ™".yellow())
171            }
172            Self::MalformedGitHubUrl(url) => {
173                writeln!(
174                    f,
175                    "{}",
176                    "๐Ÿ˜ต That GitHub URL is more broken than my ex's promises!"
177                        .red()
178                        .bold()
179                )?;
180                writeln!(f)?;
181                writeln!(f, "   {}: {}", "You gave me".red(), url.clone().cyan())?;
182                writeln!(f)?;
183                writeln!(
184                    f,
185                    "   {}",
186                    "Expected something like: https://github.com/owner/repo/issues/123".yellow()
187                )?;
188                writeln!(f, "   {}", "But this? This is just sad. ๐Ÿ˜ข".red())
189            }
190            Self::SecurityRejection(reason) => {
191                writeln!(f, "{}", "๐Ÿšจ Whoa there! Security alert!".red().bold())?;
192                writeln!(f)?;
193                writeln!(
194                    f,
195                    "   {}",
196                    "I can't process that input for personal reasons. ๐Ÿ›ก๏ธ".red()
197                )?;
198                writeln!(f)?;
199                writeln!(f, "   {}: {}", "Reason".yellow(), reason.clone())?;
200                writeln!(f)?;
201                writeln!(f, "   {}", "Please, try something safer? ๐Ÿ™".yellow())
202            }
203            Self::EmptyInput => {
204                writeln!(
205                    f,
206                    "{}",
207                    "๐Ÿซฅ Excuse me, but I can't read minds!".yellow().bold()
208                )?;
209                writeln!(f)?;
210                writeln!(
211                    f,
212                    "   {}",
213                    "You gave me... nothing. Nada. Zilch. The void! ๐Ÿ‘ป".yellow()
214                )?;
215                writeln!(f)?;
216                writeln!(
217                    f,
218                    "   {}",
219                    "Try giving me something to work with, please!".yellow()
220                )
221            }
222            Self::GitHubClientFailed => {
223                writeln!(
224                    f,
225                    "{}",
226                    "๐Ÿ”‘ Can't connect to GitHub! Something's blocking the path..."
227                        .red()
228                        .bold()
229                )?;
230                writeln!(f)?;
231                writeln!(
232                    f,
233                    "   {}",
234                    "You explicitly asked for GitHub data, but I can't reach it. ๐Ÿ˜ž".red()
235                )?;
236                writeln!(f)?;
237                writeln!(
238                    f,
239                    "   {}",
240                    "Check your GITHUB_TOKEN and network connection!".yellow()
241                )
242            }
243        }
244    }
245}
246
247impl std::error::Error for WtgError {}
248
249impl From<git2::Error> for WtgError {
250    fn from(err: git2::Error) -> Self {
251        Self::Git(err)
252    }
253}
254
255impl From<OctoError> for WtgError {
256    fn from(err: OctoError) -> Self {
257        if let OctoError::GitHub { ref source, .. } = err {
258            match source.status_code {
259                StatusCode::TOO_MANY_REQUESTS => return Self::GhRateLimit(err),
260                StatusCode::FORBIDDEN => {
261                    let msg_lower = source.message.to_ascii_lowercase();
262
263                    if msg_lower.to_ascii_lowercase().contains("saml") {
264                        return Self::GhSaml(err);
265                    }
266
267                    if msg_lower.contains("rate limit") {
268                        return Self::GhRateLimit(err);
269                    }
270
271                    return Self::GitHub(err);
272                }
273                _ => {
274                    return Self::GitHub(err);
275                }
276            }
277        }
278
279        Self::GitHub(err)
280    }
281}
282
283impl From<std::io::Error> for WtgError {
284    fn from(err: std::io::Error) -> Self {
285        Self::Io(err)
286    }
287}
288
289impl WtgError {
290    pub const fn exit_code(&self) -> i32 {
291        match self {
292            Self::Cli { code, .. } => *code,
293            _ => 1,
294        }
295    }
296}