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}