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 MultipleMatches(Vec<String>),
26 Io(std::io::Error),
27 Cli { message: String, code: i32 },
28}
29
30impl fmt::Display for WtgError {
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::Git(e) => write!(f, "Git error: {e}"),
59 Self::GitHub(e) => {
60 if is_rate_limit_error(e) {
61 writeln!(
62 f,
63 "{}",
64 "⏱️ Whoa there, speed demon! GitHub says you're moving too fast."
65 .yellow()
66 .bold()
67 )?;
68 writeln!(f)?;
69 writeln!(
70 f,
71 " {}",
72 "You've hit the rate limit. Maybe take a coffee break? ☕".yellow()
73 )?;
74 writeln!(
75 f,
76 " {}",
77 "Or set a GITHUB_TOKEN to get higher limits.".yellow()
78 )
79 } else {
80 write!(f, "GitHub error: {e}")
81 }
82 }
83 Self::MultipleMatches(types) => {
84 writeln!(f, "{}", "💥 OH MY, YOU BLEW ME UP!".red().bold())?;
85 writeln!(f)?;
86 writeln!(
87 f,
88 " {}",
89 "This matches EVERYTHING and I don't know what to do! 🤯".red()
90 )?;
91 writeln!(f)?;
92 writeln!(f, " {}", "Matches:".yellow())?;
93 for t in types {
94 writeln!(f, " {} {}", "✓".green(), t)?;
95 }
96 panic!("💥 BOOM! You broke me!");
97 }
98 Self::Io(e) => write!(f, "I/O error: {e}"),
99 Self::Cli { message, .. } => write!(f, "{message}"),
100 }
101 }
102}
103
104impl std::error::Error for WtgError {}
105
106impl From<git2::Error> for WtgError {
107 fn from(err: git2::Error) -> Self {
108 Self::Git(err)
109 }
110}
111
112impl From<octocrab::Error> for WtgError {
113 fn from(err: octocrab::Error) -> Self {
114 Self::GitHub(err)
115 }
116}
117
118impl From<std::io::Error> for WtgError {
119 fn from(err: std::io::Error) -> Self {
120 Self::Io(err)
121 }
122}
123
124impl WtgError {
125 pub const fn exit_code(&self) -> i32 {
126 match self {
127 Self::Cli { code, .. } => *code,
128 _ => 1,
129 }
130 }
131}