Skip to main content

lsp_max/jsonrpc/
base_protocol.rs

1//! Implementation of the LSP Base Protocol 0.9.
2//!
3//! This module provides conversion utilities between `lsp_types_max` Base Protocol types
4//! and `lsp-max` JSON-RPC types.
5
6use std::borrow::Cow;
7
8use lsp_types_max::{
9    NotificationMessage, NumberOrString, RequestMessage, ResponseError, ResponseMessage,
10};
11use serde_json::Value;
12
13use super::{Error, ErrorCode, Id, Request, Response};
14
15/// Conversion from `lsp_types_max` types to `lsp-max` types.
16pub trait IntoTower {
17    /// The target `lsp-max` type.
18    type Target;
19    /// Converts this type into the target `lsp-max` type.
20    fn into_tower(self) -> Self::Target;
21}
22
23/// Conversion from `lsp-max` types to `lsp_types_max` types.
24pub trait IntoLsp {
25    /// The target `lsp_types_max` type.
26    type Target;
27    /// Converts this type into the target `lsp_types_max` type.
28    fn into_lsp(self) -> Self::Target;
29}
30
31impl IntoTower for RequestMessage {
32    type Target = Request;
33
34    fn into_tower(self) -> Self::Target {
35        Request::build(self.method)
36            .id(Id::from(self.id))
37            .params(self.params.unwrap_or(Value::Null))
38            .finish()
39    }
40}
41
42impl IntoTower for NotificationMessage {
43    type Target = Request;
44
45    fn into_tower(self) -> Self::Target {
46        Request::build(self.method)
47            .params(self.params.unwrap_or(Value::Null))
48            .finish()
49    }
50}
51
52impl IntoTower for ResponseError {
53    type Target = Error;
54
55    fn into_tower(self) -> Self::Target {
56        Error {
57            code: ErrorCode::from(self.code as i64),
58            message: Cow::Owned(self.message),
59            data: self.data,
60        }
61    }
62}
63
64impl IntoTower for ResponseMessage {
65    type Target = Response;
66
67    fn into_tower(self) -> Self::Target {
68        match self {
69            ResponseMessage::Success { id, result, .. } => {
70                Response::from_ok(id.map(Id::from).unwrap_or(Id::Null), result)
71            }
72            ResponseMessage::Error { id, error, .. } => {
73                Response::from_error(id.map(Id::from).unwrap_or(Id::Null), error.into_tower())
74            }
75        }
76    }
77}
78
79impl IntoLsp for Error {
80    type Target = ResponseError;
81
82    fn into_lsp(self) -> Self::Target {
83        ResponseError {
84            code: self.code.code() as i32,
85            message: self.message.into_owned(),
86            data: self.data,
87        }
88    }
89}
90
91impl IntoLsp for Response {
92    type Target = ResponseMessage;
93
94    fn into_lsp(self) -> Self::Target {
95        let (id, result) = self.into_parts();
96        match result {
97            Ok(result) => ResponseMessage::Success {
98                jsonrpc: "2.0".to_string(),
99                id: tower_id_to_lsp(id),
100                result,
101            },
102            Err(error) => ResponseMessage::Error {
103                jsonrpc: "2.0".to_string(),
104                id: tower_id_to_lsp(id),
105                error: error.into_lsp(),
106            },
107        }
108    }
109}
110
111/// Converts a `lsp-max` `Id` to an `lsp_types_max` `Option<NumberOrString>`.
112fn tower_id_to_lsp(id: Id) -> Option<NumberOrString> {
113    match id {
114        Id::Number(n) => Some(NumberOrString::Number(n as i32)),
115        Id::String(s) => Some(NumberOrString::String(s)),
116        Id::Null => None,
117    }
118}
119
120/// A wrapper enum that encapsulates all possible LSP Base Protocol 0.9 messages.
121///
122/// This provides a bridge between the unified `Request` type in `lsp-max`
123/// and the explicit `RequestMessage` and `NotificationMessage` types in the LSP specification.
124#[derive(Debug, Clone, PartialEq)]
125pub enum BaseProtocolMessage {
126    /// A request message to describe a request between the client and the server.
127    Request(RequestMessage),
128    /// A notification message. A NotificationMessage works like an event and does not send a response back.
129    Notification(NotificationMessage),
130    /// A response message is sent as a result of a request.
131    Response(ResponseMessage),
132}
133
134impl BaseProtocolMessage {
135    /// Converts a `lsp-max` `Request` into a `BaseProtocolMessage`.
136    ///
137    /// This will be either a `Request` or a `Notification` depending on whether an ID is present.
138    pub fn from_request(request: Request) -> Self {
139        let (method, id, params) = request.into_parts();
140        if let Some(id) = id {
141            BaseProtocolMessage::Request(RequestMessage {
142                jsonrpc: "2.0".to_string(),
143                id: match id {
144                    Id::Number(n) => NumberOrString::Number(n as i32),
145                    Id::String(s) => NumberOrString::String(s),
146                    Id::Null => NumberOrString::Number(0), // Fallback for discouraged null ID
147                },
148                method: method.into_owned(),
149                params,
150            })
151        } else {
152            BaseProtocolMessage::Notification(NotificationMessage {
153                jsonrpc: "2.0".to_string(),
154                method: method.into_owned(),
155                params,
156            })
157        }
158    }
159
160    /// Converts a `lsp-max` `Response` into a `BaseProtocolMessage`.
161    pub fn from_response(response: Response) -> Self {
162        BaseProtocolMessage::Response(response.into_lsp())
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use serde_json::json;
170
171    #[test]
172    fn test_request_message_bridge() {
173        let lsp_req = RequestMessage {
174            jsonrpc: "2.0".to_string(),
175            id: NumberOrString::Number(42),
176            method: "test/method".to_string(),
177            params: Some(json!({"foo": "bar"})),
178        };
179
180        let tower_req = lsp_req.clone().into_tower();
181        assert_eq!(tower_req.method(), "test/method");
182        assert_eq!(tower_req.id(), Some(&Id::Number(42)));
183        assert_eq!(tower_req.params(), Some(&json!({"foo": "bar"})));
184
185        let bridge_msg = BaseProtocolMessage::from_request(tower_req);
186        if let BaseProtocolMessage::Request(back) = bridge_msg {
187            assert_eq!(back.id, lsp_req.id);
188            assert_eq!(back.method, lsp_req.method);
189            assert_eq!(back.params, lsp_req.params);
190        } else {
191            panic!("Expected RequestMessage variant");
192        }
193    }
194
195    #[test]
196    fn test_notification_message_bridge() {
197        let lsp_notif = NotificationMessage {
198            jsonrpc: "2.0".to_string(),
199            method: "test/notify".to_string(),
200            params: Some(json!([1, 2, 3])),
201        };
202
203        let tower_req = lsp_notif.clone().into_tower();
204        assert_eq!(tower_req.method(), "test/notify");
205        assert_eq!(tower_req.id(), None);
206        assert_eq!(tower_req.params(), Some(&json!([1, 2, 3])));
207
208        let bridge_msg = BaseProtocolMessage::from_request(tower_req);
209        if let BaseProtocolMessage::Notification(back) = bridge_msg {
210            assert_eq!(back.method, lsp_notif.method);
211            assert_eq!(back.params, lsp_notif.params);
212        } else {
213            panic!("Expected NotificationMessage variant");
214        }
215    }
216
217    #[test]
218    fn test_response_message_bridge() {
219        let lsp_res = ResponseMessage::Success {
220            jsonrpc: "2.0".to_string(),
221            id: Some(NumberOrString::String("req-1".to_string())),
222            result: json!({"status": "ok"}),
223        };
224
225        let tower_res = lsp_res.clone().into_tower();
226        assert_eq!(tower_res.id(), &Id::String("req-1".to_string()));
227        assert_eq!(tower_res.result(), Some(&json!({"status": "ok"})));
228
229        let bridge_msg = BaseProtocolMessage::from_response(tower_res);
230        if let BaseProtocolMessage::Response(back) = bridge_msg {
231            assert_eq!(back, lsp_res);
232        } else {
233            panic!("Expected ResponseMessage variant");
234        }
235    }
236
237    #[test]
238    fn test_error_bridge() {
239        let lsp_err = ResponseError {
240            code: -32601,
241            message: "Method not found".to_string(),
242            data: None,
243        };
244
245        let tower_err: Error = lsp_err.clone().into_tower();
246        assert_eq!(tower_err.code, ErrorCode::MethodNotFound);
247        assert_eq!(tower_err.message, "Method not found");
248
249        let back_lsp_err = tower_err.into_lsp();
250        assert_eq!(back_lsp_err, lsp_err);
251    }
252}