sync_ls/
lsp.rs

1//! A synchronous LSP server implementation.
2
3use std::io::{self, BufRead, Write};
4
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    ExtractError, LspOrDapResponse, LspResult, RequestId, ResponseError, invalid_data_fmt,
10    read_msg_text, write_msg_text,
11};
12
13/// A message in the Language Server Protocol.
14#[derive(Serialize, Deserialize, Debug, Clone)]
15#[serde(untagged)]
16pub enum Message {
17    /// Request messages
18    Request(Request),
19    /// Response messages
20    Response(Response),
21    /// Notification messages
22    Notification(Notification),
23}
24
25impl From<Request> for Message {
26    fn from(request: Request) -> Message {
27        Message::Request(request)
28    }
29}
30
31impl From<Response> for Message {
32    fn from(response: Response) -> Message {
33        Message::Response(response)
34    }
35}
36
37impl From<Notification> for Message {
38    fn from(notification: Notification) -> Message {
39        Message::Notification(notification)
40    }
41}
42
43impl Message {
44    /// Reads a LSP message from the reader.
45    pub fn read(r: &mut impl BufRead) -> io::Result<Option<Message>> {
46        let text = match read_msg_text(r)? {
47            None => return Ok(None),
48            Some(text) => text,
49        };
50
51        let msg = match serde_json::from_str(&text) {
52            Ok(msg) => msg,
53            Err(e) => {
54                return Err(invalid_data_fmt!("malformed LSP payload: {e:?}"));
55            }
56        };
57
58        Ok(Some(msg))
59    }
60
61    /// Writes the LSP message to the writer.
62    pub fn write(self, w: &mut impl Write) -> io::Result<()> {
63        #[derive(Serialize)]
64        struct JsonRpc {
65            jsonrpc: &'static str,
66            #[serde(flatten)]
67            msg: Message,
68        }
69        let text = serde_json::to_string(&JsonRpc {
70            jsonrpc: "2.0",
71            msg: self,
72        })?;
73        write_msg_text(w, &text)
74    }
75}
76
77/// A request in the Language Server Protocol.
78#[derive(Debug, Serialize, Deserialize, Clone)]
79pub struct Request {
80    /// The id to be used for identify this request, which will be used to
81    /// construct the corresponding response.
82    pub id: RequestId,
83    /// The method identifier of the request.
84    pub method: String,
85    /// The parameters of the request.
86    #[serde(default = "serde_json::Value::default")]
87    #[serde(skip_serializing_if = "serde_json::Value::is_null")]
88    pub params: serde_json::Value,
89}
90
91impl Request {
92    /// Creates a new LSP request.
93    pub fn new<P: serde::Serialize>(id: RequestId, method: String, params: P) -> Request {
94        Request {
95            id,
96            method,
97            params: serde_json::to_value(params).unwrap(),
98        }
99    }
100
101    /// Extracts the typed parameters of the a request accordingly.
102    pub fn extract<P: DeserializeOwned>(
103        self,
104        method: &str,
105    ) -> Result<(RequestId, P), ExtractError<Request>> {
106        if self.method != method {
107            return Err(ExtractError::MethodMismatch(self));
108        }
109        match serde_json::from_value(self.params) {
110            Ok(params) => Ok((self.id, params)),
111            Err(error) => Err(ExtractError::JsonError {
112                method: self.method,
113                error,
114            }),
115        }
116    }
117}
118
119/// A response in the Language Server Protocol.
120#[derive(Debug, Serialize, Deserialize, Clone)]
121pub struct Response {
122    /// JSON RPC allows this to be null if it was impossible
123    /// to decode the request's id. Ignore this special case
124    /// and just die horribly.
125    pub id: RequestId,
126    /// The result of the LSP request from the language server. It is not null
127    /// if executed successfully, and it is null if an error occurs.
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub result: Option<serde_json::Value>,
130    /// The error of the LSP request from the language server. It is null if
131    /// executed successfully, and it is not null if an error occurs.
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub error: Option<ResponseError>,
134}
135
136impl Response {
137    /// Creates a response
138    pub fn new(id: RequestId, result: LspResult<serde_json::Value>) -> Response {
139        match result {
140            Ok(result) => Response {
141                id,
142                result: Some(result),
143                error: None,
144            },
145            Err(err) => Response {
146                id,
147                result: None,
148                error: Some(err),
149            },
150        }
151    }
152}
153
154/// A notification in the Language Server Protocol.
155#[derive(Debug, Serialize, Deserialize, Clone)]
156pub struct Notification {
157    /// The method identifier of the notification.
158    pub method: String,
159    /// The parameters of the notification.
160    #[serde(default = "serde_json::Value::default")]
161    #[serde(skip_serializing_if = "serde_json::Value::is_null")]
162    pub params: serde_json::Value,
163}
164
165impl Notification {
166    /// Creates a new notification.
167    pub fn new(method: String, params: impl serde::Serialize) -> Notification {
168        Notification {
169            method,
170            params: serde_json::to_value(params).unwrap(),
171        }
172    }
173
174    /// Extracts the typed parameters of the a notification accordingly.
175    pub fn extract<P: DeserializeOwned>(
176        self,
177        method: &str,
178    ) -> Result<P, ExtractError<Notification>> {
179        if self.method != method {
180            return Err(ExtractError::MethodMismatch(self));
181        }
182        match serde_json::from_value(self.params) {
183            Ok(params) => Ok(params),
184            Err(error) => Err(ExtractError::JsonError {
185                method: self.method,
186                error,
187            }),
188        }
189    }
190
191    #[cfg(all(feature = "server", feature = "system"))]
192    pub(crate) fn is_exit(&self) -> bool {
193        self.method == "exit"
194    }
195}
196
197impl TryFrom<crate::Message> for Message {
198    type Error = anyhow::Error;
199
200    fn try_from(msg: crate::Message) -> anyhow::Result<Self> {
201        match msg {
202            crate::Message::Lsp(msg) => Ok(msg),
203            #[cfg(feature = "dap")]
204            crate::Message::Dap(msg) => anyhow::bail!("unexpected DAP message: {msg:?}"),
205        }
206    }
207}
208
209impl From<Request> for crate::Message {
210    fn from(request: Request) -> crate::Message {
211        crate::Message::Lsp(request.into())
212    }
213}
214
215impl From<Response> for crate::Message {
216    fn from(response: Response) -> crate::Message {
217        crate::Message::Lsp(response.into())
218    }
219}
220
221impl From<Notification> for crate::Message {
222    fn from(notification: Notification) -> crate::Message {
223        crate::Message::Lsp(notification.into())
224    }
225}
226
227impl From<Response> for LspOrDapResponse {
228    fn from(resp: Response) -> Self {
229        Self::Lsp(resp)
230    }
231}
232
233impl TryFrom<LspOrDapResponse> for Response {
234    type Error = anyhow::Error;
235
236    fn try_from(resp: LspOrDapResponse) -> anyhow::Result<Self> {
237        match resp {
238            LspOrDapResponse::Lsp(resp) => Ok(resp),
239            #[cfg(feature = "dap")]
240            LspOrDapResponse::Dap(_) => anyhow::bail!("unexpected DAP response"),
241        }
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::{Message, Notification, Request, RequestId};
248
249    #[test]
250    fn shutdown_with_explicit_null() {
251        let text = "{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"shutdown\", \"params\": null }";
252        let msg: Message = serde_json::from_str(text).unwrap();
253
254        assert!(
255            matches!(msg, Message::Request(req) if req.id == 3.into() && req.method == "shutdown")
256        );
257    }
258
259    #[test]
260    fn shutdown_with_no_params() {
261        let text = "{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"shutdown\"}";
262        let msg: Message = serde_json::from_str(text).unwrap();
263
264        assert!(
265            matches!(msg, Message::Request(req) if req.id == 3.into() && req.method == "shutdown")
266        );
267    }
268
269    #[test]
270    fn notification_with_explicit_null() {
271        let text = "{\"jsonrpc\": \"2.0\",\"method\": \"exit\", \"params\": null }";
272        let msg: Message = serde_json::from_str(text).unwrap();
273
274        assert!(matches!(msg, Message::Notification(not) if not.method == "exit"));
275    }
276
277    #[test]
278    fn notification_with_no_params() {
279        let text = "{\"jsonrpc\": \"2.0\",\"method\": \"exit\"}";
280        let msg: Message = serde_json::from_str(text).unwrap();
281
282        assert!(matches!(msg, Message::Notification(not) if not.method == "exit"));
283    }
284
285    #[test]
286    fn serialize_request_with_null_params() {
287        let msg = Message::Request(Request {
288            id: RequestId::from(3),
289            method: "shutdown".into(),
290            params: serde_json::Value::Null,
291        });
292        let serialized = serde_json::to_string(&msg).unwrap();
293
294        assert_eq!("{\"id\":3,\"method\":\"shutdown\"}", serialized);
295    }
296
297    #[test]
298    fn serialize_notification_with_null_params() {
299        let msg = Message::Notification(Notification {
300            method: "exit".into(),
301            params: serde_json::Value::Null,
302        });
303        let serialized = serde_json::to_string(&msg).unwrap();
304
305        assert_eq!("{\"method\":\"exit\"}", serialized);
306    }
307}