1use lsp_types::Url;
27use thiserror::Error;
28
29#[derive(Debug, Error)]
31pub enum Error {
32 #[error("LSP protocol error: {0}")]
35 Protocol(#[from] lsp_server::ProtocolError),
36
37 #[error("Failed to send message: {0}")]
39 MessageSend(String),
40
41 #[error("Failed to receive message: {0}")]
43 MessageReceive(String),
44
45 #[error("TOML parse error in {uri}: {message}")]
48 TomlParse { uri: String, message: String },
49
50 #[error("Rust parse error in {uri}: {message}")]
52 RustParse { uri: String, message: String },
53
54 #[error("Invalid environment variable syntax in {uri} at line {line}: {message}")]
56 EnvVarSyntax {
57 uri: String,
58 line: u32,
59 message: String,
60 },
61
62 #[error("Configuration validation error in {uri}: {message}")]
65 ConfigValidation { uri: String, message: String },
66
67 #[error("Route validation error in {uri}: {message}")]
69 RouteValidation { uri: String, message: String },
70
71 #[error("Dependency injection validation error in {uri}: {message}")]
73 DiValidation { uri: String, message: String },
74
75 #[error("Schema load error: {0}")]
78 SchemaLoad(String),
79
80 #[error("Configuration error: {0}")]
82 Config(String),
83
84 #[error("File I/O error: {0}")]
86 Io(#[from] std::io::Error),
87
88 #[error("JSON error: {0}")]
90 Json(#[from] serde_json::Error),
91
92 #[error("HTTP request error: {0}")]
94 Http(#[from] reqwest::Error),
95
96 #[error("Index build error: {0}")]
98 IndexBuild(String),
99
100 #[error("{0}")]
102 Other(#[from] anyhow::Error),
103}
104
105impl Error {
106 pub fn category(&self) -> ErrorCategory {
108 match self {
109 Error::Protocol(_) | Error::MessageSend(_) | Error::MessageReceive(_) => {
110 ErrorCategory::Protocol
111 }
112 Error::TomlParse { .. } | Error::RustParse { .. } | Error::EnvVarSyntax { .. } => {
113 ErrorCategory::Parse
114 }
115 Error::ConfigValidation { .. }
116 | Error::RouteValidation { .. }
117 | Error::DiValidation { .. } => ErrorCategory::Validation,
118 Error::SchemaLoad(_)
119 | Error::Config(_)
120 | Error::Io(_)
121 | Error::Json(_)
122 | Error::Http(_)
123 | Error::IndexBuild(_)
124 | Error::Other(_) => ErrorCategory::System,
125 }
126 }
127
128 pub fn is_recoverable(&self) -> bool {
130 match self {
131 Error::Protocol(_) => true,
133 Error::MessageSend(_) | Error::MessageReceive(_) => true,
134
135 Error::TomlParse { .. } | Error::RustParse { .. } | Error::EnvVarSyntax { .. } => true,
137
138 Error::ConfigValidation { .. }
140 | Error::RouteValidation { .. }
141 | Error::DiValidation { .. } => true,
142
143 Error::SchemaLoad(_) => true, Error::Config(_) => false, Error::Http(_) => true, Error::IndexBuild(_) => true, Error::Io(_) => false, Error::Json(_) => false, Error::Other(_) => false, }
152 }
153
154 pub fn severity(&self) -> ErrorSeverity {
156 match self {
157 Error::Protocol(_) => ErrorSeverity::Error,
159 Error::MessageSend(_) | Error::MessageReceive(_) => ErrorSeverity::Error,
160
161 Error::TomlParse { .. } | Error::RustParse { .. } => ErrorSeverity::Warning,
163 Error::EnvVarSyntax { .. } => ErrorSeverity::Warning,
164
165 Error::ConfigValidation { .. }
167 | Error::RouteValidation { .. }
168 | Error::DiValidation { .. } => ErrorSeverity::Info,
169
170 Error::SchemaLoad(_) => ErrorSeverity::Warning, Error::Config(_) => ErrorSeverity::Error, Error::Http(_) => ErrorSeverity::Warning, Error::IndexBuild(_) => ErrorSeverity::Warning, Error::Io(_) => ErrorSeverity::Error, Error::Json(_) => ErrorSeverity::Error, Error::Other(_) => ErrorSeverity::Error, }
179 }
180
181 pub fn document_uri(&self) -> Option<&str> {
183 match self {
184 Error::TomlParse { uri, .. }
185 | Error::RustParse { uri, .. }
186 | Error::EnvVarSyntax { uri, .. }
187 | Error::ConfigValidation { uri, .. }
188 | Error::RouteValidation { uri, .. }
189 | Error::DiValidation { uri, .. } => Some(uri),
190 _ => None,
191 }
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum ErrorCategory {
198 Protocol,
200 Parse,
202 Validation,
204 System,
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
210pub enum ErrorSeverity {
211 Info,
213 Warning,
215 Error,
217}
218
219pub struct ErrorHandler {
223 verbose: bool,
225}
226
227impl ErrorHandler {
228 pub fn new(verbose: bool) -> Self {
230 Self { verbose }
231 }
232
233 pub fn handle(&self, error: &Error) -> ErrorHandlingResult {
240 self.log_error(error);
242
243 match error.category() {
245 ErrorCategory::Protocol => self.handle_protocol_error(error),
246 ErrorCategory::Parse => self.handle_parse_error(error),
247 ErrorCategory::Validation => self.handle_validation_error(error),
248 ErrorCategory::System => self.handle_system_error(error),
249 }
250 }
251
252 fn log_error(&self, error: &Error) {
254 match error.severity() {
255 ErrorSeverity::Error => {
256 if self.verbose {
257 tracing::error!("{:?}", error);
258 } else {
259 tracing::error!("{}", error);
260 }
261 }
262 ErrorSeverity::Warning => {
263 if self.verbose {
264 tracing::warn!("{:?}", error);
265 } else {
266 tracing::warn!("{}", error);
267 }
268 }
269 ErrorSeverity::Info => {
270 if self.verbose {
271 tracing::info!("{:?}", error);
272 } else {
273 tracing::info!("{}", error);
274 }
275 }
276 }
277 }
278
279 fn handle_protocol_error(&self, error: &Error) -> ErrorHandlingResult {
281 tracing::error!("Protocol error occurred: {}", error);
282
283 ErrorHandlingResult {
284 action: RecoveryAction::RetryConnection,
285 fallback: None,
286 notify_client: true,
287 }
288 }
289
290 fn handle_parse_error(&self, error: &Error) -> ErrorHandlingResult {
292 tracing::warn!("Parse error occurred: {}", error);
293
294 ErrorHandlingResult {
295 action: RecoveryAction::PartialParse,
296 fallback: None,
297 notify_client: true,
298 }
299 }
300
301 fn handle_validation_error(&self, error: &Error) -> ErrorHandlingResult {
303 tracing::info!("Validation error occurred: {}", error);
304
305 ErrorHandlingResult {
306 action: RecoveryAction::GenerateDiagnostic,
307 fallback: None,
308 notify_client: false, }
310 }
311
312 fn handle_system_error(&self, error: &Error) -> ErrorHandlingResult {
314 tracing::error!("System error occurred: {}", error);
315
316 let (action, fallback) = match error {
317 Error::SchemaLoad(_) => (RecoveryAction::UseFallback, Some("builtin-schema")),
318 Error::Http(_) => (RecoveryAction::UseCache, None),
319 Error::IndexBuild(_) => (RecoveryAction::SkipOperation, None),
320 _ => (RecoveryAction::Abort, None),
321 };
322
323 ErrorHandlingResult {
324 action,
325 fallback: fallback.map(String::from),
326 notify_client: true,
327 }
328 }
329}
330
331#[derive(Debug)]
333pub struct ErrorHandlingResult {
334 pub action: RecoveryAction,
336 pub fallback: Option<String>,
338 pub notify_client: bool,
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344pub enum RecoveryAction {
345 RetryConnection,
347 PartialParse,
349 GenerateDiagnostic,
351 UseFallback,
353 UseCache,
355 SkipOperation,
357 Abort,
359}
360
361pub type Result<T> = std::result::Result<T, Error>;
363
364pub fn toml_parse_error(uri: &Url, message: impl Into<String>) -> Error {
366 Error::TomlParse {
367 uri: uri.to_string(),
368 message: message.into(),
369 }
370}
371
372pub fn rust_parse_error(uri: &Url, message: impl Into<String>) -> Error {
374 Error::RustParse {
375 uri: uri.to_string(),
376 message: message.into(),
377 }
378}
379
380pub fn env_var_syntax_error(uri: &Url, line: u32, message: impl Into<String>) -> Error {
382 Error::EnvVarSyntax {
383 uri: uri.to_string(),
384 line,
385 message: message.into(),
386 }
387}
388
389pub fn config_validation_error(uri: &Url, message: impl Into<String>) -> Error {
391 Error::ConfigValidation {
392 uri: uri.to_string(),
393 message: message.into(),
394 }
395}
396
397pub fn route_validation_error(uri: &Url, message: impl Into<String>) -> Error {
399 Error::RouteValidation {
400 uri: uri.to_string(),
401 message: message.into(),
402 }
403}
404
405pub fn di_validation_error(uri: &Url, message: impl Into<String>) -> Error {
407 Error::DiValidation {
408 uri: uri.to_string(),
409 message: message.into(),
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[test]
418 fn test_error_category() {
419 let protocol_err = Error::MessageSend("test error".to_string());
421 assert_eq!(protocol_err.category(), ErrorCategory::Protocol);
422
423 let parse_err = Error::TomlParse {
424 uri: "file:///test.toml".to_string(),
425 message: "syntax error".to_string(),
426 };
427 assert_eq!(parse_err.category(), ErrorCategory::Parse);
428
429 let validation_err = Error::ConfigValidation {
430 uri: "file:///test.toml".to_string(),
431 message: "invalid config".to_string(),
432 };
433 assert_eq!(validation_err.category(), ErrorCategory::Validation);
434
435 let system_err = Error::SchemaLoad("failed to load".to_string());
436 assert_eq!(system_err.category(), ErrorCategory::System);
437 }
438
439 #[test]
440 fn test_error_recoverability() {
441 let protocol_err = Error::MessageSend("test error".to_string());
442 assert!(protocol_err.is_recoverable());
443
444 let parse_err = Error::TomlParse {
445 uri: "file:///test.toml".to_string(),
446 message: "syntax error".to_string(),
447 };
448 assert!(parse_err.is_recoverable());
449
450 let io_err = Error::Io(std::io::Error::new(
451 std::io::ErrorKind::NotFound,
452 "file not found",
453 ));
454 assert!(!io_err.is_recoverable());
455 }
456
457 #[test]
458 fn test_error_severity() {
459 let protocol_err = Error::MessageSend("test error".to_string());
460 assert_eq!(protocol_err.severity(), ErrorSeverity::Error);
461
462 let parse_err = Error::TomlParse {
463 uri: "file:///test.toml".to_string(),
464 message: "syntax error".to_string(),
465 };
466 assert_eq!(parse_err.severity(), ErrorSeverity::Warning);
467
468 let validation_err = Error::ConfigValidation {
469 uri: "file:///test.toml".to_string(),
470 message: "invalid config".to_string(),
471 };
472 assert_eq!(validation_err.severity(), ErrorSeverity::Info);
473 }
474
475 #[test]
476 fn test_error_document_uri() {
477 let parse_err = Error::TomlParse {
478 uri: "file:///test.toml".to_string(),
479 message: "syntax error".to_string(),
480 };
481 assert_eq!(parse_err.document_uri(), Some("file:///test.toml"));
482
483 let system_err = Error::SchemaLoad("failed".to_string());
484 assert_eq!(system_err.document_uri(), None);
485 }
486
487 #[test]
488 fn test_error_handler() {
489 let handler = ErrorHandler::new(false);
490
491 let protocol_err = Error::MessageSend("test error".to_string());
493 let result = handler.handle(&protocol_err);
494 assert_eq!(result.action, RecoveryAction::RetryConnection);
495 assert!(result.notify_client);
496
497 let parse_err = Error::TomlParse {
499 uri: "file:///test.toml".to_string(),
500 message: "syntax error".to_string(),
501 };
502 let result = handler.handle(&parse_err);
503 assert_eq!(result.action, RecoveryAction::PartialParse);
504 assert!(result.notify_client);
505
506 let validation_err = Error::ConfigValidation {
508 uri: "file:///test.toml".to_string(),
509 message: "invalid config".to_string(),
510 };
511 let result = handler.handle(&validation_err);
512 assert_eq!(result.action, RecoveryAction::GenerateDiagnostic);
513 assert!(!result.notify_client);
514
515 let schema_err = Error::SchemaLoad("failed".to_string());
517 let result = handler.handle(&schema_err);
518 assert_eq!(result.action, RecoveryAction::UseFallback);
519 assert_eq!(result.fallback, Some("builtin-schema".to_string()));
520 assert!(result.notify_client);
521 }
522
523 #[test]
524 fn test_error_helper_functions() {
525 let uri = Url::parse("file:///test.toml").unwrap();
526
527 let toml_err = toml_parse_error(&uri, "syntax error");
528 assert!(matches!(toml_err, Error::TomlParse { .. }));
529
530 let rust_err = rust_parse_error(&uri, "syntax error");
531 assert!(matches!(rust_err, Error::RustParse { .. }));
532
533 let env_err = env_var_syntax_error(&uri, 10, "invalid syntax");
534 assert!(matches!(env_err, Error::EnvVarSyntax { .. }));
535
536 let config_err = config_validation_error(&uri, "invalid config");
537 assert!(matches!(config_err, Error::ConfigValidation { .. }));
538
539 let route_err = route_validation_error(&uri, "invalid route");
540 assert!(matches!(route_err, Error::RouteValidation { .. }));
541
542 let di_err = di_validation_error(&uri, "invalid injection");
543 assert!(matches!(di_err, Error::DiValidation { .. }));
544 }
545}