Skip to main content

lspkit_server/
jsonrpc.rs

1//! Content-Length-framed JSON-RPC 2.0 over async I/O.
2//!
3//! This is the primitive every LSP server uses to talk to its client. We
4//! own the framing and routing so the toolkit does not depend on an
5//! unmaintained third-party LSP server crate.
6
7use std::io;
8
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader};
12use tokio::sync::Mutex;
13
14/// JSON-RPC 2.0 message identifier. Either a request ID or absent for notifications.
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
16#[serde(untagged)]
17pub enum RequestId {
18    /// Numeric ID.
19    Number(i64),
20    /// String ID.
21    String(String),
22}
23
24/// An incoming JSON-RPC message.
25#[non_exhaustive]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Message {
28    /// Always `"2.0"`.
29    pub jsonrpc: String,
30    /// Optional ID — present for requests and responses, absent for notifications.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub id: Option<RequestId>,
33    /// Method name — present for requests and notifications, absent for responses.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub method: Option<String>,
36    /// Parameters — present for requests and notifications.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub params: Option<Value>,
39    /// Result — present for successful responses.
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub result: Option<Value>,
42    /// Error — present for failed responses.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub error: Option<JsonRpcError>,
45}
46
47/// A JSON-RPC 2.0 error object.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct JsonRpcError {
50    /// Numeric error code (see JSON-RPC spec for reserved ranges).
51    pub code: i32,
52    /// Short human-readable message.
53    pub message: String,
54    /// Optional structured details.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub data: Option<Value>,
57}
58
59impl JsonRpcError {
60    /// Construct a new error with `code` and `message` and no `data`.
61    #[must_use]
62    pub fn new(code: i32, message: impl Into<String>) -> Self {
63        Self {
64            code,
65            message: message.into(),
66            data: None,
67        }
68    }
69
70    /// Attach structured `data` to the error.
71    #[must_use]
72    pub fn with_data(mut self, data: Value) -> Self {
73        self.data = Some(data);
74        self
75    }
76}
77
78impl Message {
79    /// Construct a request with `id`, `method`, and `params`.
80    #[must_use]
81    pub fn request(id: RequestId, method: impl Into<String>, params: Value) -> Self {
82        Self {
83            jsonrpc: "2.0".to_owned(),
84            id: Some(id),
85            method: Some(method.into()),
86            params: Some(params),
87            result: None,
88            error: None,
89        }
90    }
91
92    /// Construct a notification with `method` and `params`.
93    #[must_use]
94    pub fn notification(method: impl Into<String>, params: Value) -> Self {
95        Self {
96            jsonrpc: "2.0".to_owned(),
97            id: None,
98            method: Some(method.into()),
99            params: Some(params),
100            result: None,
101            error: None,
102        }
103    }
104
105    /// Construct a successful response carrying `result`.
106    #[must_use]
107    pub fn response(id: RequestId, result: Value) -> Self {
108        Self {
109            jsonrpc: "2.0".to_owned(),
110            id: Some(id),
111            method: None,
112            params: None,
113            result: Some(result),
114            error: None,
115        }
116    }
117
118    /// Construct a failed response carrying `error`.
119    #[must_use]
120    pub fn response_error(id: RequestId, error: JsonRpcError) -> Self {
121        Self {
122            jsonrpc: "2.0".to_owned(),
123            id: Some(id),
124            method: None,
125            params: None,
126            result: None,
127            error: Some(error),
128        }
129    }
130}
131
132/// Errors from reading or writing JSON-RPC messages.
133#[non_exhaustive]
134#[derive(Debug, thiserror::Error)]
135pub enum FramingError {
136    /// Underlying I/O failure.
137    #[error(transparent)]
138    Io(#[from] io::Error),
139    /// Header could not be parsed.
140    #[error("malformed Content-Length header: {0}")]
141    BadHeader(String),
142    /// Message body was not valid JSON.
143    #[error("malformed JSON body: {0}")]
144    BadJson(#[from] serde_json::Error),
145    /// Peer closed the stream.
146    #[error("peer closed the stream")]
147    Closed,
148}
149
150/// Read one Content-Length-framed JSON-RPC message from `reader`.
151///
152/// # Errors
153/// Returns [`FramingError`] on I/O failure, header parse failure, or invalid JSON.
154pub async fn read_message<R>(reader: &mut BufReader<R>) -> Result<Message, FramingError>
155where
156    R: AsyncReadExt + Unpin,
157{
158    let mut content_length: Option<usize> = None;
159    let mut line = String::new();
160    loop {
161        line.clear();
162        let read = reader.read_line(&mut line).await?;
163        if read == 0 {
164            return Err(FramingError::Closed);
165        }
166        let trimmed = line.trim_end_matches(['\r', '\n']);
167        if trimmed.is_empty() {
168            break;
169        }
170        if let Some(value) = trimmed.strip_prefix("Content-Length:") {
171            content_length = Some(
172                value
173                    .trim()
174                    .parse()
175                    .map_err(|_| FramingError::BadHeader(trimmed.to_owned()))?,
176            );
177        }
178    }
179    let length = content_length.ok_or_else(|| FramingError::BadHeader("missing".to_owned()))?;
180    let mut body = vec![0u8; length];
181    reader.read_exact(&mut body).await?;
182    let message: Message = serde_json::from_slice(&body)?;
183    Ok(message)
184}
185
186/// Writer half of a JSON-RPC connection, serialized so multiple tasks can send.
187pub struct MessageWriter<W: AsyncWrite + Unpin + Send> {
188    inner: Mutex<W>,
189}
190
191impl<W: AsyncWrite + Unpin + Send> MessageWriter<W> {
192    /// Wrap an async writer.
193    pub fn new(writer: W) -> Self {
194        Self {
195            inner: Mutex::new(writer),
196        }
197    }
198
199    /// Write a single Content-Length-framed message.
200    ///
201    /// # Errors
202    /// Returns [`FramingError::Io`] on writer failure or
203    /// [`FramingError::BadJson`] if the message cannot be serialized.
204    pub async fn write_message(&self, message: &Message) -> Result<(), FramingError> {
205        let body = serde_json::to_vec(message)?;
206        let mut guard = self.inner.lock().await;
207        let header = format!("Content-Length: {}\r\n\r\n", body.len());
208        guard.write_all(header.as_bytes()).await?;
209        guard.write_all(&body).await?;
210        guard.flush().await?;
211        Ok(())
212    }
213}