1use std::path::PathBuf;
2
3#[non_exhaustive]
4#[derive(Debug, thiserror::Error)]
5pub enum LspError {
6 #[error(transparent)]
7 Io(#[from] std::io::Error),
8 #[error(transparent)]
9 Json(#[from] serde_json::Error),
10 #[error("invalid LSP header: {header}")]
11 InvalidHeader { header: String },
12 #[error("missing LSP Content-Length header")]
13 MissingContentLength,
14 #[error("invalid LSP Content-Length value: {value}")]
15 InvalidContentLength { value: String },
16 #[error("no LSP server configured for {}", path.display())]
17 UnsupportedDocument { path: PathBuf },
18 #[error("unknown LSP server: {name}")]
19 UnknownServer { name: String },
20 #[error("duplicate LSP extension mapping for {extension}: {existing_server} and {new_server}")]
21 DuplicateExtension {
22 extension: String,
23 existing_server: String,
24 new_server: String,
25 },
26 #[error("failed to convert path to file URL: {}", path.display())]
27 PathToUrl { path: PathBuf },
28 #[error("LSP protocol error: {message}")]
29 Protocol { message: String },
30 #[error("LSP payload too large: Content-Length {content_length} exceeds {limit} byte limit")]
31 PayloadTooLarge { content_length: usize, limit: usize },
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn display_formats_all_variants() {
40 let io_err = LspError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "gone"));
41 assert!(io_err.to_string().contains("gone"));
42
43 let json_str = "not json";
44 let json_err: serde_json::Error = serde_json::from_str::<bool>(json_str).unwrap_err();
45 let json_display = LspError::Json(json_err);
46 assert!(!json_display.to_string().is_empty());
47
48 assert_eq!(
49 LspError::InvalidHeader {
50 header: "bad".into()
51 }
52 .to_string(),
53 "invalid LSP header: bad"
54 );
55 assert_eq!(
56 LspError::MissingContentLength.to_string(),
57 "missing LSP Content-Length header"
58 );
59 assert_eq!(
60 LspError::InvalidContentLength {
61 value: "xyz".into()
62 }
63 .to_string(),
64 "invalid LSP Content-Length value: xyz"
65 );
66 assert!(LspError::UnsupportedDocument {
67 path: PathBuf::from("/foo.txt")
68 }
69 .to_string()
70 .contains("/foo.txt"));
71 assert_eq!(
72 LspError::UnknownServer {
73 name: "rust-analyzer".into()
74 }
75 .to_string(),
76 "unknown LSP server: rust-analyzer"
77 );
78 assert!(LspError::DuplicateExtension {
79 extension: ".rs".into(),
80 existing_server: "a".into(),
81 new_server: "b".into(),
82 }
83 .to_string()
84 .contains(".rs"));
85 assert!(LspError::PathToUrl {
86 path: PathBuf::from("/bad")
87 }
88 .to_string()
89 .contains("/bad"));
90 assert_eq!(
91 LspError::Protocol {
92 message: "timeout".into()
93 }
94 .to_string(),
95 "LSP protocol error: timeout"
96 );
97 assert!(LspError::PayloadTooLarge {
98 content_length: 100_000_000,
99 limit: 8_000_000,
100 }
101 .to_string()
102 .contains("100000000"));
103 }
104
105 #[test]
106 fn from_io_error_converts() {
107 let err: LspError = std::io::Error::new(std::io::ErrorKind::BrokenPipe, "pipe").into();
108 assert!(matches!(err, LspError::Io(_)));
109 }
110
111 #[test]
112 fn from_json_error_converts() {
113 let json_err: serde_json::Error = serde_json::from_str::<bool>("x").unwrap_err();
114 let err: LspError = json_err.into();
115 assert!(matches!(err, LspError::Json(_)));
116 }
117}