use anybytes::Bytes;
use winnow::stream::Stream;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ScanError {
Syntax(String),
}
impl std::fmt::Display for ScanError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ScanError::Syntax(msg) => write!(f, "json scan error: {msg}"),
}
}
}
impl std::error::Error for ScanError {}
fn syntax(msg: &str) -> ScanError {
ScanError::Syntax(msg.to_owned())
}
pub fn skip_ws(bytes: &mut Bytes) {
while matches!(bytes.peek_token(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
bytes.pop_front();
}
}
fn is_value_boundary(byte: u8) -> bool {
matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | b',' | b']' | b'}')
}
pub fn expect(bytes: &mut Bytes, expected: u8) -> Result<(), ScanError> {
match bytes.pop_front() {
Some(b) if b == expected => Ok(()),
_ => Err(syntax("unexpected token")),
}
}
pub fn expect_literal(bytes: &mut Bytes, literal: &[u8]) -> Result<(), ScanError> {
for expected in literal {
expect(bytes, *expected)?;
}
Ok(())
}
fn parse_utf16_code_unit(bytes: &mut Bytes) -> Result<u16, ScanError> {
use winnow::error::InputError;
use winnow::token::take;
use winnow::Parser;
let mut grab = take::<_, _, InputError<Bytes>>(4usize);
let hex = grab
.parse_next(bytes)
.map_err(|_| syntax("unterminated unicode escape"))?;
let mut code: u16 = 0;
for h in hex.as_ref() {
code = (code << 4)
| match h {
b'0'..=b'9' => (h - b'0') as u16,
b'a'..=b'f' => (h - b'a' + 10) as u16,
b'A'..=b'F' => (h - b'A' + 10) as u16,
_ => return Err(syntax("invalid unicode escape")),
};
}
Ok(code)
}
pub fn parse_unicode_escape(bytes: &mut Bytes) -> Result<Vec<u8>, ScanError> {
let first = parse_utf16_code_unit(bytes)?;
let code = match first {
high @ 0xD800..=0xDBFF => {
if bytes.pop_front() != Some(b'\\') || bytes.pop_front() != Some(b'u') {
return Err(syntax("high surrogate must be followed by a low surrogate"));
}
let low = parse_utf16_code_unit(bytes)?;
if !(0xDC00..=0xDFFF).contains(&low) {
return Err(syntax("high surrogate must be followed by a low surrogate"));
}
0x1_0000 + (((high as u32) - 0xD800) << 10) + ((low as u32) - 0xDC00)
}
0xDC00..=0xDFFF => return Err(syntax("lone low surrogate")),
scalar => scalar as u32,
};
if let Some(ch) = char::from_u32(code) {
let mut buf = [0u8; 4];
Ok(ch.encode_utf8(&mut buf).as_bytes().to_vec())
} else {
Err(syntax("invalid unicode escape"))
}
}
pub fn parse_string(bytes: &mut Bytes) -> Result<Bytes, ScanError> {
expect(bytes, b'"')?;
{
use winnow::error::InputError;
use winnow::token::take_while;
use winnow::Parser;
let mut tentative = bytes.clone();
let mut segment = take_while::<_, _, InputError<Bytes>>(0.., |b: u8| {
b >= 0x20 && b != b'"' && b != b'\\'
});
if let Ok(prefix) = segment.parse_next(&mut tentative) {
if tentative.peek_token() == Some(b'"') {
tentative.pop_front();
*bytes = tentative;
return Ok(prefix);
}
}
}
let mut out = Vec::new();
loop {
use winnow::error::InputError;
use winnow::token::take_while;
use winnow::Parser;
let mut segment = take_while::<_, _, InputError<Bytes>>(0.., |b: u8| {
b >= 0x20 && b != b'\\' && b != b'"'
});
let chunk = segment
.parse_next(bytes)
.map_err(|_| syntax("unterminated string"))?;
out.extend_from_slice(chunk.as_ref());
match bytes.peek_token() {
Some(b'"') => {
bytes.pop_front();
return Ok(Bytes::from(out));
}
Some(b'\\') => {
bytes.pop_front();
let esc = bytes
.pop_front()
.ok_or_else(|| syntax("unterminated escape"))?;
match esc {
b'"' => out.push(b'"'),
b'\\' => out.push(b'\\'),
b'/' => out.push(b'/'),
b'b' => out.push(0x08),
b'f' => out.push(0x0c),
b'n' => out.push(b'\n'),
b'r' => out.push(b'\r'),
b't' => out.push(b'\t'),
b'u' => out.extend_from_slice(&parse_unicode_escape(bytes)?),
_ => return Err(syntax("invalid escape sequence")),
}
}
Some(0x00..=0x1F) => return Err(syntax("unescaped control character in string")),
None => return Err(syntax("unterminated string")),
_ => unreachable!("peek_token only yields bytes"),
}
}
}
pub fn parse_number(bytes: &mut Bytes) -> Result<Bytes, ScanError> {
fn consume_digits(bytes: &mut Bytes) -> usize {
let mut count = 0;
while matches!(bytes.peek_token(), Some(b'0'..=b'9')) {
bytes.pop_front();
count += 1;
}
count
}
let start = bytes.clone();
let mut rest = start.clone();
if rest.peek_token() == Some(b'-') {
rest.pop_front();
}
match rest.peek_token() {
Some(b'0') => {
rest.pop_front();
if matches!(rest.peek_token(), Some(b'0'..=b'9')) {
return Err(syntax("leading zero in number"));
}
}
Some(b'1'..=b'9') => {
rest.pop_front();
consume_digits(&mut rest);
}
_ => return Err(syntax("expected number")),
}
if rest.peek_token() == Some(b'.') {
rest.pop_front();
if consume_digits(&mut rest) == 0 {
return Err(syntax("fraction requires at least one digit"));
}
}
if matches!(rest.peek_token(), Some(b'e' | b'E')) {
rest.pop_front();
if matches!(rest.peek_token(), Some(b'+' | b'-')) {
rest.pop_front();
}
if consume_digits(&mut rest) == 0 {
return Err(syntax("exponent requires at least one digit"));
}
}
if matches!(rest.peek_token(), Some(byte) if !is_value_boundary(byte)) {
return Err(syntax("invalid character after number"));
}
let consumed = start.len() - rest.len();
let raw = start.slice(..consumed);
*bytes = rest;
Ok(raw)
}
pub fn parse_f64(bytes: &mut Bytes) -> Result<f64, ScanError> {
let raw = parse_number(bytes)?;
let s = raw.view::<str>().map_err(|_| syntax("invalid number"))?;
let n: f64 = s.as_ref().parse().map_err(|_| syntax("invalid number"))?;
if !n.is_finite() {
return Err(syntax("non-finite number"));
}
Ok(n)
}
pub fn skip_value(bytes: &mut Bytes) -> Result<(), ScanError> {
fn literal(bytes: &mut Bytes, expected: &[u8]) -> Result<(), ScanError> {
expect_literal(bytes, expected)?;
if matches!(bytes.peek_token(), Some(byte) if !is_value_boundary(byte)) {
return Err(syntax("invalid character after literal"));
}
Ok(())
}
match bytes.peek_token() {
Some(b'"') => {
let _ = parse_string(bytes)?;
Ok(())
}
Some(b't') => literal(bytes, b"true"),
Some(b'f') => literal(bytes, b"false"),
Some(b'n') => literal(bytes, b"null"),
Some(b'{') => object(bytes, (), |(), _key, b| skip_value(b)),
Some(b'[') => array(bytes, (), |(), b| skip_value(b)),
Some(_) => {
let _ = parse_number(bytes)?;
Ok(())
}
None => Err(syntax("expected a value")),
}
}
pub fn take_value(bytes: &mut Bytes) -> Result<Bytes, ScanError> {
let start = bytes.clone();
let mut rest = start.clone();
skip_value(&mut rest)?;
let consumed = start.len() - rest.len();
let value = start.slice(..consumed);
*bytes = rest;
Ok(value)
}
pub fn object<A>(
bytes: &mut Bytes,
init: A,
mut member: impl FnMut(A, Bytes, &mut Bytes) -> Result<A, ScanError>,
) -> Result<A, ScanError> {
expect(bytes, b'{')?;
skip_ws(bytes);
if bytes.peek_token() == Some(b'}') {
bytes.pop_front();
return Ok(init);
}
let mut acc = init;
loop {
skip_ws(bytes);
let key = parse_string(bytes)?;
skip_ws(bytes);
expect(bytes, b':')?;
skip_ws(bytes);
acc = member(acc, key, bytes)?;
skip_ws(bytes);
match bytes.pop_front() {
Some(b',') => continue,
Some(b'}') => return Ok(acc),
_ => return Err(syntax("expected ',' or '}' in object")),
}
}
}
pub fn array<A>(
bytes: &mut Bytes,
init: A,
mut element: impl FnMut(A, &mut Bytes) -> Result<A, ScanError>,
) -> Result<A, ScanError> {
expect(bytes, b'[')?;
skip_ws(bytes);
if bytes.peek_token() == Some(b']') {
bytes.pop_front();
return Ok(init);
}
let mut acc = init;
loop {
skip_ws(bytes);
acc = element(acc, bytes)?;
skip_ws(bytes);
match bytes.pop_front() {
Some(b',') => continue,
Some(b']') => return Ok(acc),
_ => return Err(syntax("expected ',' or ']' in array")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bytes(s: &str) -> Bytes {
Bytes::from(s.as_bytes().to_vec())
}
#[test]
fn string_zero_copy_and_escaped() {
let mut b = bytes(r#""plain" rest"#);
let original_ptr = b.as_ptr();
let s = parse_string(&mut b).unwrap();
assert_eq!(s.as_ptr(), unsafe { original_ptr.add(1) });
assert_eq!(s.view::<str>().unwrap().as_ref(), "plain");
let mut e = bytes(r#""a\nb☺""#);
let s = parse_string(&mut e).unwrap();
assert_eq!(s.view::<str>().unwrap().as_ref(), "a\nb\u{263A}");
}
#[test]
fn string_decodes_utf16_surrogate_pairs() {
let mut emoji = bytes(r#""\uD83D\uDE00""#);
let decoded = parse_string(&mut emoji).unwrap();
assert_eq!(decoded.view::<str>().unwrap().as_ref(), "😀");
assert_eq!(emoji.peek_token(), None);
let mut maximum = bytes(r#""\uDBFF\uDFFF""#);
let decoded = parse_string(&mut maximum).unwrap();
assert_eq!(decoded.view::<str>().unwrap().as_ref(), "\u{10FFFF}");
}
#[test]
fn string_rejects_lone_and_malformed_surrogates() {
for invalid in [
r#""\uD800""#,
r#""\uDC00""#,
r#""\uD800\u0041""#,
r#""\uD800\uD800""#,
r#""\uD800x""#,
r#""\uD800\xDC00""#,
r#""\uD800\uDFF""#,
] {
let mut input = bytes(invalid);
assert!(parse_string(&mut input).is_err(), "accepted {invalid:?}");
}
}
#[test]
fn string_rejects_every_unescaped_control_byte() {
for control in 0x00..=0x1F {
let mut fast_path = Bytes::from_source(vec![b'"', b'a', control, b'"']);
assert!(
parse_string(&mut fast_path).is_err(),
"fast path accepted control byte 0x{control:02X}"
);
let mut escaped_path = Bytes::from_source(vec![b'"', b'\\', b'n', control, b'"']);
assert!(
parse_string(&mut escaped_path).is_err(),
"escaped path accepted control byte 0x{control:02X}"
);
}
let mut escaped_controls = bytes(r#""\b\f\n\r\t\u0000""#);
assert_eq!(
parse_string(&mut escaped_controls).unwrap().as_ref(),
b"\x08\x0c\n\r\t\0"
);
}
#[test]
fn skip_value_handles_arbitrary_nesting() {
let mut b = bytes(r#"{"a":[1,{"b":"c\"d"},[true,null]],"keep":42}"#);
expect(&mut b, b'{').unwrap();
skip_ws(&mut b);
let key = parse_string(&mut b).unwrap();
assert_eq!(key.view::<str>().unwrap().as_ref(), "a");
skip_ws(&mut b);
expect(&mut b, b':').unwrap();
skip_ws(&mut b);
skip_value(&mut b).unwrap(); skip_ws(&mut b);
expect(&mut b, b',').unwrap();
skip_ws(&mut b);
let key2 = parse_string(&mut b).unwrap();
assert_eq!(key2.view::<str>().unwrap().as_ref(), "keep");
}
#[test]
fn project_two_fields_skipping_the_rest() {
let mut b =
bytes(r#"{"noise":{"big":[0,1,2,3]},"uuid":"u-1","more":123,"role":"assistant"}"#);
let mut uuid = None;
let mut role = None;
expect(&mut b, b'{').unwrap();
loop {
skip_ws(&mut b);
let key = parse_string(&mut b).unwrap();
let key = key.view::<str>().unwrap().as_ref().to_owned();
skip_ws(&mut b);
expect(&mut b, b':').unwrap();
skip_ws(&mut b);
match key.as_str() {
"uuid" => {
uuid = Some(
parse_string(&mut b)
.unwrap()
.view::<str>()
.unwrap()
.as_ref()
.to_owned(),
)
}
"role" => {
role = Some(
parse_string(&mut b)
.unwrap()
.view::<str>()
.unwrap()
.as_ref()
.to_owned(),
)
}
_ => skip_value(&mut b).unwrap(),
}
skip_ws(&mut b);
match b.pop_front() {
Some(b',') => continue,
Some(b'}') => break,
other => panic!("unexpected {other:?}"),
}
}
assert_eq!(uuid.as_deref(), Some("u-1"));
assert_eq!(role.as_deref(), Some("assistant"));
}
#[test]
fn f64_rejects_non_finite_shaped_and_parses_plain() {
let mut b = bytes("3.5e2,");
assert_eq!(parse_f64(&mut b).unwrap(), 350.0);
let mut overflow = bytes("1e999");
assert!(parse_f64(&mut overflow).is_err());
}
#[test]
fn number_accepts_exact_rfc_8259_grammar() {
for valid in [
"0", "-0", "1", "-1", "10", "0.25", "-0.25", "1e10", "1E+10", "1e-10", "0.0e0",
] {
let source = format!("{valid},tail");
let mut input = bytes(&source);
let raw = parse_number(&mut input).unwrap();
assert_eq!(raw.as_ref(), valid.as_bytes(), "failed {valid:?}");
assert_eq!(input.as_ref(), b",tail");
}
}
#[test]
fn number_rejects_non_json_forms_without_advancing() {
for invalid in [
"+1", ".1", "-", "01", "-01", "1.", "1e", "1E+", "1e-", "--1", "1+2", "1e2e3", "NaN",
"Infinity",
] {
let mut input = bytes(invalid);
let before = input.clone();
assert!(parse_number(&mut input).is_err(), "accepted {invalid:?}");
assert_eq!(input, before, "advanced on {invalid:?}");
}
for invalid in ["[01]", "[-01]", "[1.]", "[1e]", "[+1]"] {
let mut input = bytes(invalid);
assert!(skip_value(&mut input).is_err(), "accepted {invalid:?}");
}
}
#[test]
fn take_value_returns_an_exact_zero_copy_slice() {
let mut input = bytes(r#"{"a": [1, 2], "s":"x\\ny"},tail"#);
let source_ptr = input.as_ptr();
let value = take_value(&mut input).unwrap();
assert_eq!(value.as_ref(), br#"{"a": [1, 2], "s":"x\\ny"}"#);
assert_eq!(value.as_ptr(), source_ptr);
assert_eq!(input.as_ref(), b",tail");
let mut invalid = bytes("[01],tail");
let before = invalid.clone();
assert!(take_value(&mut invalid).is_err());
assert_eq!(invalid, before);
}
#[test]
fn object_folds_top_level_keys() {
let mut b = bytes(r#"{"a":1,"b":"two","c":[3,3]}"#);
let keys = object(&mut b, Vec::new(), |mut acc, key, v| {
acc.push(key.view::<str>().unwrap().as_ref().to_owned());
skip_value(v)?;
Ok(acc)
})
.unwrap();
assert_eq!(keys, vec!["a", "b", "c"]);
assert_eq!(b.peek_token(), None); }
#[test]
fn array_sums_numbers() {
let mut b = bytes("[1, 2, 3, 4]");
let sum = array(&mut b, 0.0, |acc, v| Ok(acc + parse_f64(v)?)).unwrap();
assert_eq!(sum, 10.0);
assert_eq!(b.peek_token(), None);
}
#[test]
fn empty_object_and_array() {
let mut o = bytes("{}");
let n = object(&mut o, 0usize, |acc, _k, v| {
skip_value(v)?;
Ok(acc + 1)
})
.unwrap();
assert_eq!(n, 0);
assert_eq!(o.peek_token(), None);
let mut a = bytes("[]");
let n = array(&mut a, 0usize, |acc, v| {
skip_value(v)?;
Ok(acc + 1)
})
.unwrap();
assert_eq!(n, 0);
assert_eq!(a.peek_token(), None);
}
#[test]
fn whitespace_in_all_positions() {
let mut b = bytes(" { \"a\" : 1 , \"b\" : 2 } ");
skip_ws(&mut b);
let keys = object(&mut b, Vec::new(), |mut acc, key, v| {
acc.push(key.view::<str>().unwrap().as_ref().to_owned());
skip_value(v)?;
Ok(acc)
})
.unwrap();
assert_eq!(keys, vec!["a", "b"]);
skip_ws(&mut b);
assert_eq!(b.peek_token(), None);
for non_json_whitespace in [b'\x0b', b'\x0c'] {
let mut invalid = Bytes::from_source(vec![b'{', non_json_whitespace, b'}']);
assert!(object(&mut invalid, (), |(), _key, value| { skip_value(value) }).is_err());
}
}
#[test]
fn nested_object_in_array_in_object() {
let mut b = bytes(r#"{"outer":[{"inner":1},{"inner":2}],"tail":9}"#);
let mut inner_sum = 0.0;
let mut saw_tail = false;
object(&mut b, (), |(), key, v| {
match key.view::<str>().unwrap().as_ref() {
"outer" => {
inner_sum = array(v, 0.0, |acc, e| {
let one = object(e, 0.0, |a, k2, v2| {
assert_eq!(k2.view::<str>().unwrap().as_ref(), "inner");
Ok(a + parse_f64(v2)?)
})?;
Ok(acc + one)
})?;
}
"tail" => {
saw_tail = true;
skip_value(v)?;
}
_ => skip_value(v)?,
}
Ok(())
})
.unwrap();
assert_eq!(inner_sum, 3.0);
assert!(saw_tail);
assert_eq!(b.peek_token(), None);
}
#[test]
fn rejects_trailing_comma_and_missing_colon() {
let mut obj_trailing = bytes(r#"{"a":1,}"#);
assert!(matches!(
object(&mut obj_trailing, (), |(), _k, v| skip_value(v)),
Err(ScanError::Syntax(_))
));
let mut arr_trailing = bytes("[1,]");
assert!(matches!(
array(&mut arr_trailing, (), |(), v| skip_value(v)),
Err(ScanError::Syntax(_))
));
let mut missing_colon = bytes(r#"{"a" 1}"#);
assert!(matches!(
object(&mut missing_colon, (), |(), _k, v| skip_value(v)),
Err(ScanError::Syntax(_))
));
for invalid in ["truex", "false0", "nullnull"] {
let mut value = bytes(invalid);
assert!(skip_value(&mut value).is_err(), "accepted {invalid:?}");
}
}
}