sync_ls/
lsp.rs

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