hindsight_copilot/
parser.rs

1// Copyright (c) 2026 - present Nicholas D. Crosbie
2// SPDX-License-Identifier: MIT
3
4//! JSON stream parser for Copilot logs
5
6use serde_json::StreamDeserializer;
7use std::io::Read;
8
9use crate::error::CopilotError;
10use crate::lsp::LspMessage;
11
12/// Parser for JSON stream formatted Copilot logs
13pub struct LogParser<R: Read> {
14    deserializer: StreamDeserializer<'static, serde_json::de::IoRead<R>, LspMessage>,
15}
16
17impl<R: Read> LogParser<R> {
18    /// Create a new log parser from a reader
19    pub fn new(reader: R) -> Self {
20        let deserializer = serde_json::Deserializer::from_reader(reader).into_iter();
21        Self { deserializer }
22    }
23}
24
25impl<R: Read> Iterator for LogParser<R> {
26    type Item = Result<LspMessage, CopilotError>;
27
28    fn next(&mut self) -> Option<Self::Item> {
29        self.deserializer
30            .next()
31            .map(|result| result.map_err(CopilotError::from))
32    }
33}
34
35/// Parse all LSP messages from a JSON string
36///
37/// # Errors
38///
39/// Returns an error if the JSON is invalid or doesn't match the expected format.
40pub fn parse_json_stream(json: &str) -> Result<Vec<LspMessage>, CopilotError> {
41    let mut parser = LogParser::new(json.as_bytes());
42    let mut messages = Vec::new();
43
44    for result in &mut parser {
45        messages.push(result?);
46    }
47
48    Ok(messages)
49}
50
51/// Parse a single LSP message from a JSON string
52///
53/// # Errors
54///
55/// Returns an error if the JSON is invalid.
56pub fn parse_single_message(json: &str) -> Result<LspMessage, CopilotError> {
57    serde_json::from_str(json).map_err(CopilotError::from)
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use similar_asserts::assert_eq;
64    use std::io::Cursor;
65
66    #[test]
67    fn test_log_parser_single_message() {
68        let json = r#"{"jsonrpc":"2.0","method":"test","id":1}"#;
69        let reader = Cursor::new(json);
70        let mut parser = LogParser::new(reader);
71
72        let msg = parser
73            .next()
74            .expect("should have message")
75            .expect("should parse");
76        assert_eq!(msg.jsonrpc, "2.0");
77        assert_eq!(msg.method, Some("test".to_string()));
78
79        assert!(parser.next().is_none());
80    }
81
82    #[test]
83    fn test_log_parser_multiple_messages() {
84        let json = r#"{"jsonrpc":"2.0","method":"first","id":1}
85{"jsonrpc":"2.0","method":"second","id":2}"#;
86        let reader = Cursor::new(json);
87        let parser = LogParser::new(reader);
88
89        let messages: Vec<_> = parser.collect();
90        assert_eq!(messages.len(), 2);
91        assert!(messages[0].is_ok());
92        assert!(messages[1].is_ok());
93    }
94
95    #[test]
96    fn test_log_parser_empty_input() {
97        let json = "";
98        let reader = Cursor::new(json);
99        let mut parser = LogParser::new(reader);
100
101        assert!(parser.next().is_none());
102    }
103
104    #[test]
105    fn test_log_parser_invalid_json() {
106        let json = r#"{"jsonrpc":"2.0" invalid"#;
107        let reader = Cursor::new(json);
108        let mut parser = LogParser::new(reader);
109
110        let result = parser.next().expect("should have result");
111        assert!(result.is_err());
112    }
113
114    #[test]
115    fn test_parse_json_stream() {
116        let json = r#"{"jsonrpc":"2.0","method":"a","id":1}{"jsonrpc":"2.0","method":"b","id":2}"#;
117        let messages = parse_json_stream(json).expect("should parse");
118
119        assert_eq!(messages.len(), 2);
120        assert_eq!(messages[0].method, Some("a".to_string()));
121        assert_eq!(messages[1].method, Some("b".to_string()));
122    }
123
124    #[test]
125    fn test_parse_json_stream_empty() {
126        let messages = parse_json_stream("").expect("should parse");
127        assert!(messages.is_empty());
128    }
129
130    #[test]
131    fn test_parse_single_message() {
132        let json = r#"{"jsonrpc":"2.0","method":"test","id":1,"params":{"uri":"file:///test.rs"}}"#;
133        let msg = parse_single_message(json).expect("should parse");
134
135        assert_eq!(msg.jsonrpc, "2.0");
136        assert_eq!(msg.method, Some("test".to_string()));
137        assert!(msg.params.is_some());
138    }
139
140    #[test]
141    fn test_parse_single_message_invalid() {
142        let result = parse_single_message("not json");
143        assert!(result.is_err());
144    }
145
146    #[test]
147    fn test_parse_message_with_all_fields() {
148        let json = r#"{
149            "jsonrpc": "2.0",
150            "id": 42,
151            "method": "textDocument/completion",
152            "params": {"position": {"line": 10, "character": 5}},
153            "result": {"completions": []},
154            "error": null
155        }"#;
156
157        let msg = parse_single_message(json).expect("should parse");
158        assert_eq!(msg.id, Some(serde_json::json!(42)));
159        assert_eq!(msg.method, Some("textDocument/completion".to_string()));
160        assert!(msg.params.is_some());
161    }
162}