wtg_cli/
error.rs

1use crossterm::style::Stylize;
2use std::fmt;
3
4pub type Result<T> = std::result::Result<T, WtgError>;
5
6/// Check if an octocrab error is a rate limit error
7fn is_rate_limit_error(err: &octocrab::Error) -> bool {
8    if let octocrab::Error::GitHub { source, .. } = err {
9        // HTTP 403 with rate limit message, or HTTP 429 (secondary rate limit)
10        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}