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