1use crossterm::style::Stylize;
2use std::fmt;
3
4pub type Result<T> = std::result::Result<T, WtgError>;
5
6fn is_rate_limit_error(err: &octocrab::Error) -> bool {
8 if let octocrab::Error::GitHub { source, .. } = err {
9 let status = source.status_code.as_u16();
11 (status == 403
12 && (source.message.contains("rate limit") || source.message.contains("API rate limit")))
13 || status == 429
14 } else {
15 false
16 }
17}
18
19#[derive(Debug)]
20pub enum WtgError {
21 NotInGitRepo,
22 NotFound(String),
23 Git(git2::Error),
24 GitHub(octocrab::Error),
25 #[allow(dead_code)] NetworkUnavailable,
27 MultipleMatches(Vec<String>),
28 Io(std::io::Error),
29 Cli {
30 message: String,
31 code: i32,
32 },
33}
34
35impl fmt::Display for WtgError {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 Self::NotInGitRepo => {
39 writeln!(
40 f,
41 "{}",
42 "❌ What the git are you asking me to do?".red().bold()
43 )?;
44 writeln!(f, " {}", "This isn't even a git repository! 😱".red())
45 }
46 Self::NotFound(input) => {
47 writeln!(
48 f,
49 "{}",
50 "🤔 Couldn't find this anywhere - are you sure you didn't make it up?"
51 .yellow()
52 .bold()
53 )?;
54 writeln!(f)?;
55 writeln!(f, " {}", "Tried:".yellow())?;
56 writeln!(f, " {} Commit hash (local + remote)", "❌".red())?;
57 writeln!(f, " {} GitHub issue/PR", "❌".red())?;
58 writeln!(f, " {} File in repo", "❌".red())?;
59 writeln!(f, " {} Git tag", "❌".red())?;
60 writeln!(f)?;
61 writeln!(f, " {}: {}", "Input was".yellow(), input.as_str().cyan())
62 }
63 Self::Git(e) => write!(f, "Git error: {e}"),
64 Self::GitHub(e) => {
65 if is_rate_limit_error(e) {
66 writeln!(
67 f,
68 "{}",
69 "⏱️ Whoa there, speed demon! GitHub says you're moving too fast."
70 .yellow()
71 .bold()
72 )?;
73 writeln!(f)?;
74 writeln!(
75 f,
76 " {}",
77 "You've hit the rate limit. Maybe take a coffee break? ☕".yellow()
78 )?;
79 writeln!(
80 f,
81 " {}",
82 "Or set a GITHUB_TOKEN to get higher limits.".yellow()
83 )
84 } else {
85 write!(f, "GitHub error: {e}")
86 }
87 }
88 Self::NetworkUnavailable => {
89 writeln!(
90 f,
91 "{}",
92 "🌐 Network is MIA - this might be an issue, might be your imagination."
93 .yellow()
94 )?;
95 writeln!(f, " {}", "Can't reach GitHub to confirm.".yellow())
96 }
97 Self::MultipleMatches(types) => {
98 writeln!(f, "{}", "💥 OH MY, YOU BLEW ME UP!".red().bold())?;
99 writeln!(f)?;
100 writeln!(
101 f,
102 " {}",
103 "This matches EVERYTHING and I don't know what to do! 🤯".red()
104 )?;
105 writeln!(f)?;
106 writeln!(f, " {}", "Matches:".yellow())?;
107 for t in types {
108 writeln!(f, " {} {}", "✓".green(), t)?;
109 }
110 panic!("💥 BOOM! You broke me!");
111 }
112 Self::Io(e) => write!(f, "I/O error: {e}"),
113 Self::Cli { message, .. } => write!(f, "{message}"),
114 }
115 }
116}
117
118impl std::error::Error for WtgError {}
119
120impl From<git2::Error> for WtgError {
121 fn from(err: git2::Error) -> Self {
122 Self::Git(err)
123 }
124}
125
126impl From<octocrab::Error> for WtgError {
127 fn from(err: octocrab::Error) -> Self {
128 Self::GitHub(err)
129 }
130}
131
132impl From<std::io::Error> for WtgError {
133 fn from(err: std::io::Error) -> Self {
134 Self::Io(err)
135 }
136}
137
138impl WtgError {
139 pub const fn exit_code(&self) -> i32 {
140 match self {
141 Self::Cli { code, .. } => *code,
142 _ => 1,
143 }
144 }
145}