pub struct IncrementalNdjsonParser {
buffer: Vec<u8>,
depth: usize,
in_string: bool,
escape_next: bool,
started: bool,
results: Vec<String>,
}
const MAX_JSON_DEPTH: usize = 1000;
impl IncrementalNdjsonParser {
pub const fn new() -> Self {
Self {
buffer: Vec::new(),
depth: 0,
in_string: false,
escape_next: false,
started: false,
results: Vec::new(),
}
}
pub fn feed(self, byte: u8) -> Self {
self.process_byte(byte)
}
pub fn feed_and_get_events(self, data: &[u8]) -> (Self, Vec<String>) {
let parser = data.iter().fold(self, |acc, &byte| acc.process_byte(byte));
let (results, empty_parser) = extract_results(parser);
(empty_parser, results)
}
pub fn drain_results(&mut self) -> Vec<String> {
std::mem::take(&mut self.results)
}
#[must_use]
pub fn get_results(&self) -> Vec<String> {
self.results.clone()
}
#[cfg(test)]
pub fn clear(&mut self) {
self.buffer = Vec::new();
self.depth = 0;
self.in_string = false;
self.escape_next = false;
self.started = false;
}
#[must_use]
pub const fn is_parsing(&self) -> bool {
self.started
}
#[must_use]
pub fn finish(self) -> Option<String> {
String::from_utf8(self.buffer)
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}
}
include!("incremental_parser/io.rs");
fn extract_results(parser: IncrementalNdjsonParser) -> (Vec<String>, IncrementalNdjsonParser) {
let IncrementalNdjsonParser {
buffer,
depth,
in_string,
escape_next,
started,
results,
} = parser;
let empty = IncrementalNdjsonParser {
buffer,
depth,
in_string,
escape_next,
started,
results: Vec::new(),
};
(results, empty)
}
impl Default for IncrementalNdjsonParser {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_incremental_parser_single_json() {
let parser = IncrementalNdjsonParser::new();
let (_, events) = parser.feed_and_get_events(b"{\"type\": \"delta\"}\n");
assert_eq!(events.len(), 1);
assert_eq!(events[0], "{\"type\": \"delta\"}");
}
#[test]
fn test_incremental_parser_split_json() {
let parser = IncrementalNdjsonParser::new();
let (parser, events1) = parser.feed_and_get_events(b"{\"type\": \"de");
assert_eq!(events1.len(), 0);
let (_, events2) = parser.feed_and_get_events(b"lta\"}\n");
assert_eq!(events2.len(), 1);
assert_eq!(events2[0], "{\"type\": \"delta\"}");
}
#[test]
fn test_incremental_parser_multiple_jsons() {
let parser = IncrementalNdjsonParser::new();
let input = b"{\"type\": \"delta\"}\n{\"type\": \"done\"}\n";
let (_, events) = parser.feed_and_get_events(input);
assert_eq!(events.len(), 2);
assert_eq!(events[0], "{\"type\": \"delta\"}");
assert_eq!(events[1], "{\"type\": \"done\"}");
}
#[test]
fn test_incremental_parser_nested_json() {
let parser = IncrementalNdjsonParser::new();
let input = b"{\"type\": \"delta\", \"data\": {\"nested\": true}}\n";
let (_, events) = parser.feed_and_get_events(input);
assert_eq!(events.len(), 1);
assert!(events[0].contains("\"nested\": true"));
}
#[test]
fn test_incremental_parser_json_with_strings_containing_braces() {
let parser = IncrementalNdjsonParser::new();
let input = b"{\"text\": \"hello {world}\"}\n";
let (_, events) = parser.feed_and_get_events(input);
assert_eq!(events.len(), 1);
assert_eq!(events[0], "{\"text\": \"hello {world}\"}");
}
#[test]
fn test_incremental_parser_json_with_escaped_quotes() {
let parser = IncrementalNdjsonParser::new();
let input = b"{\"text\": \"hello \\\"world\\\"\"}\n";
let (_, events) = parser.feed_and_get_events(input);
assert_eq!(events.len(), 1);
assert!(events[0].contains("\\\""));
}
#[test]
fn test_incremental_parser_empty_input() {
let parser = IncrementalNdjsonParser::new();
let (_, events) = parser.feed_and_get_events(b"");
assert_eq!(events.len(), 0);
}
#[test]
fn test_incremental_parser_whitespace_only() {
let parser = IncrementalNdjsonParser::new();
let (_, events) = parser.feed_and_get_events(b" \n \n");
assert_eq!(events.len(), 0);
}
#[test]
fn test_incremental_parser_ignores_preamble_before_json() {
let parser = IncrementalNdjsonParser::new();
let input = b"[i] Joined existing CLIProxy\n{\"type\":\"delta\"}\n";
let (_, events) = parser.feed_and_get_events(input);
assert_eq!(events, vec!["{\"type\":\"delta\"}".to_string()]);
}
#[test]
fn test_incremental_parser_clear() {
let parser = IncrementalNdjsonParser::new();
let (mut parser, _) = parser.feed_and_get_events(b"{\"type\":");
assert!(parser.is_parsing());
parser.clear();
assert!(!parser.is_parsing());
let (_, events) = parser.feed_and_get_events(b"{\"type\": \"delta\"}\n");
assert_eq!(events.len(), 1);
}
#[test]
fn test_incremental_parser_byte_by_byte() {
let input = b"{\"type\": \"delta\"}\n";
let mut parser = IncrementalNdjsonParser::new();
let mut all_events = Vec::new();
for &byte in input {
parser = parser.feed(byte);
all_events.extend(parser.drain_results());
}
assert_eq!(all_events.len(), 1);
assert_eq!(all_events[0], "{\"type\": \"delta\"}");
}
#[test]
fn test_incremental_parser_multiline_json() {
let parser = IncrementalNdjsonParser::new();
let input = b"{\n \"type\": \"delta\",\n \"value\": 123\n}\n";
let (_, events) = parser.feed_and_get_events(input);
assert_eq!(events.len(), 1);
assert!(events[0].contains("\"type\": \"delta\""));
assert!(events[0].contains("\"value\": 123"));
}
#[test]
fn test_incremental_parser_depth_limit() {
let input = "{".repeat(MAX_JSON_DEPTH + 1);
let (parser, events) = IncrementalNdjsonParser::new().feed_and_get_events(input.as_bytes());
assert_eq!(events.len(), 0);
assert!(!parser.is_parsing());
}
#[test]
fn test_incremental_parser_finish_returns_buffered_data() {
let parser = IncrementalNdjsonParser::new();
let (parser, events) = parser.feed_and_get_events(b"{\"type\": \"incomplete\"");
assert_eq!(events, vec![] as Vec<String>);
let remaining = parser.finish();
assert_eq!(remaining, Some("{\"type\": \"incomplete\"".to_string()));
}
#[test]
fn test_incremental_parser_finish_returns_none_for_empty_buffer() {
let parser = IncrementalNdjsonParser::new();
assert_eq!(parser.finish(), None);
}
#[test]
fn test_incremental_parser_finish_returns_none_for_complete_json() {
let parser = IncrementalNdjsonParser::new();
let (parser, events) = parser.feed_and_get_events(b"{\"type\": \"delta\"}\n");
assert_eq!(events.len(), 1);
assert_eq!(parser.finish(), None);
}
#[test]
fn test_incremental_parser_finish_with_complete_json_no_newline() {
let parser = IncrementalNdjsonParser::new();
let (parser, events) = parser.feed_and_get_events(b"{\"type\": \"delta\"}");
assert_eq!(events.len(), 1);
assert_eq!(events[0], "{\"type\": \"delta\"}");
assert_eq!(parser.finish(), None);
}
#[test]
fn test_incremental_parser_finish_with_incomplete_json_missing_brace() {
let parser = IncrementalNdjsonParser::new();
let (parser, events) = parser.feed_and_get_events(b"{\"type\": \"delta\"");
assert_eq!(events.len(), 0);
let remaining = parser.finish();
assert_eq!(remaining, Some("{\"type\": \"delta\"".to_string()));
}
}