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    #[allow(dead_code)] // Will be used for network error handling
26    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}