Skip to main content

codineer_lsp/
error.rs

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}