use std::borrow::Cow;
use std::fmt;
const DEPTH: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct JsonError {
pub(crate) at: usize,
pub(crate) wanted: &'static str,
}
impl fmt::Display for JsonError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} at byte {} of the document", self.wanted, self.at)
}
}
impl std::error::Error for JsonError {}
pub(crate) type Json<T> = Result<T, JsonError>;
#[derive(Debug)]
pub(crate) struct Reader<'a> {
text: &'a str,
at: usize,
depth: usize,
}
impl<'a> Reader<'a> {
#[must_use]
pub(crate) fn new(text: &'a str) -> Self {
Reader { text, at: 0, depth: 0 }
}
pub(crate) fn enter_object(&mut self) -> Json<()> {
self.take(b'{', "an object")
}
pub(crate) fn enter_array(&mut self) -> Json<()> {
self.take(b'[', "an array")
}
pub(crate) fn next_key(&mut self) -> Json<Option<Cow<'a, str>>> {
if !self.more(b'}')? {
return Ok(None);
}
let key = self.string()?;
self.take(b':', "a colon after a key")?;
Ok(Some(key))
}
pub(crate) fn next_item(&mut self) -> Json<bool> {
self.more(b']')
}
pub(crate) fn string(&mut self) -> Json<Cow<'a, str>> {
self.take(b'"', "a string")?;
let from = self.at;
let bytes = self.text.as_bytes();
let mut here = self.at;
while here < bytes.len() && bytes[here] != b'"' && bytes[here] != b'\\' {
here += 1;
}
if here < bytes.len() && bytes[here] == b'"' {
self.at = here + 1;
return Ok(Cow::Borrowed(&self.text[from..here]));
}
self.at = here;
self.escaped(from)
}
pub(crate) fn integer(&mut self) -> Json<u64> {
self.white();
let from = self.at;
let bytes = self.text.as_bytes();
while self.at < bytes.len() && bytes[self.at].is_ascii_digit() {
self.at += 1;
}
if self.at == from {
return Err(self.wanted("a whole number"));
}
self.text[from..self.at].parse().map_err(|_| JsonError {
at: from,
wanted: "a number this size fits in sixty four bits",
})
}
pub(crate) fn skip(&mut self) -> Json<()> {
self.white();
let Some(byte) = self.peek() else {
return Err(self.wanted("a value"));
};
match byte {
b'{' => self.skip_nested(b'{'),
b'[' => self.skip_nested(b'['),
b'"' => self.string().map(|_| ()),
_ => {
let from = self.at;
let bytes = self.text.as_bytes();
while self.at < bytes.len() && !matches!(bytes[self.at], b',' | b'}' | b']') {
self.at += 1;
}
if self.at == from { Err(self.wanted("a value")) } else { Ok(()) }
}
}
}
fn skip_nested(&mut self, open: u8) -> Json<()> {
if self.depth >= DEPTH {
return Err(self.wanted("a document nested less deeply than this one"));
}
self.depth += 1;
let close = if open == b'{' { b'}' } else { b']' };
self.take(open, "a value")?;
while self.more(close)? {
if close == b'}' {
self.string()?;
self.take(b':', "a colon after a key")?;
}
self.skip()?;
}
self.depth -= 1;
Ok(())
}
fn escaped(&mut self, from: usize) -> Json<Cow<'a, str>> {
let mut out = String::from(&self.text[from..self.at]);
let bytes = self.text.as_bytes();
while self.at < bytes.len() {
match bytes[self.at] {
b'"' => {
self.at += 1;
return Ok(Cow::Owned(out));
}
b'\\' => {
self.at += 1;
let Some(what) = self.peek() else { break };
self.at += 1;
match what {
b'"' => out.push('"'),
b'\\' => out.push('\\'),
b'/' => out.push('/'),
b'b' => out.push('\u{8}'),
b'f' => out.push('\u{c}'),
b'n' => out.push('\n'),
b'r' => out.push('\r'),
b't' => out.push('\t'),
b'u' => out.push(self.unicode()?),
_ => return Err(self.wanted("an escape this reader knows")),
}
}
_ => {
let rest = &self.text[self.at..];
let ch = rest.chars().next().unwrap_or('\0');
out.push(ch);
self.at += ch.len_utf8();
}
}
}
Err(self.wanted("a closing quote"))
}
fn unicode(&mut self) -> Json<char> {
let first = self.hex()?;
if (0xd800..0xdc00).contains(&first) {
if self.peek() != Some(b'\\') {
return Err(self.wanted("the second half of a surrogate pair"));
}
self.at += 1;
if self.peek() != Some(b'u') {
return Err(self.wanted("the second half of a surrogate pair"));
}
self.at += 1;
let second = self.hex()?;
if !(0xdc00..0xe000).contains(&second) {
return Err(self.wanted("the second half of a surrogate pair"));
}
let joined = 0x1_0000 + ((first - 0xd800) << 10) + (second - 0xdc00);
return char::from_u32(joined).ok_or_else(|| self.wanted("a character"));
}
char::from_u32(first).ok_or_else(|| self.wanted("a character"))
}
fn hex(&mut self) -> Json<u32> {
let from = self.at;
let bytes = self.text.as_bytes();
if from + 4 > bytes.len() {
return Err(self.wanted("four hex digits"));
}
let mut value = 0u32;
for byte in &bytes[from..from + 4] {
let digit = (*byte as char)
.to_digit(16)
.ok_or(JsonError { at: from, wanted: "four hex digits" })?;
value = value * 16 + digit;
}
self.at = from + 4;
Ok(value)
}
fn more(&mut self, close: u8) -> Json<bool> {
self.white();
match self.peek() {
Some(byte) if byte == close => {
self.at += 1;
Ok(false)
}
Some(b',') => {
self.at += 1;
self.white();
if self.peek() == Some(close) {
self.at += 1;
return Ok(false);
}
Ok(true)
}
Some(_) => Ok(true),
None => Err(self.wanted("the end of the object or array this is inside")),
}
}
fn take(&mut self, byte: u8, wanted: &'static str) -> Json<()> {
self.white();
if self.peek() == Some(byte) {
self.at += 1;
return Ok(());
}
Err(self.wanted(wanted))
}
fn peek(&self) -> Option<u8> {
self.text.as_bytes().get(self.at).copied()
}
fn white(&mut self) {
let bytes = self.text.as_bytes();
while self.at < bytes.len() && matches!(bytes[self.at], b' ' | b'\t' | b'\n' | b'\r') {
self.at += 1;
}
}
fn wanted(&self, wanted: &'static str) -> JsonError {
JsonError { at: self.at, wanted }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_object_is_walked_key_by_key_and_the_reader_ends_after_it() {
let mut reader = Reader::new(r#"[ { "a" : 1 , "b" : "two" } , "tail" ]"#);
reader.enter_array().expect("an array");
assert!(reader.next_item().expect("the object"));
reader.enter_object().expect("an object");
let mut keys = Vec::new();
while let Some(key) = reader.next_key().expect("a key or the end") {
keys.push(key.into_owned());
reader.skip().expect("a value");
}
assert_eq!(keys, ["a", "b"]);
assert!(reader.next_item().expect("one more"));
assert_eq!(reader.string().expect("a string"), "tail");
assert!(!reader.next_item().expect("the end"));
}
#[test]
fn an_array_is_walked_item_by_item_and_an_empty_one_has_none() {
let mut reader = Reader::new(r#"[10,20,30]"#);
reader.enter_array().expect("an array");
let mut sizes = Vec::new();
while reader.next_item().expect("an item or the end") {
sizes.push(reader.integer().expect("a number"));
}
assert_eq!(sizes, [10, 20, 30]);
let mut reader = Reader::new("[ ]");
reader.enter_array().expect("an array");
assert!(!reader.next_item().expect("the end"));
}
#[test]
fn a_string_with_no_escape_in_it_is_a_slice_of_the_document() {
let mut reader = Reader::new(r#""Microsoft.VC.14.44.17.14.CRT.Headers.base""#);
let read = reader.string().expect("a string");
assert!(matches!(read, Cow::Borrowed(_)), "it was copied");
assert_eq!(read, "Microsoft.VC.14.44.17.14.CRT.Headers.base");
}
#[test]
fn the_escapes_the_manifest_uses_come_out_as_the_characters_they_name() {
let mut reader =
Reader::new(r#""Installers\\Windows SDK Desktop Headers x86-x86_en-us.msi""#);
let read = reader.string().expect("a string");
assert!(matches!(read, Cow::Owned(_)), "it was borrowed and it has an escape in it");
assert_eq!(read, r"Installers\Windows SDK Desktop Headers x86-x86_en-us.msi");
let mut reader = Reader::new(r#""\" \/ \b \f \n \r \t é 😀""#);
assert_eq!(reader.string().expect("a string"), "\" / \u{8} \u{c} \n \r \t é 😀");
}
#[test]
fn a_nesting_deeper_than_the_cap_is_an_error_rather_than_a_stack_overflow() {
let deep = format!("{}{}", "[".repeat(DEPTH + 2), "]".repeat(DEPTH + 2));
let mut reader = Reader::new(&deep);
let why = reader.skip().expect_err("a refusal");
assert!(why.wanted.contains("nested less deeply"), "{why}");
}
#[test]
fn each_thing_that_is_not_there_says_what_was_wanted_and_where() {
let mut reader = Reader::new(" [1]");
let why = reader.enter_object().expect_err("not an object");
assert_eq!(why.wanted, "an object");
assert_eq!(why.at, 2);
let mut reader = Reader::new(r#"{"a" 1}"#);
reader.enter_object().expect("an object");
let why = reader.next_key().expect_err("no colon");
assert_eq!(why.wanted, "a colon after a key");
let mut reader = Reader::new(r#""unterminated"#);
assert_eq!(reader.string().expect_err("no closing quote").wanted, "a closing quote");
let mut reader = Reader::new("-3");
assert_eq!(reader.integer().expect_err("not a whole number").wanted, "a whole number");
}
}