1use std::io::{BufRead, Write};
14
15use serde::{Deserialize, Serialize};
16use serde_json::Value;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Framing {
23 Headers,
25 Lines,
27}
28
29#[derive(Debug, Clone, Deserialize)]
35pub struct Incoming {
36 #[serde(default)]
38 pub id: Option<Value>,
39 pub method: String,
41 #[serde(default)]
43 pub params: Value,
44}
45
46impl Incoming {
47 #[must_use]
49 pub const fn expects_reply(&self) -> bool {
50 self.id.is_some()
51 }
52}
53
54#[derive(Debug, Clone, Serialize)]
56pub struct Outgoing {
57 pub jsonrpc: &'static str,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub id: Option<Value>,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub result: Option<Value>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub error: Option<ErrorObject>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub method: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub params: Option<Value>,
74}
75
76#[derive(Debug, Clone, Serialize)]
78pub struct ErrorObject {
79 pub code: i32,
81 pub message: String,
83}
84
85pub mod codes {
87 pub const PARSE_ERROR: i32 = -32700;
89 pub const INVALID_REQUEST: i32 = -32600;
91 pub const METHOD_NOT_FOUND: i32 = -32601;
93 pub const INVALID_PARAMS: i32 = -32602;
95 pub const INTERNAL_ERROR: i32 = -32603;
97}
98
99impl Outgoing {
100 #[must_use]
102 pub fn result(id: Option<Value>, result: Value) -> Self {
103 Self {
104 jsonrpc: "2.0",
105 id,
106 result: Some(result),
107 error: None,
108 method: None,
109 params: None,
110 }
111 }
112
113 #[must_use]
115 pub fn error(id: Option<Value>, code: i32, message: impl Into<String>) -> Self {
116 Self {
117 jsonrpc: "2.0",
118 id,
119 result: None,
120 error: Some(ErrorObject {
121 code,
122 message: message.into(),
123 }),
124 method: None,
125 params: None,
126 }
127 }
128
129 #[must_use]
131 pub fn notification(method: impl Into<String>, params: Value) -> Self {
132 Self {
133 jsonrpc: "2.0",
134 id: None,
135 result: None,
136 error: None,
137 method: Some(method.into()),
138 params: Some(params),
139 }
140 }
141}
142
143pub fn read(input: &mut impl BufRead, framing: Framing) -> std::io::Result<Option<String>> {
151 match framing {
152 Framing::Lines => {
153 let mut line = String::new();
154 if input.read_line(&mut line)? == 0 {
155 return Ok(None);
156 }
157 let line = line.trim().to_owned();
158 if line.is_empty() {
160 return read(input, framing);
161 }
162 Ok(Some(line))
163 }
164
165 Framing::Headers => {
166 let mut length: Option<usize> = None;
167
168 loop {
169 let mut line = String::new();
170 if input.read_line(&mut line)? == 0 {
171 return Ok(None);
172 }
173 let line = line.trim_end_matches(['\r', '\n']);
174
175 if line.is_empty() {
177 break;
178 }
179
180 if let Some((name, value)) = line.split_once(':')
183 && name.trim().eq_ignore_ascii_case("content-length")
184 {
185 length = value.trim().parse().ok();
186 }
187 }
188
189 let Some(length) = length else {
192 return Ok(None);
193 };
194
195 let mut body = vec![0_u8; length];
196 std::io::Read::read_exact(input, &mut body)?;
197 Ok(Some(String::from_utf8_lossy(&body).into_owned()))
198 }
199 }
200}
201
202pub fn write(output: &mut impl Write, framing: Framing, message: &Outgoing) -> std::io::Result<()> {
208 let body = serde_json::to_string(message).unwrap_or_else(|_| {
209 String::from(r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"unserializable"}}"#)
212 });
213
214 match framing {
215 Framing::Lines => writeln!(output, "{body}")?,
216 Framing::Headers => write!(output, "Content-Length: {}\r\n\r\n{body}", body.len())?,
217 }
218 output.flush()
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 fn read_all(input: &str, framing: Framing) -> Vec<String> {
226 let mut cursor = std::io::BufReader::new(input.as_bytes());
227 let mut out = Vec::new();
228 while let Ok(Some(message)) = read(&mut cursor, framing) {
229 out.push(message);
230 }
231 out
232 }
233
234 #[test]
235 fn reads_a_header_framed_message() {
236 let body = r#"{"jsonrpc":"2.0","id":1,"method":"initialize"}"#;
237 let wire = format!("Content-Length: {}\r\n\r\n{body}", body.len());
238 assert_eq!(read_all(&wire, Framing::Headers), [body]);
239 }
240
241 #[test]
242 fn reads_several_header_framed_messages() {
243 let a = r#"{"id":1}"#;
244 let b = r#"{"id":2}"#;
245 let wire = format!(
246 "Content-Length: {}\r\n\r\n{a}Content-Length: {}\r\n\r\n{b}",
247 a.len(),
248 b.len()
249 );
250 assert_eq!(read_all(&wire, Framing::Headers), [a, b]);
251 }
252
253 #[test]
254 fn the_header_name_is_case_insensitive() {
255 let body = r#"{"id":1}"#;
258 let wire = format!("content-length: {}\r\n\r\n{body}", body.len());
259 assert_eq!(read_all(&wire, Framing::Headers), [body]);
260 }
261
262 #[test]
263 fn other_headers_are_ignored() {
264 let body = r#"{"id":1}"#;
265 let wire = format!(
266 "Content-Type: application/vscode-jsonrpc\r\nContent-Length: {}\r\n\r\n{body}",
267 body.len()
268 );
269 assert_eq!(read_all(&wire, Framing::Headers), [body]);
270 }
271
272 #[test]
273 fn a_body_with_no_length_ends_the_stream() {
274 assert!(read_all("Content-Type: x\r\n\r\n{}", Framing::Headers).is_empty());
276 }
277
278 #[test]
279 fn reads_line_framed_messages() {
280 let wire = "{\"id\":1}\n{\"id\":2}\n";
281 assert_eq!(
282 read_all(wire, Framing::Lines),
283 [r#"{"id":1}"#, r#"{"id":2}"#]
284 );
285 }
286
287 #[test]
288 fn blank_lines_between_messages_are_skipped() {
289 let wire = "{\"id\":1}\n\n\n{\"id\":2}\n";
290 assert_eq!(
291 read_all(wire, Framing::Lines),
292 [r#"{"id":1}"#, r#"{"id":2}"#]
293 );
294 }
295
296 #[test]
297 fn empty_input_reads_nothing() {
298 assert!(read_all("", Framing::Headers).is_empty());
299 assert!(read_all("", Framing::Lines).is_empty());
300 }
301
302 #[test]
303 fn a_notification_expects_no_reply() {
304 let notification: Incoming =
305 serde_json::from_str(r#"{"method":"initialized","params":{}}"#).expect("parses");
306 assert!(!notification.expects_reply());
307
308 let request: Incoming =
309 serde_json::from_str(r#"{"id":1,"method":"initialize"}"#).expect("parses");
310 assert!(request.expects_reply());
311 }
312
313 #[test]
314 fn params_default_to_null_when_absent() {
315 let message: Incoming =
317 serde_json::from_str(r#"{"id":1,"method":"shutdown"}"#).expect("parses");
318 assert!(message.params.is_null());
319 }
320
321 #[test]
322 fn a_written_message_round_trips_through_the_reader() {
323 for framing in [Framing::Headers, Framing::Lines] {
324 let mut buffer = Vec::new();
325 write(
326 &mut buffer,
327 framing,
328 &Outgoing::result(Some(Value::from(7)), serde_json::json!({"ok": true})),
329 )
330 .expect("writes");
331
332 let text = String::from_utf8(buffer).expect("utf-8");
333 let read_back = read_all(&text, framing);
334 assert_eq!(read_back.len(), 1, "{framing:?}");
335 let parsed: Value = serde_json::from_str(&read_back[0]).expect("parses");
336 assert_eq!(parsed["id"], 7, "{framing:?}");
337 assert_eq!(parsed["result"]["ok"], true, "{framing:?}");
338 assert_eq!(parsed["jsonrpc"], "2.0", "{framing:?}");
339 }
340 }
341
342 #[test]
343 fn a_header_framed_write_states_the_byte_length_not_the_character_count() {
344 let mut buffer = Vec::new();
347 write(
348 &mut buffer,
349 Framing::Headers,
350 &Outgoing::result(None, serde_json::json!({"m": "café — ✓"})),
351 )
352 .expect("writes");
353
354 let text = String::from_utf8(buffer).expect("utf-8");
355 let (header, body) = text.split_once("\r\n\r\n").expect("framed");
356 let declared: usize = header
357 .trim_start_matches("Content-Length:")
358 .trim()
359 .parse()
360 .expect("a number");
361 assert_eq!(declared, body.len());
362 assert_ne!(
363 declared,
364 body.chars().count(),
365 "the test needs a multi-byte body"
366 );
367 }
368
369 #[test]
370 fn an_error_reply_carries_a_code_and_no_result() {
371 let message = Outgoing::error(Some(Value::from(1)), codes::METHOD_NOT_FOUND, "nope");
372 let rendered = serde_json::to_value(&message).expect("serializes");
373 assert_eq!(rendered["error"]["code"], codes::METHOD_NOT_FOUND);
374 assert!(rendered.get("result").is_none());
375 }
376
377 #[test]
378 fn a_notification_carries_a_method_and_no_id() {
379 let message = Outgoing::notification("textDocument/publishDiagnostics", Value::Null);
380 let rendered = serde_json::to_value(&message).expect("serializes");
381 assert_eq!(rendered["method"], "textDocument/publishDiagnostics");
382 assert!(rendered.get("id").is_none());
383 }
384}