Skip to main content

clickup_cli/
error.rs

1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum CliError {
5    #[error("{message}")]
6    ClientError { message: String, status: u16 },
7    #[error("{message}")]
8    AuthError { message: String },
9    #[error("{message}")]
10    Forbidden { message: String },
11    #[error("{message}")]
12    NotFound {
13        message: String,
14        resource_id: String,
15    },
16    #[error("{message}")]
17    RateLimited {
18        message: String,
19        retry_after: Option<u64>,
20    },
21    #[error("{message}")]
22    ServerError { message: String },
23    #[error("{0}")]
24    ConfigError(String),
25    #[error("{message}")]
26    BranchDetect { message: String, hint: String },
27    #[error("{0}")]
28    IoError(#[from] std::io::Error),
29}
30
31impl CliError {
32    pub fn exit_code(&self) -> i32 {
33        match self {
34            CliError::ClientError { .. } => 1,
35            CliError::AuthError { .. } => 2,
36            CliError::Forbidden { .. } => 2,
37            CliError::NotFound { .. } => 3,
38            CliError::RateLimited { .. } => 4,
39            CliError::ServerError { .. } => 5,
40            CliError::ConfigError(_) => 1,
41            CliError::BranchDetect { .. } => 1,
42            CliError::IoError(_) => 1,
43        }
44    }
45
46    pub fn print(&self, output_mode: &str) {
47        if output_mode == "json" {
48            let json = serde_json::json!({
49                "error": true,
50                "message": self.to_string(),
51                "exit_code": self.exit_code(),
52                "hint": self.hint(),
53            });
54            eprintln!("{}", serde_json::to_string_pretty(&json).unwrap());
55        } else {
56            eprintln!("Error: {}", self);
57            if let Some(status) = self.status() {
58                eprintln!("  Status:  {}", status);
59            }
60            if let Some(hint) = self.hint() {
61                eprintln!("  Hint:    {}", hint);
62            }
63        }
64    }
65
66    pub fn status(&self) -> Option<u16> {
67        match self {
68            CliError::ClientError { status, .. } => Some(*status),
69            CliError::AuthError { .. } => Some(401),
70            CliError::Forbidden { .. } => Some(403),
71            CliError::NotFound { .. } => Some(404),
72            CliError::RateLimited { .. } => Some(429),
73            CliError::ServerError { .. } => Some(500),
74            _ => None,
75        }
76    }
77
78    pub fn hint(&self) -> Option<String> {
79        match self {
80            CliError::AuthError { .. } => {
81                Some("Check your API token, or run 'clickup setup' to reconfigure".into())
82            }
83            CliError::Forbidden { .. } => Some(
84                "This feature may require a higher ClickUp plan (Business+, Enterprise)".into(),
85            ),
86            CliError::NotFound { resource_id, .. } => Some(format!(
87                "Check the ID '{}', or use --custom-task-id if using a custom task ID",
88                resource_id
89            )),
90            CliError::RateLimited { retry_after, .. } => {
91                retry_after.map(|s| format!("Rate limited. Retry after {} seconds", s))
92            }
93            CliError::ServerError { .. } => {
94                Some("ClickUp server error. Try again in a few seconds.".into())
95            }
96            CliError::ConfigError(_) => {
97                Some("Run 'clickup setup' to configure your API token".into())
98            }
99            CliError::BranchDetect { hint, .. } => Some(hint.clone()),
100            _ => None,
101        }
102    }
103}