use crate::parser::{JsonishParser, ParserConfig};
use serde_json::Value;
use simple_agent_type::coercion::CoercionResult;
use simple_agent_type::error::HealingError;
use std::collections::VecDeque;
#[derive(Debug, Clone, PartialEq)]
enum ParseState {
Outside,
}
pub struct StreamingParser {
buffer: String,
parsed_index: usize,
parser: JsonishParser,
extracted_values: VecDeque<Value>,
state: ParseState,
}
impl StreamingParser {
pub fn new() -> Self {
Self {
buffer: String::new(),
parsed_index: 0,
parser: JsonishParser::new(),
extracted_values: VecDeque::new(),
state: ParseState::Outside,
}
}
pub fn with_config(config: ParserConfig) -> Self {
Self {
buffer: String::new(),
parsed_index: 0,
parser: JsonishParser::with_config(config),
extracted_values: VecDeque::new(),
state: ParseState::Outside,
}
}
pub fn feed(&mut self, chunk: &str) -> Vec<Value> {
self.buffer.push_str(chunk);
self.extract_completed_values()
}
pub fn try_parse(&self) -> Option<CoercionResult<Value>> {
if self.buffer.trim().is_empty() {
return None;
}
self.parser.parse(&self.buffer).ok()
}
pub fn finalize(
self,
) -> std::result::Result<CoercionResult<Value>, simple_agent_type::SimpleAgentsError> {
if self.buffer.trim().is_empty() {
return Err(simple_agent_type::SimpleAgentsError::Healing(
HealingError::ParseFailed {
error_message: "Empty buffer".to_string(),
input: String::new(),
},
));
}
self.parser.parse(&self.buffer)
}
pub fn buffer_len(&self) -> usize {
self.buffer.len()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn clear(&mut self) {
self.buffer.clear();
self.parsed_index = 0;
self.extracted_values.clear();
self.state = ParseState::Outside;
}
fn extract_completed_values(&mut self) -> Vec<Value> {
let mut result = Vec::new();
while let Some(value) = self.extracted_values.pop_front() {
result.push(value);
}
result
}
}
impl Default for StreamingParser {
fn default() -> Self {
Self::new()
}
}
pub struct PartialExtractor {
parser: StreamingParser,
}
impl PartialExtractor {
pub fn new() -> Self {
Self {
parser: StreamingParser::new(),
}
}
pub fn feed(&mut self, chunk: &str) -> Option<Value> {
self.parser.feed(chunk);
self.parser.try_parse().map(|result| result.value)
}
pub fn finalize(self) -> std::result::Result<Value, simple_agent_type::SimpleAgentsError> {
self.parser.finalize().map(|result| result.value)
}
}
impl Default for PartialExtractor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_streaming_parser_new() {
let parser = StreamingParser::new();
assert_eq!(parser.buffer_len(), 0);
assert!(parser.is_empty());
}
#[test]
fn test_feed_single_chunk_complete() {
let mut parser = StreamingParser::new();
parser.feed(r#"{"name": "Alice", "age": 30}"#);
let result = parser.finalize().unwrap();
assert_eq!(result.value["name"], "Alice");
assert_eq!(result.value["age"], 30);
}
#[test]
fn test_feed_multiple_chunks() {
let mut parser = StreamingParser::new();
parser.feed(r#"{"name": "#);
parser.feed(r#""Alice", "#);
parser.feed(r#""age": 30}"#);
let result = parser.finalize().unwrap();
assert_eq!(result.value["name"], "Alice");
assert_eq!(result.value["age"], 30);
}
#[test]
fn test_feed_with_nested_objects() {
let mut parser = StreamingParser::new();
parser.feed(r#"{"user": {"name": "#);
parser.feed(r#""Alice", "age": 30}, "#);
parser.feed(r#""active": true}"#);
let result = parser.finalize().unwrap();
assert_eq!(result.value["user"]["name"], "Alice");
assert_eq!(result.value["user"]["age"], 30);
assert_eq!(result.value["active"], true);
}
#[test]
fn test_feed_array() {
let mut parser = StreamingParser::new();
parser.feed(r#"["#);
parser.feed(r#"{"id": 1}, "#);
parser.feed(r#"{"id": 2}, "#);
parser.feed(r#"{"id": 3}]"#);
let result = parser.finalize().unwrap();
assert!(result.value.is_array());
assert_eq!(result.value[0]["id"], 1);
assert_eq!(result.value[1]["id"], 2);
assert_eq!(result.value[2]["id"], 3);
}
#[test]
fn test_try_parse_incomplete() {
let mut parser = StreamingParser::new();
parser.feed(r#"{"name": "Alice", "age":"#);
let result = parser.try_parse();
if let Some(parsed) = result {
assert_eq!(parsed.value["name"], "Alice");
}
}
#[test]
fn test_try_parse_complete() {
let mut parser = StreamingParser::new();
parser.feed(r#"{"name": "Alice", "age": 30}"#);
let result = parser.try_parse().unwrap();
assert_eq!(result.value["name"], "Alice");
assert_eq!(result.value["age"], 30);
}
#[test]
fn test_buffer_len() {
let mut parser = StreamingParser::new();
assert_eq!(parser.buffer_len(), 0);
parser.feed("hello");
assert_eq!(parser.buffer_len(), 5);
parser.feed(" world");
assert_eq!(parser.buffer_len(), 11);
}
#[test]
fn test_clear() {
let mut parser = StreamingParser::new();
parser.feed(r#"{"name": "Alice"}"#);
assert!(!parser.is_empty());
parser.clear();
assert!(parser.is_empty());
assert_eq!(parser.buffer_len(), 0);
}
#[test]
fn test_finalize_empty_buffer() {
let parser = StreamingParser::new();
let result = parser.finalize();
assert!(result.is_err());
}
#[test]
fn test_streaming_with_markdown() {
let mut parser = StreamingParser::new();
parser.feed("```json\n");
parser.feed(r#"{"name": "Alice"}"#);
parser.feed("\n```");
let result = parser.finalize().unwrap();
assert_eq!(result.value["name"], "Alice");
assert!(result.flags.iter().any(|f| matches!(
f,
simple_agent_type::coercion::CoercionFlag::StrippedMarkdown
)));
}
#[test]
fn test_streaming_with_trailing_comma() {
let mut parser = StreamingParser::new();
parser.feed(r#"{"name": "#);
parser.feed(r#""Alice","#);
parser.feed(r#"}"#);
let result = parser.finalize().unwrap();
assert_eq!(result.value["name"], "Alice");
assert!(result.flags.iter().any(|f| matches!(
f,
simple_agent_type::coercion::CoercionFlag::FixedTrailingComma
)));
}
#[test]
fn test_partial_extractor() {
let mut extractor = PartialExtractor::new();
extractor.feed(r#"{"name": "Alice", "#);
extractor.feed(r#""age": 30"#);
extractor.feed("}");
let result = extractor.finalize().unwrap();
assert_eq!(result["name"], "Alice");
assert_eq!(result["age"], 30);
}
#[test]
fn test_streaming_preserves_confidence() {
let mut parser = StreamingParser::new();
parser.feed(r#"{"name": "Alice"}"#);
let result = parser.finalize().unwrap();
assert_eq!(result.confidence, 1.0);
assert!(result.flags.is_empty());
}
#[test]
fn test_streaming_with_malformed_json() {
let mut parser = StreamingParser::new();
parser.feed(r#"{name: "#);
parser.feed(r#""Alice"}"#);
let result = parser.finalize().unwrap();
assert_eq!(result.value["name"], "Alice");
assert!(result.confidence < 1.0);
assert!(!result.flags.is_empty());
}
}