use crate::error::{MiniLLMError, Result};
use std::collections::HashMap;
use tokio::sync::mpsc;
#[derive(Debug)]
enum FieldEvent {
Text(String),
Complete(Option<serde_json::Value>),
}
#[derive(Debug)]
pub struct FieldHandle {
field: String,
rx: mpsc::UnboundedReceiver<FieldEvent>,
done: bool,
}
impl FieldHandle {
pub async fn delta(&mut self) -> Option<String> {
if self.done {
return None;
}
match self.rx.recv().await {
Some(FieldEvent::Text(text)) => Some(text),
Some(FieldEvent::Complete(_)) | None => {
self.done = true;
None
}
}
}
pub async fn wait(mut self) -> Result<serde_json::Value> {
let mut text = String::new();
let mut got_text = false;
loop {
match self.rx.recv().await {
Some(FieldEvent::Text(t)) => {
text.push_str(&t);
got_text = true;
}
Some(FieldEvent::Complete(Some(value))) => return Ok(value),
Some(FieldEvent::Complete(None)) => return Ok(serde_json::Value::String(text)),
None => {
if got_text {
return Ok(serde_json::Value::String(text));
}
return Err(MiniLLMError::MalformedResponse(format!(
"streamed tool arguments ended without the field '{}'",
self.field
)));
}
}
}
}
}
#[derive(Debug, Clone)]
enum Target {
Handle(mpsc::UnboundedSender<FieldEvent>),
Buffer,
}
#[derive(Debug)]
enum State {
Prelude(Prelude),
Str(Target),
Pending {
target: Target,
raw: String,
phase: Phase,
},
Closed,
Done,
Failed,
}
#[derive(Debug)]
enum Phase {
AfterQuote,
AfterComma,
InKey { key: String, escaped: bool },
AfterKey { key: String },
ObjClose,
}
#[derive(Debug)]
enum Prelude {
BeforeBrace,
BeforeKey,
InKey {
key: String,
escaped: bool,
},
AfterKey,
BeforeValue,
SkipValue {
depth: u32,
in_string: bool,
escaped: bool,
},
AfterValue,
}
#[derive(Debug, Default, PartialEq)]
enum Esc {
#[default]
None,
Slash,
Unicode(String),
AwaitLowSlash { high: u16 },
AwaitLowU { high: u16 },
AwaitLowHex { high: u16, buf: String },
}
#[derive(Debug)]
pub struct ArgumentStream {
lenient: bool,
state: State,
esc: Esc,
raw: String,
current_key: String,
buf: String,
chunk: String,
value_buf: String,
handles: HashMap<String, mpsc::UnboundedSender<FieldEvent>>,
fields: serde_json::Map<String, serde_json::Value>,
}
impl ArgumentStream {
pub fn strict() -> Self {
Self::new(false)
}
pub fn lenient() -> Self {
Self::new(true)
}
fn new(lenient: bool) -> Self {
Self {
lenient,
state: State::Prelude(Prelude::BeforeBrace),
esc: Esc::default(),
raw: String::new(),
current_key: String::new(),
buf: String::new(),
chunk: String::new(),
value_buf: String::new(),
handles: HashMap::new(),
fields: serde_json::Map::new(),
}
}
pub fn field(&mut self, name: impl Into<String>) -> FieldHandle {
let name = name.into();
let (tx, rx) = mpsc::unbounded_channel();
if let Some(value) = self.fields.get(&name) {
let _ = tx.send(FieldEvent::Complete(Some(value.clone())));
}
self.handles.insert(name.clone(), tx);
FieldHandle {
field: name,
rx,
done: false,
}
}
pub fn fields(&self) -> &serde_json::Map<String, serde_json::Value> {
&self.fields
}
pub fn feed(&mut self, fragment: &str) -> Result<()> {
match self.state {
State::Failed => return Err(self.error("stream already failed")),
State::Done => return Err(self.error("stream already finished")),
_ => {}
}
self.raw.push_str(fragment);
for c in fragment.chars() {
if let Err(e) = self.step(c) {
self.state = State::Failed;
return Err(e);
}
}
self.flush_chunk();
Ok(())
}
pub fn finish(&mut self) -> Result<()> {
let result = self.finish_inner();
if result.is_ok() {
if self.lenient {
if let Ok(serde_json::Value::Object(repaired)) =
crate::utils::extract_json_value(&self.raw)
{
for (key, value) in repaired {
if !self.fields.contains_key(&key) && !self.handles.contains_key(&key) {
self.fields.insert(key, value);
}
}
}
}
self.state = State::Done;
}
self.handles.clear();
result
}
fn finish_inner(&mut self) -> Result<()> {
loop {
match std::mem::replace(&mut self.state, State::Failed) {
State::Failed => return Err(self.error("stream already failed")),
State::Done => return Err(self.error("stream already finished")),
State::Prelude(Prelude::BeforeBrace) if self.raw.trim().is_empty() => {
return Ok(());
}
State::Prelude(_) => {
if self.lenient {
tracing::warn!(
"streamed tool arguments ended mid-structure; keeping completed fields"
);
return Ok(());
}
return Err(self.error("arguments ended mid-structure"));
}
State::Str(target) => {
if !self.lenient {
return Err(self.error("string value never closed"));
}
let mut flush = String::new();
match std::mem::take(&mut self.esc) {
Esc::None => {}
Esc::Slash => flush.push('\\'),
Esc::Unicode(buf) => {
flush.push_str("\\u");
flush.push_str(&buf);
}
Esc::AwaitLowSlash { .. } => flush.push('\u{FFFD}'),
Esc::AwaitLowU { .. } => {
flush.push('\u{FFFD}');
flush.push('\\');
}
Esc::AwaitLowHex { buf, .. } => {
flush.push('\u{FFFD}');
flush.push_str("\\u");
flush.push_str(&buf);
}
}
for fc in flush.chars() {
self.emit(&target, fc);
}
tracing::warn!(
field = %self.current_key,
"streamed tool arguments ended inside a string; accepting its content"
);
self.end_string(target);
return Ok(());
}
State::Pending { target, raw, phase } => match phase {
Phase::AfterQuote | Phase::ObjClose => {
self.end_string(target);
return Ok(());
}
Phase::AfterComma | Phase::InKey { .. } | Phase::AfterKey { .. } => {
self.state = State::Str(target.clone());
self.emit(&target, '"');
for rc in raw.chars() {
if let Err(e) = self.step(rc) {
self.state = State::Failed;
return Err(e);
}
}
}
},
State::Closed => return Ok(()),
}
}
}
fn error(&self, what: &str) -> MiniLLMError {
MiniLLMError::MalformedResponse(format!(
"streamed tool arguments: {} (raw: {})",
what, self.raw
))
}
fn emit(&mut self, target: &Target, c: char) {
match target {
Target::Handle(_) => self.chunk.push(c),
Target::Buffer => self.buf.push(c),
}
}
fn flush_chunk(&mut self) {
if self.chunk.is_empty() {
return;
}
let tx = match &self.state {
State::Str(Target::Handle(tx)) => Some(tx),
State::Pending {
target: Target::Handle(tx),
..
} => Some(tx),
_ => None,
};
if let Some(tx) = tx {
let _ = tx.send(FieldEvent::Text(std::mem::take(&mut self.chunk)));
}
}
fn end_string(&mut self, target: Target) {
let key = std::mem::take(&mut self.current_key);
match target {
Target::Handle(tx) => {
if !self.chunk.is_empty() {
let _ = tx.send(FieldEvent::Text(std::mem::take(&mut self.chunk)));
}
let _ = tx.send(FieldEvent::Complete(None));
}
Target::Buffer => {
let value = serde_json::Value::String(std::mem::take(&mut self.buf));
self.fields.insert(key, value);
}
}
}
fn complete_captured_value(&mut self) -> Result<()> {
match serde_json::from_str::<serde_json::Value>(self.value_buf.trim()) {
Ok(value) => {
let key = std::mem::take(&mut self.current_key);
self.value_buf.clear();
if let Some(tx) = self.handles.get(&key) {
let _ = tx.send(FieldEvent::Complete(Some(value)));
} else {
self.fields.insert(key, value);
}
Ok(())
}
Err(e) => Err(self.error(&format!(
"field '{}' is not valid JSON ({}): {}",
self.current_key, e, self.value_buf
))),
}
}
fn step(&mut self, c: char) -> Result<()> {
match &self.state {
State::Prelude(_) => self.step_prelude(c),
State::Str(target) => {
let target = target.clone();
self.step_str(c, &target)
}
State::Pending { .. } => self.step_pending(c),
State::Closed => {
if c.is_whitespace() {
Ok(())
} else {
Err(self.error(&format!("unexpected '{c}' after the arguments closed")))
}
}
State::Done | State::Failed => unreachable!("feed guards Done/Failed"),
}
}
fn step_pending(&mut self, c: char) -> Result<()> {
let State::Pending { target, raw, phase } = &mut self.state else {
unreachable!()
};
let target = target.clone();
raw.push(c);
let deviated = match phase {
Phase::AfterQuote => {
if c.is_whitespace() {
false
} else if c == ',' {
*phase = Phase::AfterComma;
false
} else if c == '}' {
*phase = Phase::ObjClose;
false
} else {
true
}
}
Phase::AfterComma => {
if c.is_whitespace() {
false
} else if c == '"' {
*phase = Phase::InKey {
key: String::new(),
escaped: false,
};
false
} else {
true
}
}
Phase::InKey { key, escaped } => {
if *escaped {
key.push(c);
*escaped = false;
} else if c == '\\' {
*escaped = true;
} else if c == '"' {
let key = std::mem::take(key);
*phase = Phase::AfterKey { key };
} else {
key.push(c);
}
false
}
Phase::AfterKey { key } => {
if c.is_whitespace() {
false
} else if c == ':' {
let key = std::mem::take(key);
self.end_string(target);
self.current_key = key;
self.state = State::Prelude(Prelude::BeforeValue);
return Ok(());
} else {
true
}
}
Phase::ObjClose => !c.is_whitespace(),
};
if !deviated {
return Ok(());
}
let raw = std::mem::take(raw);
self.state = State::Str(target.clone());
self.emit(&target, '"');
for rc in raw.chars() {
self.step(rc)?;
}
Ok(())
}
fn step_prelude(&mut self, c: char) -> Result<()> {
let mut value_completed = false;
let State::Prelude(p) = &mut self.state else {
unreachable!()
};
match p {
Prelude::BeforeBrace => {
if c.is_whitespace() {
} else if c == '{' {
*p = Prelude::BeforeKey;
} else {
return Err(
self.error(&format!("arguments do not start with '{{' (got '{c}')"))
);
}
}
Prelude::BeforeKey => {
if c.is_whitespace() {
} else if c == '"' {
*p = Prelude::InKey {
key: String::new(),
escaped: false,
};
} else if c == '}' {
self.state = State::Closed;
} else {
return Err(self.error(&format!("expected a field name, got '{c}'")));
}
}
Prelude::InKey { key, escaped } => {
if *escaped {
key.push(c);
*escaped = false;
} else if c == '\\' {
*escaped = true;
} else if c == '"' {
self.current_key = std::mem::take(key);
*p = Prelude::AfterKey;
} else {
key.push(c);
}
}
Prelude::AfterKey => {
if c.is_whitespace() {
} else if c == ':' {
*p = Prelude::BeforeValue;
} else {
return Err(self.error(&format!("expected ':' after a field name, got '{c}'")));
}
}
Prelude::BeforeValue => {
if c.is_whitespace() {
} else if c == '"' && (self.lenient || self.handles.contains_key(&self.current_key))
{
let target = match self.handles.get(&self.current_key) {
Some(tx) => Target::Handle(tx.clone()),
None => {
self.buf.clear();
Target::Buffer
}
};
self.state = State::Str(target);
} else {
self.value_buf.clear();
self.value_buf.push(c);
*p = if c == '"' {
Prelude::SkipValue {
depth: 0,
in_string: true,
escaped: false,
}
} else if c == '{' || c == '[' {
Prelude::SkipValue {
depth: 1,
in_string: false,
escaped: false,
}
} else {
Prelude::SkipValue {
depth: 0,
in_string: false,
escaped: false,
}
};
}
}
Prelude::SkipValue {
depth,
in_string,
escaped,
} => {
if *in_string {
self.value_buf.push(c);
if *escaped {
*escaped = false;
} else if c == '\\' {
*escaped = true;
} else if c == '"' {
*in_string = false;
if *depth == 0 {
value_completed = true;
*p = Prelude::AfterValue;
}
}
} else if *depth > 0 {
self.value_buf.push(c);
match c {
'"' => *in_string = true,
'{' | '[' => *depth += 1,
'}' | ']' => {
*depth -= 1;
if *depth == 0 {
value_completed = true;
*p = Prelude::AfterValue;
}
}
_ => {}
}
} else {
if c == ',' {
value_completed = true;
*p = Prelude::BeforeKey;
} else if c == '}' {
value_completed = true;
self.state = State::Closed;
} else if c.is_whitespace() {
value_completed = true;
*p = Prelude::AfterValue;
} else {
self.value_buf.push(c);
}
}
}
Prelude::AfterValue => {
if c.is_whitespace() {
} else if c == ',' {
*p = Prelude::BeforeKey;
} else if c == '}' {
self.state = State::Closed;
} else {
return Err(self.error(&format!("expected ',' or '}}', got '{c}'")));
}
}
}
if value_completed {
self.complete_captured_value()?;
}
Ok(())
}
fn step_str(&mut self, c: char, target: &Target) -> Result<()> {
match std::mem::take(&mut self.esc) {
Esc::None => match c {
'\\' => self.esc = Esc::Slash,
'"' => {
if self.lenient {
self.flush_chunk();
self.state = State::Pending {
target: target.clone(),
raw: String::new(),
phase: Phase::AfterQuote,
};
} else {
self.end_string(target.clone());
self.state = State::Prelude(Prelude::AfterValue);
}
}
c if (c as u32) < 0x20 => {
if self.lenient {
self.emit(target, c); } else {
return Err(self.error(&format!(
"unescaped control character U+{:04X} in string value",
c as u32
)));
}
}
c => self.emit(target, c),
},
Esc::Slash => match c {
'"' => self.emit(target, '"'),
'\\' => self.emit(target, '\\'),
'/' => self.emit(target, '/'),
'b' => self.emit(target, '\u{8}'),
'f' => self.emit(target, '\u{c}'),
'n' => self.emit(target, '\n'),
'r' => self.emit(target, '\r'),
't' => self.emit(target, '\t'),
'u' => self.esc = Esc::Unicode(String::new()),
other => {
if self.lenient {
self.emit(target, '\\');
return self.step_str(other, target);
}
return Err(self.error(&format!("invalid escape '\\{other}'")));
}
},
Esc::Unicode(mut buf) => {
if c.is_ascii_hexdigit() {
buf.push(c);
if buf.len() == 4 {
let code = u16::from_str_radix(&buf, 16).expect("4 hex digits");
self.take_unit(code, target)?;
} else {
self.esc = Esc::Unicode(buf);
}
} else {
if self.lenient {
self.emit(target, '\\');
self.emit(target, 'u');
for bc in buf.chars() {
self.emit(target, bc);
}
return self.step_str(c, target);
}
return Err(self.error(&format!("invalid \\u escape '\\u{buf}{c}'")));
}
}
Esc::AwaitLowSlash { high } => {
if c == '\\' {
self.esc = Esc::AwaitLowU { high };
} else {
if self.lenient {
self.emit(target, '\u{FFFD}'); return self.step_str(c, target);
}
return Err(self.error("unpaired \\u surrogate in string value"));
}
}
Esc::AwaitLowU { high } => {
if c == 'u' {
self.esc = Esc::AwaitLowHex {
high,
buf: String::new(),
};
} else {
if self.lenient {
self.emit(target, '\u{FFFD}');
self.esc = Esc::Slash;
return self.step_str(c, target);
}
return Err(self.error("unpaired \\u surrogate in string value"));
}
}
Esc::AwaitLowHex { high, mut buf } => {
if c.is_ascii_hexdigit() {
buf.push(c);
if buf.len() == 4 {
let low = u16::from_str_radix(&buf, 16).expect("4 hex digits");
if (0xDC00..=0xDFFF).contains(&low) {
let combined =
0x10000 + ((high as u32 - 0xD800) << 10) + (low as u32 - 0xDC00);
self.emit(
target,
char::from_u32(combined).expect("valid surrogate pair"),
);
} else {
if !self.lenient {
return Err(self.error("unpaired \\u surrogate in string value"));
}
self.emit(target, '\u{FFFD}');
self.take_unit(low, target)?;
}
} else {
self.esc = Esc::AwaitLowHex { high, buf };
}
} else {
if self.lenient {
self.emit(target, '\u{FFFD}');
self.emit(target, '\\');
self.emit(target, 'u');
for bc in buf.chars() {
self.emit(target, bc);
}
return self.step_str(c, target);
}
return Err(self.error("unpaired \\u surrogate in string value"));
}
}
}
Ok(())
}
fn take_unit(&mut self, code: u16, target: &Target) -> Result<()> {
if (0xD800..=0xDBFF).contains(&code) {
self.esc = Esc::AwaitLowSlash { high: code };
Ok(())
} else if (0xDC00..=0xDFFF).contains(&code) {
if self.lenient {
self.emit(target, '\u{FFFD}');
Ok(())
} else {
Err(self.error("unpaired \\u surrogate in string value"))
}
} else {
self.emit(
target,
char::from_u32(code as u32).expect("non-surrogate BMP code point"),
);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn drain(mut h: FieldHandle) -> Result<String> {
let mut out = String::new();
let mut completed = false;
while let Ok(ev) = h.rx.try_recv() {
match ev {
FieldEvent::Text(t) => out.push_str(&t),
FieldEvent::Complete(Some(v)) => {
return Ok(v.as_str().map(String::from).unwrap_or(v.to_string()))
}
FieldEvent::Complete(None) => completed = true,
}
}
if completed || !out.is_empty() {
Ok(out)
} else {
Err(MiniLLMError::MalformedResponse(format!(
"field '{}' never arrived",
h.field
)))
}
}
fn all_splits(field: &str, lenient: bool, raw: &str) -> Result<String> {
let make = || {
if lenient {
ArgumentStream::lenient()
} else {
ArgumentStream::strict()
}
};
let run = |fragments: Vec<&str>| -> Result<String> {
let mut args = make();
let h = args.field(field);
for f in fragments {
args.feed(f)?;
}
args.finish()?;
drain(h)
};
let reference = run(vec![raw]);
for i in 0..=raw.len() {
if !raw.is_char_boundary(i) {
continue;
}
let split = run(vec![&raw[..i], &raw[i..]]);
match (&reference, &split) {
(Ok(a), Ok(b)) => assert_eq!(a, b, "split at {i} diverged for {raw:?}"),
(Err(_), Err(_)) => {}
_ => panic!("split at {i} changed ok/err for {raw:?}"),
}
}
let fragments: Vec<String> = raw.chars().map(|c| c.to_string()).collect();
let by_char = run(fragments.iter().map(|s| s.as_str()).collect());
match (&reference, &by_char) {
(Ok(a), Ok(b)) => assert_eq!(a, b, "char-by-char diverged for {raw:?}"),
(Err(_), Err(_)) => {}
_ => panic!("char-by-char changed ok/err for {raw:?}"),
}
reference
}
fn strict(raw: &str) -> Result<String> {
all_splits("content", false, raw)
}
fn lenient(raw: &str) -> Result<String> {
all_splits("content", true, raw)
}
#[test]
fn strict_decodes_simple_payload() {
assert_eq!(
strict(r#"{"content": "hello world"}"#).unwrap(),
"hello world"
);
}
#[test]
fn strict_decodes_every_escape() {
assert_eq!(
strict(r#"{"content":"a\"b\\c\/d\be\ff\ng\rh\tiéj"}"#).unwrap(),
"a\"b\\c/d\u{8}e\u{c}f\ng\rh\ti\u{e9}j"
);
}
#[test]
fn strict_decodes_surrogate_pairs() {
assert_eq!(strict(r#"{"content":"ok 😀!"}"#).unwrap(), "ok 😀!");
}
#[test]
fn strict_parses_other_fields_around_the_handled_one() {
let raw = r#"{"lang": "py\"x", "n": 4.5e2, "flag": true,
"meta": {"a": ["b", {"c": 1}]}, "content": "payload", "after": 1}"#;
let mut args = ArgumentStream::strict();
let h = args.field("content");
args.feed(raw).unwrap();
args.finish().unwrap();
assert_eq!(drain(h).unwrap(), "payload");
assert_eq!(args.fields()["lang"], "py\"x");
assert_eq!(args.fields()["n"], 450.0);
assert_eq!(args.fields()["flag"], true);
assert_eq!(args.fields()["meta"]["a"][1]["c"], 1);
assert_eq!(args.fields()["after"], 1, "fields after a string work");
}
#[test]
fn strict_handles_empty_payload_and_whitespace_envelope() {
assert_eq!(strict(" { \"content\" : \"\" } ").unwrap(), "");
}
#[test]
fn empty_arguments_are_a_valid_no_parameter_call() {
for lenient_mode in [false, true] {
let mut args = if lenient_mode {
ArgumentStream::lenient()
} else {
ArgumentStream::strict()
};
args.feed("").unwrap();
args.finish().unwrap();
assert!(args.fields().is_empty());
}
}
#[test]
fn strict_rejects_malformed() {
for bad in [
r#"{"content": "unterminated"#, r#"{"content": "no brace""#, r#"{"content": "bad \q escape"}"#, r#"{"content": "lone \ud800 high"}"#, r#"{"content": "a"} trailing"#, "{\"content\": \"raw\nnewline\"}", r#"["content"]"#, ] {
assert!(strict(bad).is_err(), "must reject: {bad:?}");
}
}
#[test]
fn missing_field_resolves_as_handle_error_not_stream_error() {
let mut args = ArgumentStream::strict();
let h = args.field("content");
args.feed(r#"{"other": "x"}"#).unwrap();
args.finish().unwrap();
assert!(drain(h).is_err(), "wait on a missing field errors");
assert_eq!(args.fields()["other"], "x");
}
#[test]
fn error_carries_the_raw_text() {
let mut args = ArgumentStream::strict();
let err = args.feed(r#"{"content" x"#).unwrap_err().to_string();
assert!(err.contains(r#"{"content" x"#), "raw text in error: {err}");
assert!(args.feed("x").is_err());
assert!(args.finish().is_err());
}
#[test]
fn lenient_decodes_well_formed_identically() {
let raw = r#"{"content":"a\"b\\c\nd 😀"}"#;
assert_eq!(lenient(raw).unwrap(), strict(raw).unwrap());
}
#[test]
fn lenient_unescaped_quote_mid_content_is_literal() {
assert_eq!(
lenient(r#"{"content": "say "hi" ok"}"#).unwrap(),
r#"say "hi" ok"#
);
}
#[test]
fn lenient_raw_newline_is_itself() {
assert_eq!(
lenient("{\"content\": \"line1\nline2\"}").unwrap(),
"line1\nline2"
);
}
#[test]
fn lenient_backslash_before_non_escape_is_literal() {
assert_eq!(
lenient(r#"{"content": "C:\path \q \ x"}"#).unwrap(),
r#"C:\path \q \ x"#
);
}
#[test]
fn lenient_model_forgot_the_closing_quote_and_brace() {
assert_eq!(
lenient(r#"{"content": "the model stopped here"#).unwrap(),
"the model stopped here"
);
}
#[test]
fn lenient_model_closed_string_but_not_object() {
assert_eq!(lenient(r#"{"content": "done""#).unwrap(), "done");
}
#[test]
fn lenient_trailing_partial_escape_is_flushed_literally() {
assert_eq!(
lenient(r#"{"content": "ends with \"#).unwrap(),
"ends with \\"
);
assert_eq!(
lenient(r#"{"content": "ends with \u12"#).unwrap(),
"ends with \\u12"
);
}
#[test]
fn lenient_quote_then_more_content_after_whitespace_and_brace() {
assert_eq!(lenient(r#"{"content": "a "} b"}"#).unwrap(), r#"a "} b"#);
}
#[test]
fn lenient_unpaired_surrogate_is_replacement_not_dropped() {
assert_eq!(
lenient(r#"{"content": "x \ud800 y"}"#).unwrap(),
"x \u{FFFD} y"
);
}
#[test]
fn lenient_sloppy_field_recovers_via_key_boundary() {
let raw = r#"{"note": "he said "hi" there", "content": "ok"}"#;
assert_eq!(lenient(raw).unwrap(), "ok");
let mut args = ArgumentStream::lenient();
args.feed(raw).unwrap();
args.finish().unwrap();
assert_eq!(args.fields()["note"], r#"he said "hi" there"#);
}
#[test]
fn lenient_boundary_key_may_contain_spaces_and_tight_whitespace() {
let raw = r#"{"a": "v "x"","some thing":"w","content":"c"}"#;
assert_eq!(lenient(raw).unwrap(), "c");
let mut args = ArgumentStream::lenient();
args.feed(raw).unwrap();
args.finish().unwrap();
assert_eq!(args.fields()["a"], r#"v "x""#);
assert_eq!(args.fields()["some thing"], "w");
}
#[test]
fn lenient_documented_misfire_content_containing_a_boundary() {
let raw = r#"{"content": "embed ", "meta": " done"}"#;
assert_eq!(lenient(raw).unwrap(), "embed ");
let mut args = ArgumentStream::lenient();
args.feed(raw).unwrap();
args.finish().unwrap();
assert_eq!(args.fields()["meta"], " done");
}
#[test]
fn lenient_partial_boundary_at_end_is_literal_content() {
assert_eq!(
lenient(r#"{"content": "abc", "de"#).unwrap(),
r#"abc", "de"#
);
}
#[test]
fn lenient_stream_dying_mid_structure_keeps_completed_fields() {
let mut args = ArgumentStream::lenient();
let h = args.field("content");
args.feed(r#"{"content": "x", "meta": 1"#).unwrap();
args.finish().unwrap();
assert_eq!(drain(h).unwrap(), "x");
assert_eq!(args.fields()["meta"], 1);
}
#[test]
fn lenient_repair_pass_fills_fields_the_parse_missed() {
let mut args = ArgumentStream::lenient();
args.feed(r#"{"note": "he died her"#).unwrap();
args.finish().unwrap();
assert_eq!(args.fields()["note"], "he died her");
}
#[test]
fn two_streamed_fields_route_independently() {
let raw = r#"{"old_code": "a\nb", "new_code": "c\nd"}"#;
for lenient_mode in [false, true] {
let mut args = if lenient_mode {
ArgumentStream::lenient()
} else {
ArgumentStream::strict()
};
let old = args.field("old_code");
let new = args.field("new_code");
args.feed(raw).unwrap();
args.finish().unwrap();
assert_eq!(drain(old).unwrap(), "a\nb");
assert_eq!(drain(new).unwrap(), "c\nd");
assert!(
args.fields().is_empty(),
"handled fields are not duplicated"
);
}
}
#[test]
fn handle_on_non_string_field_resolves_via_wait() {
let mut args = ArgumentStream::strict();
let line = args.field("line");
args.feed(r#"{"line": 42, "content": "x"}"#).unwrap();
args.finish().unwrap();
let v = futures::executor::block_on(line.wait()).unwrap();
assert_eq!(v, 42);
}
#[test]
fn late_handle_for_a_completed_field_gets_the_value() {
let mut args = ArgumentStream::strict();
args.feed(r#"{"path": "a.rs", "content": "x"}"#).unwrap();
let path = args.field("path");
args.finish().unwrap();
let v = futures::executor::block_on(path.wait()).unwrap();
assert_eq!(v, "a.rs");
}
#[test]
fn wait_accumulates_streamed_chunks() {
let mut args = ArgumentStream::strict();
let content = args.field("content");
args.feed(r#"{"content": "hel"#).unwrap();
args.feed(r#"lo"}"#).unwrap();
args.finish().unwrap();
let v = futures::executor::block_on(content.wait()).unwrap();
assert_eq!(v, "hello");
}
#[test]
fn delta_streams_per_feed_chunks_live() {
let mut args = ArgumentStream::strict();
let mut content = args.field("content");
args.feed(r#"{"content": "ab"#).unwrap();
assert_eq!(
futures::executor::block_on(content.delta()).as_deref(),
Some("ab")
);
args.feed(r#"cd"}"#).unwrap();
args.finish().unwrap();
assert_eq!(
futures::executor::block_on(content.delta()).as_deref(),
Some("cd")
);
assert_eq!(futures::executor::block_on(content.delta()), None);
}
#[test]
fn finish_closes_handles_so_missing_fields_resolve() {
let mut args = ArgumentStream::strict();
let ghost = args.field("ghost");
args.feed(r#"{"content": "x"}"#).unwrap();
args.finish().unwrap();
assert!(futures::executor::block_on(ghost.wait()).is_err());
assert!(args.feed("x").is_err());
assert!(args.finish().is_err());
}
#[test]
fn duplicate_key_last_wins_in_fields() {
let mut args = ArgumentStream::strict();
args.feed(r#"{"path": "a", "path": "b"}"#).unwrap();
args.finish().unwrap();
assert_eq!(args.fields()["path"], "b");
}
#[test]
fn malformed_non_string_field_fails_loudly_in_both_modes() {
for lenient_mode in [false, true] {
let mut args = if lenient_mode {
ArgumentStream::lenient()
} else {
ArgumentStream::strict()
};
assert!(
args.feed(r#"{"line": 4x2, "content": "y"}"#).is_err(),
"lenient={lenient_mode} must reject a broken number"
);
}
}
#[test]
fn code_file_payload_streams_decoded() {
let code = "fn main() {\n println!(\"hi \\\\ there\");\n}\n";
let raw = format!(r#"{{"content": {}}}"#, serde_json::to_string(code).unwrap());
assert_eq!(strict(&raw).unwrap(), code);
assert_eq!(lenient(&raw).unwrap(), code);
}
}