1use serde_json::Value;
2use thiserror::Error;
3
4use crate::ipc::error_codes::ErrorCategory;
5
6#[derive(Error, Debug)]
7pub enum ClientError {
8 #[error("Failed to connect to daemon: {0}")]
9 ConnectionFailed(#[from] std::io::Error),
10
11 #[error("Failed to serialize request: {0}")]
12 SerializationFailed(#[from] serde_json::Error),
13
14 #[error("RPC error ({code}): {message}")]
15 RpcError {
16 code: i32,
17 message: String,
18 category: Option<ErrorCategory>,
19 retryable: bool,
20 context: Option<Value>,
21 suggestion: Option<String>,
22 },
23
24 #[error("Daemon not running")]
25 DaemonNotRunning,
26
27 #[error("Invalid response from daemon")]
28 InvalidResponse,
29
30 #[error("Failed to send signal to process {pid}: {message}")]
31 SignalFailed { pid: u32, message: String },
32}
33
34impl ClientError {
35 pub fn is_retryable(&self) -> bool {
36 match self {
37 ClientError::RpcError { retryable, .. } => *retryable,
38 _ => false,
39 }
40 }
41
42 pub fn category(&self) -> Option<ErrorCategory> {
43 match self {
44 ClientError::RpcError { category, .. } => *category,
45 _ => None,
46 }
47 }
48
49 pub fn suggestion(&self) -> Option<&str> {
50 match self {
51 ClientError::RpcError { suggestion, .. } => suggestion.as_deref(),
52 _ => None,
53 }
54 }
55
56 pub fn context(&self) -> Option<&Value> {
57 match self {
58 ClientError::RpcError { context, .. } => context.as_ref(),
59 _ => None,
60 }
61 }
62
63 pub fn to_json(&self) -> Value {
64 match self {
65 ClientError::RpcError {
66 code,
67 message,
68 category,
69 retryable,
70 context,
71 suggestion,
72 } => {
73 let mut obj = serde_json::json!({
74 "code": code,
75 "message": message,
76 "retryable": retryable,
77 });
78 if let Some(cat) = category {
79 obj["category"] = serde_json::json!(cat.as_str());
80 }
81 if let Some(ctx) = context {
82 obj["context"] = ctx.clone();
83 }
84 if let Some(sug) = suggestion {
85 obj["suggestion"] = serde_json::json!(sug);
86 }
87 obj
88 }
89 ClientError::ConnectionFailed(e) => serde_json::json!({
90 "code": -32000,
91 "message": format!("Connection failed: {}", e),
92 "category": "external",
93 "retryable": true,
94 }),
95 ClientError::DaemonNotRunning => serde_json::json!({
96 "code": -32000,
97 "message": "Daemon not running",
98 "category": "external",
99 "retryable": false,
100 "suggestion": "Start daemon with: agent-tui daemon",
101 }),
102 ClientError::InvalidResponse => serde_json::json!({
103 "code": -32000,
104 "message": "Invalid response from daemon",
105 "category": "internal",
106 "retryable": false,
107 }),
108 ClientError::SerializationFailed(e) => serde_json::json!({
109 "code": -32000,
110 "message": format!("Serialization failed: {}", e),
111 "category": "internal",
112 "retryable": false,
113 }),
114 ClientError::SignalFailed { pid, message } => serde_json::json!({
115 "code": -32000,
116 "message": format!("Failed to send signal to process {}: {}", pid, message),
117 "category": "external",
118 "retryable": false,
119 "exit_code": 74,
120 }),
121 }
122 }
123}