1use std::io::{BufRead, Write};
15
16pub fn read_payload<R: BufRead>(reader: &mut R) -> Result<Option<String>, String> {
26 let mut content_length: Option<usize> = None;
27 let mut header = String::new();
28
29 loop {
30 header.clear();
31 let bytes = reader
32 .read_line(&mut header)
33 .map_err(|e| format!("failed to read header: {}", e))?;
34 if bytes == 0 {
35 return Ok(None);
36 }
37
38 let trimmed = header.trim_end_matches(['\n', '\r']);
39 if trimmed.is_empty() {
40 break;
41 }
42
43 let lower = trimmed.to_ascii_lowercase();
44 if lower.starts_with("content-length:") {
45 let value = trimmed["Content-Length:".len()..].trim();
46 let length = value
47 .parse::<usize>()
48 .map_err(|_| "invalid Content-Length header".to_string())?;
49 content_length = Some(length);
50 }
51 }
52
53 let length = content_length.ok_or_else(|| "missing Content-Length header".to_string())?;
54 let mut buffer = vec![0u8; length];
55 reader
56 .read_exact(&mut buffer)
57 .map_err(|e| format!("failed to read payload: {}", e))?;
58
59 if let Ok(buf) = reader.fill_buf() {
61 let to_consume = if buf.starts_with(b"\r\n") {
62 Some(2)
63 } else if buf.starts_with(b"\n") {
64 Some(1)
65 } else {
66 None
67 };
68 if let Some(count) = to_consume {
69 reader.consume(count);
70 }
71 }
72
73 String::from_utf8(buffer)
74 .map(Some)
75 .map_err(|_| "payload is not UTF-8".to_string())
76}
77
78pub fn write_message<W: Write>(writer: &mut W, body: &str) -> Result<(), String> {
80 write!(writer, "Content-Length: {}\r\n\r\n{}", body.len(), body)
81 .map_err(|e| format!("failed to write response: {}", e))?;
82 writer
83 .flush()
84 .map_err(|e| format!("failed to flush: {}", e))
85}
86
87pub trait JsonRpcSerializer {
96 type Value: Clone;
98
99 fn null() -> Self::Value;
101
102 fn string(value: &str) -> Self::Value;
104
105 fn number(value: i64) -> Self::Value;
107
108 fn object(entries: Vec<(&'static str, Self::Value)>) -> Self::Value;
110
111 fn to_compact_string(value: &Self::Value) -> String;
113}
114
115pub fn build_result_message<S: JsonRpcSerializer>(
119 id: Option<&S::Value>,
120 result: S::Value,
121) -> String {
122 let id_value = match id {
123 Some(identifier) => identifier.clone(),
124 None => S::null(),
125 };
126 let object = S::object(vec![
127 ("jsonrpc", S::string("2.0")),
128 ("id", id_value),
129 ("result", result),
130 ]);
131 S::to_compact_string(&object)
132}
133
134pub fn build_error_message<S: JsonRpcSerializer>(
138 id: Option<&S::Value>,
139 code: i64,
140 message: &str,
141) -> String {
142 let error = S::object(vec![
143 ("code", S::number(code)),
144 ("message", S::string(message)),
145 ]);
146 let id_value = match id {
147 Some(identifier) => identifier.clone(),
148 None => S::null(),
149 };
150 let object = S::object(vec![
151 ("jsonrpc", S::string("2.0")),
152 ("id", id_value),
153 ("error", error),
154 ]);
155 S::to_compact_string(&object)
156}
157
158pub fn build_notification<S: JsonRpcSerializer>(method: &str, params: S::Value) -> String {
160 let object = S::object(vec![
161 ("jsonrpc", S::string("2.0")),
162 ("method", S::string(method)),
163 ("params", params),
164 ]);
165 S::to_compact_string(&object)
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use std::collections::BTreeMap;
172
173 #[derive(Clone)]
179 enum TestJson {
180 Null,
181 Number(i64),
182 Str(String),
183 Object(BTreeMap<String, TestJson>),
184 }
185
186 struct TestSerializer;
187
188 impl JsonRpcSerializer for TestSerializer {
189 type Value = TestJson;
190
191 fn null() -> TestJson {
192 TestJson::Null
193 }
194 fn string(value: &str) -> TestJson {
195 TestJson::Str(value.to_string())
196 }
197 fn number(value: i64) -> TestJson {
198 TestJson::Number(value)
199 }
200 fn object(entries: Vec<(&'static str, TestJson)>) -> TestJson {
201 let mut map = BTreeMap::new();
202 for (key, value) in entries {
203 map.insert(key.to_string(), value);
204 }
205 TestJson::Object(map)
206 }
207 fn to_compact_string(value: &TestJson) -> String {
208 let mut out = String::new();
209 write(value, &mut out);
210 out
211 }
212 }
213
214 fn write(value: &TestJson, out: &mut String) {
215 match value {
216 TestJson::Null => out.push_str("null"),
217 TestJson::Number(n) => out.push_str(&n.to_string()),
218 TestJson::Str(s) => {
219 out.push('"');
220 out.push_str(s);
221 out.push('"');
222 }
223 TestJson::Object(map) => {
224 out.push('{');
225 for (idx, (key, value)) in map.iter().enumerate() {
226 if idx > 0 {
227 out.push(',');
228 }
229 out.push('"');
230 out.push_str(key);
231 out.push('"');
232 out.push(':');
233 write(value, out);
234 }
235 out.push('}');
236 }
237 }
238 }
239
240 #[test]
241 fn test_read_payload_basic() {
242 let body = r#"{"id":1}"#;
243 let msg = format!("Content-Length: {}\r\n\r\n{}", body.len(), body);
244 let mut reader = std::io::BufReader::new(msg.as_bytes());
245 let payload = read_payload(&mut reader).unwrap();
246 assert_eq!(payload, Some(body.to_string()));
247 }
248
249 #[test]
250 fn test_read_payload_eof() {
251 let input = b"";
252 let mut reader = std::io::BufReader::new(&input[..]);
253 let payload = read_payload(&mut reader).unwrap();
254 assert!(payload.is_none());
255 }
256
257 #[test]
258 fn test_read_payload_case_insensitive_header() {
259 let body = r#"{"ok":true}"#;
260 let msg = format!("content-LENGTH: {}\r\n\r\n{}", body.len(), body);
261 let mut reader = std::io::BufReader::new(msg.as_bytes());
262 let payload = read_payload(&mut reader).unwrap();
263 assert_eq!(payload, Some(body.to_string()));
264 }
265
266 #[test]
267 fn test_read_payload_consumes_trailing_newline_and_reads_next() {
268 let first = r#"{"n":1}"#;
272 let second = r#"{"n":2}"#;
273 let msg = format!(
274 "Content-Length: {}\r\n\r\n{}\nContent-Length: {}\r\n\r\n{}",
275 first.len(),
276 first,
277 second.len(),
278 second
279 );
280 let mut reader = std::io::BufReader::new(msg.as_bytes());
281 assert_eq!(read_payload(&mut reader).unwrap(), Some(first.to_string()));
282 assert_eq!(read_payload(&mut reader).unwrap(), Some(second.to_string()));
283 }
284
285 #[test]
286 fn test_write_message_framing_roundtrip() {
287 let body = r#"{"jsonrpc":"2.0","id":1,"result":true}"#;
288 let mut buffer = Vec::new();
289 write_message(&mut buffer, body).unwrap();
290 let written = String::from_utf8(buffer).unwrap();
291 assert_eq!(
292 written,
293 format!("Content-Length: {}\r\n\r\n{}", body.len(), body)
294 );
295
296 let mut reader = std::io::BufReader::new(written.as_bytes());
298 assert_eq!(read_payload(&mut reader).unwrap(), Some(body.to_string()));
299 }
300
301 #[test]
302 fn test_build_result_message_field_order() {
303 let id = TestJson::Number(1);
304 let msg =
305 build_result_message::<TestSerializer>(Some(&id), TestJson::Str("ok".to_string()));
306 assert_eq!(msg, r#"{"id":1,"jsonrpc":"2.0","result":"ok"}"#);
308 }
309
310 #[test]
311 fn test_build_result_message_null_id() {
312 let msg = build_result_message::<TestSerializer>(None, TestJson::Null);
313 assert_eq!(msg, r#"{"id":null,"jsonrpc":"2.0","result":null}"#);
314 }
315
316 #[test]
317 fn test_build_error_message_field_order() {
318 let id = TestJson::Number(2);
319 let msg = build_error_message::<TestSerializer>(Some(&id), -32601, "method not found");
320 assert_eq!(
321 msg,
322 r#"{"error":{"code":-32601,"message":"method not found"},"id":2,"jsonrpc":"2.0"}"#
323 );
324 }
325
326 #[test]
327 fn test_build_notification_field_order() {
328 let msg = build_notification::<TestSerializer>("test/event", TestJson::Null);
329 assert_eq!(
330 msg,
331 r#"{"jsonrpc":"2.0","method":"test/event","params":null}"#
332 );
333 }
334}