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    invalid_data_fmt, read_msg_text, write_msg_text, ExtractError, LspOrDapResponse, RequestId,
10    ResponseError,
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 with the success payload.
138    pub fn new_ok<R: serde::Serialize>(id: RequestId, result: R) -> Response {
139        Response {
140            id,
141            result: Some(serde_json::to_value(result).unwrap()),
142            error: None,
143        }
144    }
145
146    /// Creates a response with the failure reason.
147    pub fn new_err(id: RequestId, code: i32, message: String) -> Response {
148        let error = ResponseError {
149            code,
150            message,
151            data: None,
152        };
153        Response {
154            id,
155            result: None,
156            error: Some(error),
157        }
158    }
159}
160
161/// A notification in the Language Server Protocol.
162#[derive(Debug, Serialize, Deserialize, Clone)]
163pub struct Notification {
164    /// The method identifier of the notification.
165    pub method: String,
166    /// The parameters of the notification.
167    #[serde(default = "serde_json::Value::default")]
168    #[serde(skip_serializing_if = "serde_json::Value::is_null")]
169    pub params: serde_json::Value,
170}
171
172impl Notification {
173    /// Creates a new notification.
174    pub fn new(method: String, params: impl serde::Serialize) -> Notification {
175        Notification {
176            method,
177            params: serde_json::to_value(params).unwrap(),
178        }
179    }
180
181    /// Extracts the typed parameters of the a notification accordingly.
182    pub fn extract<P: DeserializeOwned>(
183        self,
184        method: &str,
185    ) -> Result<P, ExtractError<Notification>> {
186        if self.method != method {
187            return Err(ExtractError::MethodMismatch(self));
188        }
189        match serde_json::from_value(self.params) {
190            Ok(params) => Ok(params),
191            Err(error) => Err(ExtractError::JsonError {
192                method: self.method,
193                error,
194            }),
195        }
196    }
197
198    #[cfg(feature = "server")]
199    pub(crate) fn is_exit(&self) -> bool {
200        self.method == "exit"
201    }
202}
203
204impl TryFrom<crate::Message> for Message {
205    type Error = anyhow::Error;
206
207    fn try_from(msg: crate::Message) -> anyhow::Result<Self> {
208        match msg {
209            crate::Message::Lsp(msg) => Ok(msg),
210            #[cfg(feature = "dap")]
211            crate::Message::Dap(msg) => anyhow::bail!("unexpected DAP message: {msg:?}"),
212        }
213    }
214}
215
216impl From<Request> for crate::Message {
217    fn from(request: Request) -> crate::Message {
218        crate::Message::Lsp(request.into())
219    }
220}
221
222impl From<Response> for crate::Message {
223    fn from(response: Response) -> crate::Message {
224        crate::Message::Lsp(response.into())
225    }
226}
227
228impl From<Notification> for crate::Message {
229    fn from(notification: Notification) -> crate::Message {
230        crate::Message::Lsp(notification.into())
231    }
232}
233
234impl From<Response> for LspOrDapResponse {
235    fn from(resp: Response) -> Self {
236        Self::Lsp(resp)
237    }
238}
239
240impl TryFrom<LspOrDapResponse> for Response {
241    type Error = anyhow::Error;
242
243    fn try_from(resp: LspOrDapResponse) -> anyhow::Result<Self> {
244        match resp {
245            LspOrDapResponse::Lsp(resp) => Ok(resp),
246            #[cfg(feature = "dap")]
247            LspOrDapResponse::Dap(_) => anyhow::bail!("unexpected DAP response"),
248        }
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::{Message, Notification, Request, RequestId};
255
256    #[test]
257    fn shutdown_with_explicit_null() {
258        let text = "{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"shutdown\", \"params\": null }";
259        let msg: Message = serde_json::from_str(text).unwrap();
260
261        assert!(
262            matches!(msg, Message::Request(req) if req.id == 3.into() && req.method == "shutdown")
263        );
264    }
265
266    #[test]
267    fn shutdown_with_no_params() {
268        let text = "{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"shutdown\"}";
269        let msg: Message = serde_json::from_str(text).unwrap();
270
271        assert!(
272            matches!(msg, Message::Request(req) if req.id == 3.into() && req.method == "shutdown")
273        );
274    }
275
276    #[test]
277    fn notification_with_explicit_null() {
278        let text = "{\"jsonrpc\": \"2.0\",\"method\": \"exit\", \"params\": null }";
279        let msg: Message = serde_json::from_str(text).unwrap();
280
281        assert!(matches!(msg, Message::Notification(not) if not.method == "exit"));
282    }
283
284    #[test]
285    fn notification_with_no_params() {
286        let text = "{\"jsonrpc\": \"2.0\",\"method\": \"exit\"}";
287        let msg: Message = serde_json::from_str(text).unwrap();
288
289        assert!(matches!(msg, Message::Notification(not) if not.method == "exit"));
290    }
291
292    #[test]
293    fn serialize_request_with_null_params() {
294        let msg = Message::Request(Request {
295            id: RequestId::from(3),
296            method: "shutdown".into(),
297            params: serde_json::Value::Null,
298        });
299        let serialized = serde_json::to_string(&msg).unwrap();
300
301        assert_eq!("{\"id\":3,\"method\":\"shutdown\"}", serialized);
302    }
303
304    #[test]
305    fn serialize_notification_with_null_params() {
306        let msg = Message::Notification(Notification {
307            method: "exit".into(),
308            params: serde_json::Value::Null,
309        });
310        let serialized = serde_json::to_string(&msg).unwrap();
311
312        assert_eq!("{\"method\":\"exit\"}", serialized);
313    }
314}