1use crate::language_catalog::LanguageId;
2use lsp_types::Uri;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::io;
6use std::path::PathBuf;
7
8#[doc = include_str!("docs/protocol.md")]
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub enum DaemonRequest {
11 Initialize(InitializeRequest),
12 LspCall {
13 client_id: i64,
14 method: String,
15 params: Value,
16 },
17 GetDiagnostics {
18 client_id: i64,
19 uri: Option<Uri>,
21 },
22 QueueDiagnosticRefresh {
23 client_id: i64,
24 uri: Uri,
25 },
26 Disconnect,
27 Ping,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct InitializeRequest {
33 pub workspace_root: PathBuf,
34 pub language: LanguageId,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct LspNotification {
40 pub method: String,
41 pub params: Value,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub enum DaemonResponse {
47 Initialized,
48 Pong,
49 LspResult { client_id: i64, result: Result<Value, LspErrorResponse> },
50 Error(ProtocolError),
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct LspErrorResponse {
56 pub code: i32,
57 pub message: String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ProtocolError {
63 pub message: String,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub client_id: Option<i64>,
67}
68
69impl ProtocolError {
70 pub fn new(message: impl Into<String>) -> Self {
71 Self { message: message.into(), client_id: None }
72 }
73
74 pub fn with_client_id(message: impl Into<String>, client_id: i64) -> Self {
75 Self { message: message.into(), client_id: Some(client_id) }
76 }
77}
78
79pub fn extract_document_uri(method: &str, params: &Value) -> Option<Uri> {
84 if !method.starts_with("textDocument/") {
85 return None;
86 }
87 params.pointer("/textDocument/uri").and_then(|v| v.as_str()).and_then(|s| s.parse().ok())
88}
89
90pub const MAX_MESSAGE_SIZE: u32 = 16 * 1024 * 1024;
92
93pub const LSP_REQUEST_TIMED_OUT: i32 = -33001;
100
101pub const LSP_TRANSPORT_CLOSED: i32 = -33002;
104
105pub(crate) async fn read_frame<R, T>(reader: &mut R) -> io::Result<Option<T>>
107where
108 R: tokio::io::AsyncReadExt + Unpin,
109 T: for<'de> Deserialize<'de>,
110{
111 let mut len_buf = [0u8; 4];
112 match reader.read_exact(&mut len_buf).await {
113 Ok(_) => {}
114 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
115 Err(e) => return Err(e),
116 }
117
118 let len = u32::from_be_bytes(len_buf);
119
120 if len > MAX_MESSAGE_SIZE {
121 return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Message too large: {len} bytes")));
122 }
123
124 let mut buf = vec![0u8; len as usize];
125 reader.read_exact(&mut buf).await?;
126
127 serde_json::from_slice(&buf).map(Some).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
128}
129
130pub(crate) async fn write_frame<W, T>(writer: &mut W, message: &T) -> io::Result<()>
132where
133 W: tokio::io::AsyncWriteExt + Unpin,
134 T: Serialize,
135{
136 let json = serde_json::to_vec(message)?;
137
138 if json.len() > MAX_MESSAGE_SIZE as usize {
139 return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Message too large: {} bytes", json.len())));
140 }
141
142 let len = u32::try_from(json.len()).unwrap_or(u32::MAX);
143 writer.write_all(&len.to_be_bytes()).await?;
144 writer.write_all(&json).await?;
145 writer.flush().await
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_protocol_error_new() {
154 let err = ProtocolError::new("test error");
155 assert_eq!(err.message, "test error");
156 }
157
158 #[test]
159 fn test_daemon_request_lsp_call_roundtrip() {
160 let req = DaemonRequest::LspCall {
161 client_id: 42,
162 method: "textDocument/definition".to_string(),
163 params: serde_json::json!({
164 "textDocument": { "uri": "file:///test.rs" },
165 "position": { "line": 0, "character": 0 }
166 }),
167 };
168 let json = serde_json::to_string(&req).unwrap();
169 let decoded: DaemonRequest = serde_json::from_str(&json).unwrap();
170 match decoded {
171 DaemonRequest::LspCall { client_id, method, .. } => {
172 assert_eq!(client_id, 42);
173 assert_eq!(method, "textDocument/definition");
174 }
175 _ => panic!("Wrong variant"),
176 }
177 }
178
179 #[test]
180 fn test_extract_document_uri_definition() {
181 let params = serde_json::json!({
182 "textDocument": { "uri": "file:///src/main.rs" },
183 "position": { "line": 10, "character": 5 }
184 });
185 let uri = extract_document_uri("textDocument/definition", ¶ms);
186 assert!(uri.is_some());
187 assert_eq!(uri.unwrap().as_str(), "file:///src/main.rs");
188 }
189
190 #[test]
191 fn test_extract_document_uri_references() {
192 let params = serde_json::json!({
193 "textDocument": { "uri": "file:///src/lib.rs" },
194 "position": { "line": 5, "character": 3 },
195 "context": { "includeDeclaration": true }
196 });
197 let uri = extract_document_uri("textDocument/references", ¶ms);
198 assert!(uri.is_some());
199 assert_eq!(uri.unwrap().as_str(), "file:///src/lib.rs");
200 }
201
202 #[test]
203 fn test_extract_document_uri_document_symbol() {
204 let params = serde_json::json!({
205 "textDocument": { "uri": "file:///src/foo.rs" }
206 });
207 let uri = extract_document_uri("textDocument/documentSymbol", ¶ms);
208 assert!(uri.is_some());
209 assert_eq!(uri.unwrap().as_str(), "file:///src/foo.rs");
210 }
211
212 #[test]
213 fn test_extract_document_uri_workspace_symbol_returns_none() {
214 let params = serde_json::json!({ "query": "Foo" });
215 let uri = extract_document_uri("workspace/symbol", ¶ms);
216 assert!(uri.is_none());
217 }
218
219 #[test]
220 fn test_extract_document_uri_unknown_method_returns_none() {
221 let params = serde_json::json!({});
222 let uri = extract_document_uri("textDocument/unknown", ¶ms);
223 assert!(uri.is_none());
224 }
225}