1use serde_json::Value;
8use thiserror::Error;
9
10#[derive(Debug, Clone)]
31pub struct ParseError {
32 pub raw_line: String,
34 pub raw_json: Option<Value>,
37 pub error_message: String,
39 pub method: Option<String>,
42}
43
44impl ParseError {
45 pub fn from_line(line: impl Into<String>, error: serde_json::Error) -> Self {
47 let raw_line = line.into();
48 let raw_json = serde_json::from_str::<Value>(&raw_line).ok();
49 ParseError {
50 raw_line,
51 raw_json,
52 error_message: error.to_string(),
53 method: None,
54 }
55 }
56
57 pub fn from_envelope(
64 method: impl Into<String>,
65 params: Option<Value>,
66 error: serde_json::Error,
67 ) -> Self {
68 let method = method.into();
69 let raw_line = match ¶ms {
70 Some(p) => format!(
71 r#"{{"method":{},"params":{}}}"#,
72 serde_json::to_string(&method).unwrap_or_else(|_| "\"<unserializable>\"".into()),
73 serde_json::to_string(p).unwrap_or_else(|_| "null".into()),
74 ),
75 None => format!(
76 r#"{{"method":{}}}"#,
77 serde_json::to_string(&method).unwrap_or_else(|_| "\"<unserializable>\"".into()),
78 ),
79 };
80 ParseError {
81 raw_line,
82 raw_json: params,
83 error_message: error.to_string(),
84 method: Some(method),
85 }
86 }
87}
88
89impl std::fmt::Display for ParseError {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match &self.method {
92 Some(m) => write!(
93 f,
94 "Failed to decode params for method {:?}: {} (raw: {})",
95 m, self.error_message, self.raw_line
96 ),
97 None => write!(
98 f,
99 "Failed to parse JSON-RPC message: {} (raw: {})",
100 self.error_message, self.raw_line
101 ),
102 }
103 }
104}
105
106impl std::error::Error for ParseError {}
107
108#[derive(Error, Debug)]
110pub enum Error {
111 #[error("JSON error: {0}")]
116 Json(#[from] serde_json::Error),
117
118 #[error("IO error: {0}")]
122 Io(#[from] std::io::Error),
123
124 #[error("Protocol error: {0}")]
126 Protocol(String),
127
128 #[error("Connection closed")]
130 ConnectionClosed,
131
132 #[error("Deserialization error: {0}")]
139 Deserialization(#[from] ParseError),
140
141 #[error("Process exited with status {0}: {1}")]
143 ProcessFailed(i32, String),
144
145 #[error("JSON-RPC error ({code}): {message}")]
150 JsonRpc { code: i64, message: String },
151
152 #[error("Server closed connection")]
156 ServerClosed,
157
158 #[error("Binary not found: '{name}' is not on PATH. Is it installed?")]
160 BinaryNotFound { name: String },
161
162 #[error("Unknown error: {0}")]
164 Unknown(String),
165}
166
167pub type Result<T> = std::result::Result<T, Error>;
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use serde_json::json;
174
175 fn serde_err(s: &str) -> serde_json::Error {
176 serde_json::from_str::<Value>(s).unwrap_err()
177 }
178
179 #[test]
180 fn parse_error_from_line_valid_json_populates_raw_json() {
181 let line = r#"{"foo":"bar"}"#;
182 let err = serde_json::from_str::<i32>(line).unwrap_err();
185
186 let pe = ParseError::from_line(line, err);
187 assert_eq!(pe.raw_line, line);
188 assert_eq!(pe.raw_json, Some(json!({"foo": "bar"})));
189 assert!(pe.method.is_none());
190 assert!(!pe.error_message.is_empty());
191 }
192
193 #[test]
194 fn parse_error_from_line_invalid_json_has_none_raw_json() {
195 let line = "not-json{";
196 let err = serde_err(line);
197
198 let pe = ParseError::from_line(line, err);
199 assert_eq!(pe.raw_line, line);
200 assert!(pe.raw_json.is_none());
201 assert!(pe.method.is_none());
202 }
203
204 #[test]
205 fn parse_error_from_envelope_carries_method_params_and_reconstructs_line() {
206 let params = json!({"callId": null, "kind": "fileChange"});
207 let err = serde_json::from_value::<i32>(params.clone()).unwrap_err();
208
209 let pe =
210 ParseError::from_envelope("item/fileChange/requestApproval", Some(params.clone()), err);
211
212 assert_eq!(
213 pe.method.as_deref(),
214 Some("item/fileChange/requestApproval")
215 );
216 assert_eq!(pe.raw_json, Some(params.clone()));
217
218 let v: Value = serde_json::from_str(&pe.raw_line).unwrap();
220 assert_eq!(v["method"], "item/fileChange/requestApproval");
221 assert_eq!(v["params"], params);
222 }
223
224 #[test]
225 fn parse_error_from_envelope_handles_missing_params() {
226 let err = serde_err("not json");
227 let pe = ParseError::from_envelope("turn/completed", None, err);
228 let v: Value = serde_json::from_str(&pe.raw_line).unwrap();
229 assert_eq!(v["method"], "turn/completed");
230 assert!(v.get("params").is_none());
231 assert!(pe.raw_json.is_none());
232 }
233
234 #[test]
235 fn error_deserialization_display_includes_method_and_raw() {
236 let params = json!({"foo": 1});
237 let err = serde_err("not json");
238 let pe = ParseError::from_envelope("item/bogus", Some(params), err);
239 let e: Error = Error::Deserialization(pe);
240 let rendered = format!("{}", e);
241 assert!(rendered.contains("item/bogus"), "got: {}", rendered);
242 assert!(rendered.contains("foo"), "got: {}", rendered);
243 }
244}