use std::collections::HashMap;
use structio::{ErrorCode, Options, Value, beve, json, to_beve};
const ATTEMPTS: usize = 2 * json::MAX_DEPTH as usize + 1;
fn json_retried(
doc: &str,
mut read: impl FnMut(&mut json::Parser<'_>) -> Result<(), ErrorCode>,
) -> ErrorCode {
let mut p = json::Parser::new(doc);
let start = p.position();
let mut attempt = |p: &mut json::Parser<'_>| {
let code = read(p).expect_err("the case is meant to fail");
(code, p.position())
};
let first = attempt(&mut p);
for n in 2..=ATTEMPTS {
p.rewind(start);
assert_eq!(attempt(&mut p), first, "attempt {n} on {doc}");
}
first.0
}
fn beve_retried(
doc: &[u8],
mut read: impl FnMut(&mut beve::Reader<'_>) -> Result<(), ErrorCode>,
) -> ErrorCode {
let mut r = beve::Reader::new(doc);
let start = r.position();
let mut attempt = |r: &mut beve::Reader<'_>| {
let code = read(r).expect_err("the case is meant to fail");
(code, r.position())
};
let first = attempt(&mut r);
for n in 2..=ATTEMPTS {
r.rewind(start);
assert_eq!(attempt(&mut r), first, "attempt {n}");
}
first.0
}
fn beve_of(json: &str) -> Vec<u8> {
to_beve(&Value::from_json(json).unwrap())
}
fn retried<T>(json: &str) -> (ErrorCode, ErrorCode)
where
T: Default + for<'de> json::Read<'de> + for<'de> beve::Read<'de>,
{
let text = json_retried(json, |p| json::Read::read(&mut T::default(), p));
let bytes = beve_of(json);
let binary = beve_retried(&bytes, |r| beve::Read::read(&mut T::default(), r));
(text, binary)
}
#[derive(Debug, Default, PartialEq)]
enum Unit {
#[default]
A,
B,
}
structio::unit_enum!(Unit { A, B });
#[derive(Debug, Default, PartialEq)]
struct Ab {
a: u32,
b: u32,
}
structio::object!(Ab { a, b });
#[derive(Debug, Default, PartialEq)]
enum Outer {
#[default]
Empty,
P(Ab),
}
structio::tagged_enum!(Outer { Empty, P(_) });
#[derive(Debug, Default, PartialEq)]
enum Inner {
#[default]
Empty,
V(Ab),
}
structio::tagged_enum!(Inner as tag "kind" { Empty, V(_) });
#[test]
fn an_enum_in_object_form_fails_the_same_way_every_time() {
assert_eq!(
retried::<Unit>(r#"{"Nope":1}"#),
(ErrorCode::UnknownVariant, ErrorCode::UnknownVariant)
);
assert_eq!(
json_retried(r#"{"A":null,"B":null}"#, |p| p.read_enum(&mut Unit::A)),
ErrorCode::ExpectedVariant
);
assert_eq!(
json_retried("{}", |p| p.read_enum(&mut Unit::A)),
ErrorCode::ExpectedVariant
);
assert_eq!(
retried::<Outer>(r#"{"P":{"a":1,"b":"x"}}"#).0,
ErrorCode::ExpectedNumber
);
}
#[test]
fn a_member_or_element_that_fails_releases_every_level_above_it() {
assert_eq!(
retried::<Ab>(r#"{"a":1,"b":"x"}"#).0,
ErrorCode::ExpectedNumber
);
retried::<Vec<Ab>>(r#"[{"a":1,"b":2},{"a":"x"}]"#);
retried::<Vec<Vec<u32>>>(r#"[[1,2],[3,"x"]]"#);
retried::<HashMap<String, Vec<u32>>>(r#"{"k":[1,"x"]}"#);
retried::<Vec<Outer>>(r#"[{"P":{"a":"x"}}]"#);
}
#[test]
fn an_internally_tagged_object_fails_the_same_way_every_time() {
for doc in [
r#"{"kind":"V","a":1,"b":"x"}"#,
r#"{"a":1,"kind":"V","b":"x"}"#,
r#"{"a":"x","kind":"V","b":1}"#,
r#"{"kind":"Nope"}"#,
r#"{"a":1,"kind":"Nope"}"#,
] {
retried::<Inner>(doc);
retried::<Vec<Inner>>(&format!("[{doc}]"));
}
}
#[test]
fn a_value_and_a_skip_fail_the_same_way_every_time() {
let text = r#"{"a":[[1,2],[3,"#;
assert_eq!(
json_retried(text, |p| json::Read::read(&mut Value::Null, p)),
ErrorCode::UnexpectedEnd
);
assert_eq!(
json_retried(text, |p| p.skip_value()),
ErrorCode::UnexpectedEnd
);
let whole = beve_of(r#"{"a":[[1,2],[3,"four"]]}"#);
let cut = &whole[..whole.len() - 2];
assert_eq!(
beve_retried(cut, |r| beve::Read::read(&mut Value::Null, r)),
ErrorCode::UnexpectedEnd
);
assert_eq!(
beve_retried(cut, |r| r.skip_value()),
ErrorCode::UnexpectedEnd
);
}
#[derive(Debug, Default, PartialEq)]
struct Maybe(Option<Inner>);
impl<'de> json::Read<'de> for Maybe {
fn read<O: Options>(&mut self, p: &mut json::Parser<'de, O>) -> Result<(), ErrorCode> {
let at = p.position();
let mut v = Inner::default();
match json::Read::read(&mut v, p) {
Ok(()) => self.0 = Some(v),
Err(_) => {
p.rewind(at);
p.skip_value()?;
self.0 = None;
}
}
Ok(())
}
}
impl<'de> beve::Read<'de> for Maybe {
fn read<O: Options>(&mut self, r: &mut beve::Reader<'de, O>) -> Result<(), ErrorCode> {
let at = r.position();
let mut v = Inner::default();
match beve::Read::read(&mut v, r) {
Ok(()) => self.0 = Some(v),
Err(_) => {
r.rewind(at);
r.skip_value()?;
self.0 = None;
}
}
Ok(())
}
}
#[test]
fn a_late_tag_that_failed_leaves_no_run_for_the_next_object() {
let expected = vec![Maybe(None), Maybe(Some(Inner::V(Ab { a: 2, b: 3 })))];
for first in [r#"{"a":1,"kind":"V","b":"x"}"#, r#"{"a":1,"kind":"Nope"}"#] {
let doc = format!(r#"[{first},{{"kind":"V","a":2,"b":3}}]"#);
assert_eq!(structio::from_str::<Vec<Maybe>>(&doc).unwrap(), expected);
assert_eq!(
structio::from_beve::<Vec<Maybe>>(&beve_of(&doc)).unwrap(),
expected
);
}
}
#[test]
fn the_limit_stays_where_it_was() {
let limit = json::MAX_DEPTH as usize;
let over = format!("{}{}", "[".repeat(limit + 1), "]".repeat(limit + 1));
assert_eq!(
json_retried(&over, |p| p.skip_value()),
ErrorCode::ExceededMaxDepth
);
assert_eq!(
json_retried(&over, |p| json::Read::read(&mut Value::Null, p)),
ErrorCode::ExceededMaxDepth
);
let at = format!("{}x{}", "[".repeat(limit), "]".repeat(limit));
let code = json_retried(&at, |p| json::Read::read(&mut Value::Null, p));
assert_ne!(code, ErrorCode::ExceededMaxDepth);
}