use crate::prelude::*;
use arbitrary::{Arbitrary, Result, Unstructured};
use super::{Mapping, Number, Tag, TaggedValue, Value};
const LEAF_THRESHOLD: usize = 4;
impl<'a> Arbitrary<'a> for Number {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
#[cfg(feature = "lossless-u64")]
let kinds = 3u8;
#[cfg(not(feature = "lossless-u64"))]
let kinds = 2u8;
Ok(match u.int_in_range(0..=kinds - 1)? {
0 => Self::Integer(i64::arbitrary(u)?),
1 => Self::Float(f64::arbitrary(u)?),
#[cfg(feature = "lossless-u64")]
_ => Self::Unsigned(u64::arbitrary(u)? | (1 << 63)),
#[cfg(not(feature = "lossless-u64"))]
_ => Self::Integer(i64::arbitrary(u)?),
})
}
}
impl<'a> Arbitrary<'a> for Tag {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let handle = if bool::arbitrary(u)? { "!" } else { "!!" };
let len = u.int_in_range(1..=12usize)?;
let mut name = String::with_capacity(len + 2);
name.push_str(handle);
for _ in 0..len {
let c = u.choose(&['a', 'b', 'c', 'x', 'y', 'z', 'T', 'N', '_', '-', '0', '9'])?;
name.push(*c);
}
Ok(Self::new(name))
}
}
impl<'a> Arbitrary<'a> for TaggedValue {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let payload = match u.int_in_range(0..=2u8)? {
0 => Value::String(String::arbitrary(u)?),
1 => Value::Sequence(Vec::<Value>::arbitrary(u)?),
_ => Value::Mapping(Mapping::arbitrary(u)?),
};
Ok(Self::new(Tag::arbitrary(u)?, payload))
}
}
impl<'a> Arbitrary<'a> for Mapping {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let mut m = Self::new();
for _ in 0..u.arbitrary_len::<(String, Value)>()? {
let key = String::arbitrary(u)?;
let value = Value::arbitrary(u)?;
let _replaced = m.insert(key, value);
}
Ok(m)
}
}
impl<'a> Arbitrary<'a> for Value {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let variants = if u.len() < LEAF_THRESHOLD { 4u8 } else { 7u8 };
Ok(match u.int_in_range(0..=variants - 1)? {
0 => Self::Null,
1 => Self::Bool(bool::arbitrary(u)?),
2 => Self::Number(Number::arbitrary(u)?),
3 => Self::String(String::arbitrary(u)?),
4 => Self::Sequence(Vec::<Self>::arbitrary(u)?),
5 => Self::Mapping(Mapping::arbitrary(u)?),
_ => Self::Tagged(Box::new(TaggedValue::arbitrary(u)?)),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generated_values_round_trip() {
let mut seed: Vec<u8> = (0..=255u8).cycle().take(4096).collect();
for salt in 0..64u8 {
let by = usize::from(salt) * 7 % seed.len().max(1);
seed.rotate_left(by);
let mut u = Unstructured::new(&seed);
let v = Value::arbitrary(&mut u).expect("generation never fails on 4 KiB");
let text = crate::to_string(&v).expect("every generated Value serialises");
#[cfg(feature = "lossless-u64")]
let cfg = crate::ParserConfig::new().lossless_u64_integers(true);
#[cfg(not(feature = "lossless-u64"))]
let cfg = crate::ParserConfig::new();
let back: Value =
crate::from_str_with_config(&text, &cfg).expect("emitted YAML re-parses");
if !text.to_ascii_lowercase().contains("nan") {
assert_eq!(back, v, "round-trip drift on:\n{text}");
}
}
}
#[test]
fn empty_entropy_yields_a_scalar() {
let mut u = Unstructured::new(&[]);
let v = Value::arbitrary(&mut u).expect("no entropy is still a value");
assert!(
!matches!(v, Value::Sequence(_) | Value::Mapping(_) | Value::Tagged(_)),
"{v:?}"
);
}
}