1use serde_json::{Map, Number, Value};
5
6const VALID_ESCAPES: &[char] = &['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u'];
7
8pub fn repair_json(json: &str) -> String {
11 let chars: Vec<char> = json.chars().collect();
12 let mut repaired = String::with_capacity(json.len());
13 let mut in_string = false;
14 let mut index = 0;
15 while index < chars.len() {
16 let char = chars[index];
17 if !in_string {
18 repaired.push(char);
19 in_string = char == '"';
20 } else if char == '"' {
21 repaired.push(char);
22 in_string = false;
23 } else if char == '\\' {
24 match chars.get(index + 1) {
25 None => repaired.push_str("\\\\"),
26 Some('u')
27 if chars.len() >= index + 6
28 && chars[index + 2..index + 6]
29 .iter()
30 .all(char::is_ascii_hexdigit) =>
31 {
32 repaired.extend(&chars[index..index + 6]);
33 index += 5;
34 }
35 Some(next) if VALID_ESCAPES.contains(next) => {
36 repaired.push('\\');
37 repaired.push(*next);
38 index += 1;
39 }
40 Some(_) => repaired.push_str("\\\\"),
41 }
42 } else if (char as u32) < 0x20 {
43 match char {
44 '\u{8}' => repaired.push_str("\\b"),
45 '\u{c}' => repaired.push_str("\\f"),
46 '\n' => repaired.push_str("\\n"),
47 '\r' => repaired.push_str("\\r"),
48 '\t' => repaired.push_str("\\t"),
49 _ => repaired.push_str(&format!("\\u{:04x}", char as u32)),
50 }
51 } else {
52 repaired.push(char);
53 }
54 index += 1;
55 }
56 repaired
57}
58
59pub fn parse_json_with_repair(json: &str) -> Result<Value, serde_json::Error> {
61 serde_json::from_str(json).or_else(|err| {
62 let repaired = repair_json(json);
63 if repaired == json {
64 Err(err)
65 } else {
66 serde_json::from_str(&repaired)
67 }
68 })
69}
70
71pub fn js_parse_error(text: &str) -> Option<String> {
74 let units: Vec<u16> = text.encode_utf16().collect();
75 let mut scan = V8Scan {
76 units: &units,
77 at: 0,
78 };
79 let failure = match scan.value() {
80 Ok(()) => {
81 scan.skip_whitespace();
82 if scan.at == units.len() {
83 return None;
84 }
85 Some("Unexpected non-whitespace character after JSON")
86 }
87 Err(failure) => failure,
88 };
89 Some(scan.message(text, failure))
90}
91
92struct V8Scan<'a> {
95 units: &'a [u16],
96 at: usize,
97}
98
99type V8Failure = Option<&'static str>;
100
101impl V8Scan<'_> {
102 fn peek(&self) -> Option<u8> {
103 let unit = *self.units.get(self.at)?;
104 Some(u8::try_from(unit).unwrap_or(0xFF))
106 }
107
108 fn skip_whitespace(&mut self) {
109 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
110 self.at += 1;
111 }
112 }
113
114 fn expect(&mut self, token: u8, message: &'static str) -> Result<(), V8Failure> {
116 self.skip_whitespace();
117 if self.peek() != Some(token) {
118 return Err(Some(message));
119 }
120 self.at += 1;
121 Ok(())
122 }
123
124 fn digits(&mut self) -> usize {
125 let start = self.at;
126 while self.peek().is_some_and(|unit| unit.is_ascii_digit()) {
127 self.at += 1;
128 }
129 self.at - start
130 }
131
132 fn value(&mut self) -> Result<(), V8Failure> {
135 let mut open = Vec::new();
137 loop {
138 self.skip_whitespace();
139 match self.peek() {
140 Some(b'"') => self.string()?,
141 Some(b'-' | b'0'..=b'9') => self.number()?,
142 Some(b't') => self.literal(b"true")?,
143 Some(b'f') => self.literal(b"false")?,
144 Some(b'n') => self.literal(b"null")?,
145 Some(token @ (b'{' | b'[')) => {
146 let close = if token == b'{' { b'}' } else { b']' };
147 self.at += 1;
148 self.skip_whitespace();
149 if self.peek() == Some(close) {
150 self.at += 1;
151 } else {
152 if close == b'}' {
153 self.property("Expected property name or '}' in JSON")?;
154 }
155 open.push(close);
156 continue;
157 }
158 }
159 _ => return Err(None),
160 }
161 loop {
162 let Some(&close) = open.last() else {
163 return Ok(());
164 };
165 self.skip_whitespace();
166 if self.peek() == Some(b',') {
167 self.at += 1;
168 if close == b'}' {
169 self.property("Expected double-quoted property name in JSON")?;
170 }
171 break;
172 }
173 self.expect(
174 close,
175 if close == b'}' {
176 "Expected ',' or '}' after property value in JSON"
177 } else {
178 "Expected ',' or ']' after array element in JSON"
179 },
180 )?;
181 open.pop();
182 }
183 }
184 }
185
186 fn property(&mut self, message: &'static str) -> Result<(), V8Failure> {
189 self.skip_whitespace();
190 if self.peek() != Some(b'"') {
191 return Err(Some(message));
192 }
193 self.string()?;
194 self.expect(b':', "Expected ':' after property name in JSON")
195 }
196
197 fn literal(&mut self, word: &[u8]) -> Result<(), V8Failure> {
198 for &unit in word {
199 if self.peek() != Some(unit) {
200 return Err(None);
201 }
202 self.at += 1;
203 }
204 Ok(())
205 }
206
207 fn number(&mut self) -> Result<(), V8Failure> {
208 if self.peek() == Some(b'-') {
209 self.at += 1;
210 }
211 if self.peek() == Some(b'0') {
212 self.at += 1;
213 if self.peek().is_some_and(|unit| unit.is_ascii_digit()) {
214 return Err(None);
215 }
216 } else if self.digits() == 0 {
217 return Err(Some("No number after minus sign in JSON"));
218 }
219 if self.peek() == Some(b'.') {
220 self.at += 1;
221 if self.digits() == 0 {
222 return Err(Some("Unterminated fractional number in JSON"));
223 }
224 }
225 if matches!(self.peek(), Some(b'e' | b'E')) {
226 self.at += 1;
227 if matches!(self.peek(), Some(b'+' | b'-')) {
228 self.at += 1;
229 }
230 if self.digits() == 0 {
231 return Err(Some("Exponent part is missing a number in JSON"));
232 }
233 }
234 Ok(())
235 }
236
237 fn string(&mut self) -> Result<(), V8Failure> {
238 self.at += 1;
239 loop {
240 match self.peek() {
241 None => return Err(Some("Unterminated string in JSON")),
242 Some(b'"') => {
243 self.at += 1;
244 return Ok(());
245 }
246 Some(b'\\') => {
247 self.at += 1;
248 match self.peek() {
249 None => return Err(None),
250 Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => {
251 self.at += 1
252 }
253 Some(b'u') => {
254 self.at += 1;
255 for _ in 0..4 {
256 if !self.peek().is_some_and(|unit| unit.is_ascii_hexdigit()) {
257 return Err(Some("Bad Unicode escape in JSON"));
258 }
259 self.at += 1;
260 }
261 }
262 Some(_) => return Err(Some("Bad escaped character in JSON")),
263 }
264 }
265 Some(0..0x20) => {
266 return Err(Some("Bad control character in string literal in JSON"));
267 }
268 Some(_) => self.at += 1,
269 }
270 }
271 }
272
273 fn message(&self, text: &str, failure: V8Failure) -> String {
275 let at = self.at;
276 let failure = match (failure, self.peek()) {
277 (Some(message), _) => message,
278 (None, None) => return "Unexpected end of JSON input".into(),
279 (None, Some(b'-' | b'0'..=b'9')) => "Unexpected number in JSON",
280 (None, Some(b'"')) => "Unexpected string in JSON",
281 (None, Some(_))
282 if ["NaN", "Infinity", "undefined", "[object Object]"].contains(&text) =>
283 {
284 return format!("\"{text}\" is not valid JSON");
285 }
286 (None, Some(_)) => {
287 const CONTEXT: usize = 10;
288 let units = self.units;
289 let slice = |from: usize, to: usize| String::from_utf16_lossy(&units[from..to]);
290 let token = slice(at, at + 1);
291 let source = if units.len() <= CONTEXT * 2 {
292 format!("\"{text}\"")
293 } else if at < CONTEXT {
294 format!("\"{}\"...", slice(0, at + CONTEXT))
295 } else if at < units.len() - CONTEXT {
296 format!("...\"{}\"...", slice(at - CONTEXT, at + CONTEXT))
297 } else {
298 format!("...\"{}\"", slice(at - CONTEXT, units.len()))
299 };
300 return format!("Unexpected token '{token}', {source} is not valid JSON");
301 }
302 };
303 let mut line = 1;
305 let mut line_start = 0;
306 let mut index = 0;
307 while index < at {
308 if self.units[index] == u16::from(b'\r')
309 && self.units.get(index + 1) == Some(&u16::from(b'\n'))
310 && index + 1 < at
311 {
312 index += 1;
313 }
314 if matches!(u8::try_from(self.units[index]), Ok(b'\r' | b'\n')) {
315 line += 1;
316 line_start = index + 1;
317 }
318 index += 1;
319 }
320 let column = at - line_start + 1;
321 format!("{failure} at position {at} (line {line} column {column})")
322 }
323}
324
325pub fn parse_streaming_json(partial: &str) -> Map<String, Value> {
331 if partial.trim().is_empty() {
332 return Map::new();
333 }
334 let value = parse_json_with_repair(partial)
335 .ok()
336 .or_else(|| parse_partial(partial))
337 .or_else(|| parse_partial(&repair_json(partial)));
338 match value {
339 Some(Value::Object(map)) => map,
340 _ => Map::new(),
341 }
342}
343
344fn parse_partial(text: &str) -> Option<Value> {
346 let mut parser = Partial {
347 chars: text.chars().collect(),
348 index: 0,
349 depth: 0,
350 };
351 parser.whitespace();
352 parser.value().map(|(value, _)| value)
353}
354
355struct Partial {
356 chars: Vec<char>,
357 index: usize,
358 depth: usize,
360}
361
362const MAX_PARTIAL_DEPTH: usize = 512;
365
366type Complete = bool;
368
369impl Partial {
370 fn peek(&self) -> Option<char> {
371 self.chars.get(self.index).copied()
372 }
373
374 fn whitespace(&mut self) {
375 while self
376 .peek()
377 .is_some_and(|c| matches!(c, ' ' | '\t' | '\n' | '\r'))
378 {
379 self.index += 1;
380 }
381 }
382
383 fn value(&mut self) -> Option<(Value, Complete)> {
385 let next = self.peek()?;
386 if matches!(next, '{' | '[') {
387 if self.depth == MAX_PARTIAL_DEPTH {
388 return None;
389 }
390 self.depth += 1;
391 let value = if next == '{' {
392 self.object()
393 } else {
394 self.array()
395 };
396 self.depth -= 1;
397 return value;
398 }
399 match next {
400 '"' => self
401 .string()
402 .map(|(text, complete)| (Value::String(text), complete)),
403 't' => self.literal("true", Value::Bool(true)),
404 'f' => self.literal("false", Value::Bool(false)),
405 'n' => self.literal("null", Value::Null),
406 '-' | '0'..='9' => self.number(),
407 _ => None,
408 }
409 }
410
411 fn object(&mut self) -> Option<(Value, Complete)> {
412 self.index += 1;
413 let mut map = Map::new();
414 loop {
415 self.whitespace();
416 match self.peek() {
417 None => return Some((Value::Object(map), false)),
418 Some('}') => {
419 self.index += 1;
420 return Some((Value::Object(map), true));
421 }
422 Some(',') => {
423 self.index += 1;
424 continue;
425 }
426 Some('"') => {}
427 Some(_) => return None,
428 }
429 let (key, complete) = self.string()?;
430 if !complete {
431 return Some((Value::Object(map), false));
432 }
433 self.whitespace();
434 match self.peek() {
435 None => return Some((Value::Object(map), false)),
436 Some(':') => self.index += 1,
437 Some(_) => return None,
438 }
439 self.whitespace();
440 if self.peek().is_none() {
441 return Some((Value::Object(map), false));
442 }
443 match self.value() {
444 Some((value, true)) => {
445 map.insert(key, value);
446 }
447 Some((value, false)) => {
448 map.insert(key, value);
449 return Some((Value::Object(map), false));
450 }
451 None if self.peek().is_none() => return Some((Value::Object(map), false)),
452 None => return None,
453 }
454 }
455 }
456
457 fn array(&mut self) -> Option<(Value, Complete)> {
458 self.index += 1;
459 let mut items = Vec::new();
460 loop {
461 self.whitespace();
462 match self.peek() {
463 None => return Some((Value::Array(items), false)),
464 Some(']') => {
465 self.index += 1;
466 return Some((Value::Array(items), true));
467 }
468 Some(',') => {
469 self.index += 1;
470 continue;
471 }
472 Some(_) => {}
473 }
474 match self.value() {
475 Some((value, true)) => items.push(value),
476 Some((value, false)) => {
477 items.push(value);
478 return Some((Value::Array(items), false));
479 }
480 None if self.peek().is_none() => return Some((Value::Array(items), false)),
481 None => return None,
482 }
483 }
484 }
485
486 fn string(&mut self) -> Option<(String, Complete)> {
487 self.index += 1;
488 let mut text = String::new();
489 while let Some(char) = self.peek() {
490 self.index += 1;
491 match char {
492 '"' => return Some((text, true)),
493 '\\' => {
494 let Some(escape) = self.peek() else {
495 return Some((text, false));
496 };
497 self.index += 1;
498 match escape {
499 '"' | '\\' | '/' => text.push(escape),
500 'b' => text.push('\u{8}'),
501 'f' => text.push('\u{c}'),
502 'n' => text.push('\n'),
503 'r' => text.push('\r'),
504 't' => text.push('\t'),
505 'u' => {
506 let Some(unit) = self.hex4() else {
507 return Some((text, false));
508 };
509 let code = if (0xd800..0xdc00).contains(&unit)
510 && self.chars.get(self.index) == Some(&'\\')
511 && self.chars.get(self.index + 1) == Some(&'u')
512 {
513 self.index += 2;
514 match self.hex4() {
515 Some(low) if (0xdc00..0xe000).contains(&low) => {
516 0x10000 + ((unit - 0xd800) << 10) + (low - 0xdc00)
517 }
518 _ => 0xfffd,
519 }
520 } else {
521 unit
522 };
523 text.push(char::from_u32(code).unwrap_or('\u{fffd}'));
524 }
525 _ => return None,
526 }
527 }
528 _ => text.push(char),
529 }
530 }
531 Some((text, false))
532 }
533
534 fn hex4(&mut self) -> Option<u32> {
535 let digits: String = self.chars.get(self.index..self.index + 4)?.iter().collect();
536 let value = u32::from_str_radix(&digits, 16).ok()?;
537 self.index += 4;
538 Some(value)
539 }
540
541 fn literal(&mut self, word: &str, value: Value) -> Option<(Value, Complete)> {
542 for expected in word.chars() {
543 match self.peek() {
544 None => return Some((value, false)),
545 Some(char) if char == expected => self.index += 1,
546 Some(_) => return None,
547 }
548 }
549 Some((value, true))
550 }
551
552 fn number(&mut self) -> Option<(Value, Complete)> {
553 let start = self.index;
554 while self
555 .peek()
556 .is_some_and(|c| c.is_ascii_digit() || matches!(c, '-' | '+' | '.' | 'e' | 'E'))
557 {
558 self.index += 1;
559 }
560 let complete = self.peek().is_some();
561 let mut text: String = self.chars[start..self.index].iter().collect();
562 loop {
563 if let Ok(number) = serde_json::from_str::<Number>(&text) {
564 return Some((Value::Number(number), complete));
565 }
566 if complete || text.pop().is_none() || text.is_empty() {
567 return None;
568 }
569 }
570 }
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576 use serde_json::json;
577
578 #[test]
580 fn v8_parse_errors() {
581 for (text, message) in [
582 ("{\"type\":\"get_state\"}", None),
583 (" [1, 2.5e-3, true, null, \"\\u00e9\"] ", None),
584 ("", Some("Unexpected end of JSON input")),
585 (
586 "not json",
587 Some("Unexpected token 'o', \"not json\" is not valid JSON"),
588 ),
589 (
590 "{\"type\":\"x\"} extra",
591 Some(
592 "Unexpected non-whitespace character after JSON at position 13 (line 1 column 14)",
593 ),
594 ),
595 (
596 "{",
597 Some("Expected property name or '}' in JSON at position 1 (line 1 column 2)"),
598 ),
599 ("{\"type\":", Some("Unexpected end of JSON input")),
600 (
601 "{\"a\":1,}",
602 Some(
603 "Expected double-quoted property name in JSON at position 7 (line 1 column 8)",
604 ),
605 ),
606 (
607 "{\"a\" 1}",
608 Some("Expected ':' after property name in JSON at position 5 (line 1 column 6)"),
609 ),
610 (
611 "\"abc",
612 Some("Unterminated string in JSON at position 4 (line 1 column 5)"),
613 ),
614 (
615 "[1 2]",
616 Some(
617 "Expected ',' or ']' after array element in JSON at position 3 (line 1 column 4)",
618 ),
619 ),
620 (
621 "{\"a\":1 \"b\":2}",
622 Some(
623 "Expected ',' or '}' after property value in JSON at position 7 (line 1 column 8)",
624 ),
625 ),
626 (
627 "-",
628 Some("No number after minus sign in JSON at position 1 (line 1 column 2)"),
629 ),
630 (
631 "1.",
632 Some("Unterminated fractional number in JSON at position 2 (line 1 column 3)"),
633 ),
634 (
635 "1e+",
636 Some("Exponent part is missing a number in JSON at position 3 (line 1 column 4)"),
637 ),
638 (
639 "\"\\x\"",
640 Some("Bad escaped character in JSON at position 2 (line 1 column 3)"),
641 ),
642 (
643 "\"\\u12g4\"",
644 Some("Bad Unicode escape in JSON at position 5 (line 1 column 6)"),
645 ),
646 (
647 "\"a\tb\"",
648 Some(
649 "Bad control character in string literal in JSON at position 2 (line 1 column 3)",
650 ),
651 ),
652 ("\"\\", Some("Unexpected end of JSON input")),
653 ("tru", Some("Unexpected end of JSON input")),
654 (
655 "01",
656 Some("Unexpected number in JSON at position 1 (line 1 column 2)"),
657 ),
658 (
659 "[1,]",
660 Some("Unexpected token ']', \"[1,]\" is not valid JSON"),
661 ),
662 ("NaN", Some("\"NaN\" is not valid JSON")),
663 ("undefined", Some("\"undefined\" is not valid JSON")),
664 (
665 "x{\"type\":\"prompt\",\"message\":\"this is long\"}",
666 Some("Unexpected token 'x', \"x{\"type\":\"\"... is not valid JSON"),
667 ),
668 (
669 "{\"type\":\"prompt\",\"message\":\"long enough\"x",
670 Some(
671 "Expected ',' or '}' after property value in JSON at position 40 (line 1 column 41)",
672 ),
673 ),
674 (
675 "{\"a\":[1,2,3,4,5,6,7,8,9]",
676 Some(
677 "Expected ',' or '}' after property value in JSON at position 24 (line 1 column 25)",
678 ),
679 ),
680 (
681 "{\"x\":1}\r\n{",
682 Some(
683 "Unexpected non-whitespace character after JSON at position 9 (line 2 column 1)",
684 ),
685 ),
686 (
687 "\"😀\" x",
688 Some(
689 "Unexpected non-whitespace character after JSON at position 5 (line 1 column 6)",
690 ),
691 ),
692 ("é", Some("Unexpected token 'é', \"é\" is not valid JSON")),
693 (
694 "{\"type\":\"prompt\",\"message\":x,\"more\":\"stuff\"}",
695 Some("Unexpected token 'x', ...\"\"message\":x,\"more\":\"\"... is not valid JSON"),
696 ),
697 (
698 "{\"type\":\"prompt\",\"message\":x}",
699 Some("Unexpected token 'x', ...\"\"message\":x}\" is not valid JSON"),
700 ),
701 ] {
702 assert_eq!(js_parse_error(text).as_deref(), message, "{text}");
703 }
704 let deep = "[".repeat(100_000);
706 assert_eq!(
707 js_parse_error(&deep).as_deref(),
708 Some("Unexpected end of JSON input")
709 );
710 assert_eq!(js_parse_error(&(deep.clone() + &"]".repeat(100_000))), None);
711 }
712
713 #[test]
714 fn repairs_controls_and_escapes() {
715 assert_eq!(repair_json("{\"a\":\"x\ny\"}"), "{\"a\":\"x\\ny\"}");
716 assert_eq!(repair_json(r#"{"a":"C:\dir"}"#), r#"{"a":"C:\\dir"}"#);
717 assert_eq!(repair_json(r#"{"a":"\u00e9\n"}"#), r#"{"a":"\u00e9\n"}"#);
718 assert_eq!(
719 parse_json_with_repair("{\"a\":\"tab\there\"}").unwrap(),
720 json!({"a": "tab\there"})
721 );
722 }
723
724 #[test]
725 fn parses_prefixes() {
726 let cases = [
727 ("", json!({})),
728 ("{", json!({})),
729 (r#"{"pa"#, json!({})),
730 (r#"{"path""#, json!({})),
731 (r#"{"path":"#, json!({})),
732 (r#"{"path":"/tm"#, json!({"path": "/tm"})),
733 (r#"{"path":"/tmp","n":12"#, json!({"path": "/tmp", "n": 12})),
734 (r#"{"n":1.5e"#, json!({"n": 1.5})),
735 (r#"{"a":[1,{"b":tr"#, json!({"a": [1, {"b": true}]})),
736 (r#"{"a":"\u00"#, json!({"a": ""})),
737 (r#"{"a":"x\"#, json!({"a": "x"})),
738 (r#"{"a":1}"#, json!({"a": 1})),
739 ("[1,2]", json!({})),
740 ("nonsense", json!({})),
741 ];
742 for (input, expected) in cases {
743 assert_eq!(
744 Value::Object(parse_streaming_json(input)),
745 expected,
746 "{input}"
747 );
748 }
749 let nested = |depth: usize| format!("{{\"a\":{}", "[".repeat(depth));
751 assert!(!parse_streaming_json(&nested(MAX_PARTIAL_DEPTH - 1)).is_empty());
752 assert!(parse_streaming_json(&nested(MAX_PARTIAL_DEPTH)).is_empty());
753 assert!(parse_streaming_json(&nested(100_000)).is_empty());
754 }
755}