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