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