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}