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}