use crate::{error::YsonError, lexer::YsonIterator, node::Token, ser::YsonFormat};
const MAX_DEPTH: usize = 128;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scan {
Complete {
len: usize,
},
Incomplete,
}
pub fn scan_value(input: &[u8], format: YsonFormat) -> Result<Scan, YsonError> {
let mut lexer = YsonIterator::new(input, matches!(format, YsonFormat::Binary));
match scan_tree(&mut lexer, 0) {
Ok(()) => Ok(Scan::Complete { len: lexer.pos() }),
Err(YsonError::Eof | YsonError::UnexpectedEof(_)) => Ok(Scan::Incomplete),
Err(e) => Err(e),
}
}
fn scan_tree(lexer: &mut YsonIterator<'_>, depth: usize) -> Result<(), YsonError> {
if depth > MAX_DEPTH {
return Err(YsonError::Custom("Recursion limit exceeded".into()));
}
let mut token = lexer.next_token()?;
if matches!(token, Token::BeginAttributes) {
scan_fragment(lexer, depth + 1, Token::EndAttributes)?;
token = lexer.next_token()?;
}
scan_object(lexer, depth, token)
}
fn scan_object(
lexer: &mut YsonIterator<'_>,
depth: usize,
token: Token<'_>,
) -> Result<(), YsonError> {
match token {
Token::String(_)
| Token::Int64(_)
| Token::Uint64(_)
| Token::Double(_)
| Token::Boolean(_)
| Token::Entity => Ok(()),
Token::BeginList => scan_list(lexer, depth + 1),
Token::BeginMap => scan_fragment(lexer, depth + 1, Token::EndMap),
other => Err(YsonError::UnexpectedToken {
expected: "a YSON value",
found: format!("{other:?}"),
pos: lexer.pos(),
}),
}
}
fn scan_list(lexer: &mut YsonIterator<'_>, depth: usize) -> Result<(), YsonError> {
if depth > MAX_DEPTH {
return Err(YsonError::Custom("Recursion limit exceeded".into()));
}
loop {
match lexer.peek_byte()? {
b']' => {
lexer.next_token()?;
return Ok(());
}
b';' => {
lexer.next_token()?;
}
_ => scan_tree(lexer, depth)?,
}
}
}
fn scan_fragment(
lexer: &mut YsonIterator<'_>,
depth: usize,
end: Token<'static>,
) -> Result<(), YsonError> {
if depth > MAX_DEPTH {
return Err(YsonError::Custom("Recursion limit exceeded".into()));
}
let end_byte = match end {
Token::EndMap => b'}',
Token::EndAttributes => b'>',
_ => unreachable!("scan_fragment is only called for maps and attributes"),
};
loop {
let peeked = lexer.peek_byte()?;
if peeked == end_byte {
lexer.next_token()?;
return Ok(());
}
if peeked == b';' {
lexer.next_token()?;
continue;
}
match lexer.next_token()? {
Token::String(_) => {}
other => {
return Err(YsonError::UnexpectedToken {
expected: "a map key",
found: format!("{other:?}"),
pos: lexer.pos(),
});
}
}
match lexer.next_token()? {
Token::KeyValueSeparator => {}
other => {
return Err(YsonError::UnexpectedToken {
expected: "'='",
found: format!("{other:?}"),
pos: lexer.pos(),
});
}
}
scan_tree(lexer, depth)?;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn complete(input: &[u8], format: YsonFormat) -> usize {
match scan_value(input, format).expect("scan must not fail") {
Scan::Complete { len } => len,
Scan::Incomplete => panic!("expected a complete value in {input:?}"),
}
}
#[test]
fn scans_text_scalars() {
assert_eq!(complete(b"42", YsonFormat::Text), 2);
assert_eq!(complete(b"42;43", YsonFormat::Text), 2);
assert_eq!(complete(b"#", YsonFormat::Text), 1);
assert_eq!(complete(b"%true", YsonFormat::Text), 5);
assert_eq!(complete(br#""a;b""#, YsonFormat::Text), 5);
}
#[test]
fn scans_text_composites() {
assert_eq!(complete(b"{a=1}", YsonFormat::Text), 5);
assert_eq!(complete(b"{a=1};{b=2}", YsonFormat::Text), 5);
assert_eq!(complete(b"[1;2;3]", YsonFormat::Text), 7);
assert_eq!(complete(b"{a={b=[1;2]}}", YsonFormat::Text), 13);
assert_eq!(complete(b"{a=1;}", YsonFormat::Text), 6);
}
#[test]
fn scans_attributed_values() {
assert_eq!(complete(b"<a=1>#", YsonFormat::Text), 6);
assert_eq!(complete(b"<a=1>#;{b=2}", YsonFormat::Text), 6);
assert_eq!(complete(b"<a=1;b=2>[1]", YsonFormat::Text), 12);
assert_eq!(complete(b"<a=<b=1>#>#", YsonFormat::Text), 11);
}
#[test]
fn reports_truncation_as_incomplete() {
for input in [
b"{a=1".as_slice(),
b"{a=",
b"{",
b"[1;2",
b"<a=1>",
b"<a=1",
b"",
b"\"unterminated",
] {
assert_eq!(
scan_value(input, YsonFormat::Text).expect("no error"),
Scan::Incomplete,
"input {:?}",
String::from_utf8_lossy(input)
);
}
}
#[test]
fn scans_binary_values() {
assert_eq!(complete(&[0x02, 0x02], YsonFormat::Binary), 2);
assert_eq!(complete(b"\x01\x06abc", YsonFormat::Binary), 5);
assert_eq!(complete(b"\x01\x06};]", YsonFormat::Binary), 5);
assert_eq!(
complete(&[0x03, 0, 0, 0, 0, 0, 0, 0, 0], YsonFormat::Binary),
9
);
}
#[test]
fn scans_a_binary_map_and_stops_at_the_boundary() {
let one = b"{\x01\x02a=\x02\x02}";
let mut two = one.to_vec();
two.push(b';');
two.extend_from_slice(one);
assert_eq!(complete(one, YsonFormat::Binary), one.len());
assert_eq!(complete(&two, YsonFormat::Binary), one.len());
}
#[test]
fn binary_truncation_is_incomplete() {
let full = b"{\x01\x02a=\x02\x02}";
for cut in 0..full.len() {
assert_eq!(
scan_value(&full[..cut], YsonFormat::Binary).expect("no error"),
Scan::Incomplete,
"cut at {cut}"
);
}
assert_eq!(
scan_value(b"\x01\x14abc", YsonFormat::Binary).expect("no error"),
Scan::Incomplete
);
}
#[test]
fn rejects_malformed_input() {
assert!(scan_value(&[0x07], YsonFormat::Binary).is_err());
assert!(scan_value(b"{a 1}", YsonFormat::Text).is_err());
assert!(scan_value(b"]", YsonFormat::Text).is_err());
}
#[test]
fn rejects_deep_nesting() {
let deep = vec![b'['; MAX_DEPTH + 10];
assert!(scan_value(&deep, YsonFormat::Text).is_err());
}
#[test]
fn text_comments_count_toward_the_value() {
assert_eq!(complete(b"/* c */42", YsonFormat::Text), 9);
assert_eq!(complete(b" 42", YsonFormat::Text), 4);
}
}