1use std::path::PathBuf;
7
8#[derive(Debug, thiserror::Error)]
10pub enum Error {
11 #[error("LSP server initialization failed: {message}")]
13 LspInitFailed {
14 message: String,
16 },
17
18 #[error("LSP server error: {code} - {message}")]
20 LspServerError {
21 code: i32,
23 message: String,
25 },
26
27 #[error("MCP server error: {0}")]
29 McpServer(String),
30
31 #[error("document not found: {0}")]
33 DocumentNotFound(PathBuf),
34
35 #[error("no LSP server configured for language: {0}")]
37 NoServerForLanguage(String),
38
39 #[error("no LSP server configured")]
41 NoServerConfigured,
42
43 #[error("configuration error: {0}")]
45 Config(String),
46
47 #[error("configuration file not found: {0}")]
49 ConfigNotFound(PathBuf),
50
51 #[error("invalid configuration: {0}")]
53 InvalidConfig(String),
54
55 #[error("I/O error: {0}")]
57 Io(#[from] std::io::Error),
58
59 #[error("JSON error: {0}")]
61 Json(#[from] serde_json::Error),
62
63 #[error("TOML parsing error: {0}")]
65 Toml(#[from] toml::de::Error),
66
67 #[error("transport error: {0}")]
69 Transport(String),
70
71 #[error("request timed out after {0} seconds")]
73 Timeout(u64),
74
75 #[error("server shutdown requested")]
77 Shutdown,
78
79 #[error("failed to spawn LSP server '{command}': {source}")]
81 ServerSpawnFailed {
82 command: String,
84 #[source]
86 source: std::io::Error,
87 },
88
89 #[error("LSP protocol error: {0}")]
91 LspProtocolError(String),
92
93 #[error("invalid URI: {0}")]
95 InvalidUri(String),
96
97 #[error("position encoding error: {0}")]
99 EncodingError(String),
100
101 #[error("LSP server process terminated unexpectedly")]
103 ServerTerminated,
104
105 #[error("invalid tool parameters: {0}")]
107 InvalidToolParams(String),
108
109 #[error("file I/O error for {path:?}: {source}")]
111 FileIo {
112 path: PathBuf,
114 #[source]
116 source: std::io::Error,
117 },
118
119 #[error("path outside workspace: {0}")]
121 PathOutsideWorkspace(PathBuf),
122
123 #[error("document limit exceeded: {current}/{max}")]
125 DocumentLimitExceeded {
126 current: usize,
128 max: usize,
130 },
131
132 #[error("file size limit exceeded: {size} bytes (max: {max} bytes)")]
134 FileSizeLimitExceeded {
135 size: u64,
137 max: u64,
139 },
140}
141
142pub type Result<T> = std::result::Result<T, Error>;
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_error_display_lsp_init_failed() {
151 let err = Error::LspInitFailed {
152 message: "server not found".to_string(),
153 };
154 assert_eq!(
155 err.to_string(),
156 "LSP server initialization failed: server not found"
157 );
158 }
159
160 #[test]
161 fn test_error_display_lsp_server_error() {
162 let err = Error::LspServerError {
163 code: -32600,
164 message: "Invalid request".to_string(),
165 };
166 assert_eq!(
167 err.to_string(),
168 "LSP server error: -32600 - Invalid request"
169 );
170 }
171
172 #[test]
173 fn test_error_display_document_not_found() {
174 let err = Error::DocumentNotFound(PathBuf::from("/path/to/file.rs"));
175 assert!(err.to_string().contains("document not found"));
176 assert!(err.to_string().contains("file.rs"));
177 }
178
179 #[test]
180 fn test_error_display_no_server_for_language() {
181 let err = Error::NoServerForLanguage("rust".to_string());
182 assert_eq!(
183 err.to_string(),
184 "no LSP server configured for language: rust"
185 );
186 }
187
188 #[test]
189 fn test_error_display_timeout() {
190 let err = Error::Timeout(30);
191 assert_eq!(err.to_string(), "request timed out after 30 seconds");
192 }
193
194 #[test]
195 fn test_error_display_document_limit() {
196 let err = Error::DocumentLimitExceeded {
197 current: 150,
198 max: 100,
199 };
200 assert_eq!(err.to_string(), "document limit exceeded: 150/100");
201 }
202
203 #[test]
204 fn test_error_display_file_size_limit() {
205 let err = Error::FileSizeLimitExceeded {
206 size: 20_000_000,
207 max: 10_000_000,
208 };
209 assert!(err.to_string().contains("file size limit exceeded"));
210 }
211
212 #[test]
213 fn test_error_from_io() {
214 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
215 let err: Error = io_err.into();
216 assert!(matches!(err, Error::Io(_)));
217 }
218
219 #[test]
220 #[allow(clippy::unwrap_used)]
221 fn test_error_from_json() {
222 let json_str = "{invalid json}";
223 let json_err = serde_json::from_str::<serde_json::Value>(json_str).unwrap_err();
224 let err: Error = json_err.into();
225 assert!(matches!(err, Error::Json(_)));
226 }
227
228 #[test]
229 #[allow(clippy::unwrap_used)]
230 fn test_error_from_toml() {
231 let toml_str = "[invalid toml";
232 let toml_err = toml::from_str::<toml::Value>(toml_str).unwrap_err();
233 let err: Error = toml_err.into();
234 assert!(matches!(err, Error::Toml(_)));
235 }
236
237 #[test]
238 fn test_result_type_alias() {
239 fn _returns_error() -> Result<i32> {
240 Err(Error::Config("test error".to_string()))
241 }
242
243 let result: Result<i32> = Ok(42);
244 assert!(result.is_ok());
245 if let Ok(value) = result {
246 assert_eq!(value, 42);
247 }
248 }
249
250 #[test]
251 fn test_error_source_chain() {
252 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
253 let err = Error::ServerSpawnFailed {
254 command: "rust-analyzer".to_string(),
255 source: io_err,
256 };
257
258 let source = std::error::Error::source(&err);
259 assert!(source.is_some());
260 }
261}