Skip to main content

codineer_lsp/
error.rs

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}