ricecoder_lsp/
transport.rs

1//! JSON-RPC message transport over stdio
2//!
3//! This module handles the low-level communication protocol for LSP,
4//! including message framing with Content-Length headers and JSON-RPC
5//! message parsing and serialization.
6
7use crate::types::{LspError, LspResult};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11use std::io::{self, BufRead, BufReader, Write};
12use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
13
14/// JSON-RPC request message
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct JsonRpcRequest {
17    /// JSON-RPC version (always "2.0")
18    pub jsonrpc: String,
19    /// Request ID
20    pub id: serde_json::Value,
21    /// Method name
22    pub method: String,
23    /// Request parameters
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub params: Option<Value>,
26}
27
28impl JsonRpcRequest {
29    /// Create a new JSON-RPC request
30    pub fn new(id: serde_json::Value, method: String, params: Option<Value>) -> Self {
31        Self {
32            jsonrpc: "2.0".to_string(),
33            id,
34            method,
35            params,
36        }
37    }
38}
39
40/// JSON-RPC response message
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct JsonRpcResponse {
43    /// JSON-RPC version (always "2.0")
44    pub jsonrpc: String,
45    /// Request ID
46    pub id: serde_json::Value,
47    /// Response result
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub result: Option<Value>,
50    /// Response error
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub error: Option<JsonRpcError>,
53}
54
55impl JsonRpcResponse {
56    /// Create a successful response
57    pub fn success(id: serde_json::Value, result: Value) -> Self {
58        Self {
59            jsonrpc: "2.0".to_string(),
60            id,
61            result: Some(result),
62            error: None,
63        }
64    }
65
66    /// Create an error response
67    pub fn error(id: serde_json::Value, error: JsonRpcError) -> Self {
68        Self {
69            jsonrpc: "2.0".to_string(),
70            id,
71            result: None,
72            error: Some(error),
73        }
74    }
75}
76
77/// JSON-RPC error object
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct JsonRpcError {
80    /// Error code
81    pub code: i32,
82    /// Error message
83    pub message: String,
84    /// Error data
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub data: Option<Value>,
87}
88
89impl JsonRpcError {
90    /// Create a new JSON-RPC error
91    pub fn new(code: i32, message: String) -> Self {
92        Self {
93            code,
94            message,
95            data: None,
96        }
97    }
98
99    /// Parse error (-32700)
100    pub fn parse_error(message: String) -> Self {
101        Self::new(-32700, message)
102    }
103
104    /// Invalid request (-32600)
105    pub fn invalid_request(message: String) -> Self {
106        Self::new(-32600, message)
107    }
108
109    /// Method not found (-32601)
110    pub fn method_not_found(method: String) -> Self {
111        Self::new(-32601, format!("Method not found: {}", method))
112    }
113
114    /// Invalid params (-32602)
115    pub fn invalid_params(message: String) -> Self {
116        Self::new(-32602, message)
117    }
118
119    /// Internal error (-32603)
120    pub fn internal_error(message: String) -> Self {
121        Self::new(-32603, message)
122    }
123
124    /// Server error (-32000 to -32099)
125    pub fn server_error(code: i32, message: String) -> Self {
126        Self::new(code, message)
127    }
128}
129
130/// JSON-RPC notification message
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct JsonRpcNotification {
133    /// JSON-RPC version (always "2.0")
134    pub jsonrpc: String,
135    /// Method name
136    pub method: String,
137    /// Notification parameters
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub params: Option<Value>,
140}
141
142impl JsonRpcNotification {
143    /// Create a new JSON-RPC notification
144    pub fn new(method: String, params: Option<Value>) -> Self {
145        Self {
146            jsonrpc: "2.0".to_string(),
147            method,
148            params,
149        }
150    }
151}
152
153/// LSP message (can be request, response, or notification)
154#[derive(Debug, Clone)]
155pub enum LspMessage {
156    /// Request message
157    Request(JsonRpcRequest),
158    /// Response message
159    Response(JsonRpcResponse),
160    /// Notification message
161    Notification(JsonRpcNotification),
162}
163
164impl LspMessage {
165    /// Parse a message from JSON
166    pub fn from_json(json: &str) -> LspResult<Self> {
167        let value: Value = serde_json::from_str(json)
168            .map_err(|e| LspError::ParseError(format!("Failed to parse JSON: {}", e)))?;
169
170        // Check if it's a response (has result or error)
171        if value.get("result").is_some() || value.get("error").is_some() {
172            let response: JsonRpcResponse = serde_json::from_value(value)
173                .map_err(|e| LspError::ParseError(format!("Failed to parse response: {}", e)))?;
174            Ok(LspMessage::Response(response))
175        }
176        // Check if it's a request (has id and method)
177        else if value.get("id").is_some() && value.get("method").is_some() {
178            let request: JsonRpcRequest = serde_json::from_value(value)
179                .map_err(|e| LspError::ParseError(format!("Failed to parse request: {}", e)))?;
180            Ok(LspMessage::Request(request))
181        }
182        // Otherwise it's a notification (has method but no id)
183        else if value.get("method").is_some() {
184            let notification: JsonRpcNotification = serde_json::from_value(value).map_err(|e| {
185                LspError::ParseError(format!("Failed to parse notification: {}", e))
186            })?;
187            Ok(LspMessage::Notification(notification))
188        } else {
189            Err(LspError::InvalidRequest(
190                "Message must be a request, response, or notification".to_string(),
191            ))
192        }
193    }
194
195    /// Serialize message to JSON
196    pub fn to_json(&self) -> LspResult<String> {
197        match self {
198            LspMessage::Request(req) => serde_json::to_string(req).map_err(|e| {
199                LspError::SerializationError(format!("Failed to serialize request: {}", e))
200            }),
201            LspMessage::Response(resp) => serde_json::to_string(resp).map_err(|e| {
202                LspError::SerializationError(format!("Failed to serialize response: {}", e))
203            }),
204            LspMessage::Notification(notif) => serde_json::to_string(notif).map_err(|e| {
205                LspError::SerializationError(format!("Failed to serialize notification: {}", e))
206            }),
207        }
208    }
209}
210
211/// Stdio transport for LSP messages
212pub struct StdioTransport {
213    reader: BufReader<io::Stdin>,
214    writer: io::Stdout,
215}
216
217impl StdioTransport {
218    /// Create a new stdio transport
219    pub fn new() -> Self {
220        Self {
221            reader: BufReader::new(io::stdin()),
222            writer: io::stdout(),
223        }
224    }
225
226    /// Read a message from stdin
227    pub fn read_message(&mut self) -> LspResult<LspMessage> {
228        let mut headers = HashMap::new();
229
230        // Read headers
231        loop {
232            let mut line = String::new();
233            self.reader
234                .read_line(&mut line)
235                .map_err(|e| LspError::IoError(format!("Failed to read header: {}", e)))?;
236
237            if line == "\r\n" || line == "\n" {
238                break;
239            }
240
241            let line = line.trim();
242            if line.is_empty() {
243                break;
244            }
245
246            if let Some((key, value)) = line.split_once(':') {
247                headers.insert(key.trim().to_string(), value.trim().to_string());
248            }
249        }
250
251        // Get content length
252        let content_length: usize = headers
253            .get("Content-Length")
254            .ok_or_else(|| LspError::InvalidRequest("Missing Content-Length header".to_string()))?
255            .parse()
256            .map_err(|e| LspError::InvalidRequest(format!("Invalid Content-Length: {}", e)))?;
257
258        // Read content
259        let mut content = vec![0u8; content_length];
260        use std::io::Read;
261        self.reader
262            .read_exact(&mut content)
263            .map_err(|e| LspError::IoError(format!("Failed to read content: {}", e)))?;
264
265        let json = String::from_utf8(content)
266            .map_err(|e| LspError::ParseError(format!("Invalid UTF-8: {}", e)))?;
267
268        LspMessage::from_json(&json)
269    }
270
271    /// Write a message to stdout
272    pub fn write_message(&mut self, message: &LspMessage) -> LspResult<()> {
273        let json = message.to_json()?;
274        let content_length = json.len();
275
276        write!(
277            self.writer,
278            "Content-Length: {}\r\n\r\n{}",
279            content_length, json
280        )
281        .map_err(|e| LspError::IoError(format!("Failed to write message: {}", e)))?;
282
283        self.writer
284            .flush()
285            .map_err(|e| LspError::IoError(format!("Failed to flush output: {}", e)))?;
286
287        Ok(())
288    }
289}
290
291impl Default for StdioTransport {
292    fn default() -> Self {
293        Self::new()
294    }
295}
296
297/// Async stdio transport for LSP messages
298pub struct AsyncStdioTransport {
299    reader: tokio::io::BufReader<tokio::io::Stdin>,
300    writer: tokio::io::Stdout,
301}
302
303impl AsyncStdioTransport {
304    /// Create a new async stdio transport
305    pub fn new() -> Self {
306        Self {
307            reader: tokio::io::BufReader::new(tokio::io::stdin()),
308            writer: tokio::io::stdout(),
309        }
310    }
311
312    /// Read a message from stdin asynchronously
313    pub async fn read_message(&mut self) -> LspResult<LspMessage> {
314        let mut headers = HashMap::new();
315
316        // Read headers
317        loop {
318            let mut line = String::new();
319            self.reader
320                .read_line(&mut line)
321                .await
322                .map_err(|e| LspError::IoError(format!("Failed to read header: {}", e)))?;
323
324            if line == "\r\n" || line == "\n" {
325                break;
326            }
327
328            let line = line.trim();
329            if line.is_empty() {
330                break;
331            }
332
333            if let Some((key, value)) = line.split_once(':') {
334                headers.insert(key.trim().to_string(), value.trim().to_string());
335            }
336        }
337
338        // Get content length
339        let content_length: usize = headers
340            .get("Content-Length")
341            .ok_or_else(|| LspError::InvalidRequest("Missing Content-Length header".to_string()))?
342            .parse()
343            .map_err(|e| LspError::InvalidRequest(format!("Invalid Content-Length: {}", e)))?;
344
345        // Read content
346        let mut content = vec![0u8; content_length];
347        use tokio::io::AsyncReadExt;
348        self.reader
349            .read_exact(&mut content)
350            .await
351            .map_err(|e| LspError::IoError(format!("Failed to read content: {}", e)))?;
352
353        let json = String::from_utf8(content)
354            .map_err(|e| LspError::ParseError(format!("Invalid UTF-8: {}", e)))?;
355
356        LspMessage::from_json(&json)
357    }
358
359    /// Write a message to stdout asynchronously
360    pub async fn write_message(&mut self, message: &LspMessage) -> LspResult<()> {
361        let json = message.to_json()?;
362        let content_length = json.len();
363
364        self.writer
365            .write_all(format!("Content-Length: {}\r\n\r\n{}", content_length, json).as_bytes())
366            .await
367            .map_err(|e| LspError::IoError(format!("Failed to write message: {}", e)))?;
368
369        self.writer
370            .flush()
371            .await
372            .map_err(|e| LspError::IoError(format!("Failed to flush output: {}", e)))?;
373
374        Ok(())
375    }
376}
377
378impl Default for AsyncStdioTransport {
379    fn default() -> Self {
380        Self::new()
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387    use serde_json::json;
388
389    #[test]
390    fn test_jsonrpc_request_creation() {
391        let req = JsonRpcRequest::new(
392            json!(1),
393            "initialize".to_string(),
394            Some(json!({"processId": 1234})),
395        );
396        assert_eq!(req.jsonrpc, "2.0");
397        assert_eq!(req.method, "initialize");
398    }
399
400    #[test]
401    fn test_jsonrpc_response_success() {
402        let resp = JsonRpcResponse::success(json!(1), json!({"capabilities": {}}));
403        assert_eq!(resp.jsonrpc, "2.0");
404        assert!(resp.result.is_some());
405        assert!(resp.error.is_none());
406    }
407
408    #[test]
409    fn test_jsonrpc_response_error() {
410        let error = JsonRpcError::parse_error("Invalid JSON".to_string());
411        let resp = JsonRpcResponse::error(json!(1), error);
412        assert!(resp.error.is_some());
413        assert!(resp.result.is_none());
414    }
415
416    #[test]
417    fn test_jsonrpc_notification_creation() {
418        let notif = JsonRpcNotification::new("initialized".to_string(), None);
419        assert_eq!(notif.jsonrpc, "2.0");
420        assert_eq!(notif.method, "initialized");
421    }
422
423    #[test]
424    fn test_lsp_message_from_request_json() {
425        let json_str =
426            r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":1234}}"#;
427        let msg = LspMessage::from_json(json_str).unwrap();
428        match msg {
429            LspMessage::Request(req) => {
430                assert_eq!(req.method, "initialize");
431            }
432            _ => panic!("Expected request"),
433        }
434    }
435
436    #[test]
437    fn test_lsp_message_from_notification_json() {
438        let json_str = r#"{"jsonrpc":"2.0","method":"initialized"}"#;
439        let msg = LspMessage::from_json(json_str).unwrap();
440        match msg {
441            LspMessage::Notification(notif) => {
442                assert_eq!(notif.method, "initialized");
443            }
444            _ => panic!("Expected notification"),
445        }
446    }
447
448    #[test]
449    fn test_lsp_message_to_json() {
450        let req = JsonRpcRequest::new(json!(1), "initialize".to_string(), None);
451        let msg = LspMessage::Request(req);
452        let json_str = msg.to_json().unwrap();
453        assert!(json_str.contains("initialize"));
454    }
455
456    #[test]
457    fn test_jsonrpc_error_codes() {
458        let parse_err = JsonRpcError::parse_error("test".to_string());
459        assert_eq!(parse_err.code, -32700);
460
461        let invalid_req = JsonRpcError::invalid_request("test".to_string());
462        assert_eq!(invalid_req.code, -32600);
463
464        let method_not_found = JsonRpcError::method_not_found("test".to_string());
465        assert_eq!(method_not_found.code, -32601);
466
467        let invalid_params = JsonRpcError::invalid_params("test".to_string());
468        assert_eq!(invalid_params.code, -32602);
469
470        let internal_err = JsonRpcError::internal_error("test".to_string());
471        assert_eq!(internal_err.code, -32603);
472    }
473}