noyalib/value/
arbitrary_impls.rs1use crate::prelude::*;
24use arbitrary::{Arbitrary, Result, Unstructured};
25
26use super::{Mapping, Number, Tag, TaggedValue, Value};
27
28const LEAF_THRESHOLD: usize = 4;
31
32impl<'a> Arbitrary<'a> for Number {
33 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
34 #[cfg(feature = "lossless-u64")]
35 let kinds = 3u8;
36 #[cfg(not(feature = "lossless-u64"))]
37 let kinds = 2u8;
38 Ok(match u.int_in_range(0..=kinds - 1)? {
39 0 => Self::Integer(i64::arbitrary(u)?),
40 1 => Self::Float(f64::arbitrary(u)?),
41 #[cfg(feature = "lossless-u64")]
44 _ => Self::Unsigned(u64::arbitrary(u)? | (1 << 63)),
45 #[cfg(not(feature = "lossless-u64"))]
46 _ => Self::Integer(i64::arbitrary(u)?),
47 })
48 }
49}
50
51impl<'a> Arbitrary<'a> for Tag {
52 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
56 let handle = if bool::arbitrary(u)? { "!" } else { "!!" };
57 let len = u.int_in_range(1..=12usize)?;
58 let mut name = String::with_capacity(len + 2);
59 name.push_str(handle);
60 for _ in 0..len {
61 let c = u.choose(&['a', 'b', 'c', 'x', 'y', 'z', 'T', 'N', '_', '-', '0', '9'])?;
62 name.push(*c);
63 }
64 Ok(Self::new(name))
65 }
66}
67
68impl<'a> Arbitrary<'a> for TaggedValue {
69 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
75 let payload = match u.int_in_range(0..=2u8)? {
76 0 => Value::String(String::arbitrary(u)?),
77 1 => Value::Sequence(Vec::<Value>::arbitrary(u)?),
78 _ => Value::Mapping(Mapping::arbitrary(u)?),
79 };
80 Ok(Self::new(Tag::arbitrary(u)?, payload))
81 }
82}
83
84impl<'a> Arbitrary<'a> for Mapping {
85 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
86 let mut m = Self::new();
87 for _ in 0..u.arbitrary_len::<(String, Value)>()? {
88 let key = String::arbitrary(u)?;
89 let value = Value::arbitrary(u)?;
90 let _replaced = m.insert(key, value);
91 }
92 Ok(m)
93 }
94}
95
96impl<'a> Arbitrary<'a> for Value {
97 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
98 let variants = if u.len() < LEAF_THRESHOLD { 4u8 } else { 7u8 };
99 Ok(match u.int_in_range(0..=variants - 1)? {
100 0 => Self::Null,
101 1 => Self::Bool(bool::arbitrary(u)?),
102 2 => Self::Number(Number::arbitrary(u)?),
103 3 => Self::String(String::arbitrary(u)?),
104 4 => Self::Sequence(Vec::<Self>::arbitrary(u)?),
105 5 => Self::Mapping(Mapping::arbitrary(u)?),
106 _ => Self::Tagged(Box::new(TaggedValue::arbitrary(u)?)),
107 })
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
118 fn generated_values_round_trip() {
119 let mut seed: Vec<u8> = (0..=255u8).cycle().take(4096).collect();
120 for salt in 0..64u8 {
121 let by = usize::from(salt) * 7 % seed.len().max(1);
122 seed.rotate_left(by);
123 let mut u = Unstructured::new(&seed);
124 let v = Value::arbitrary(&mut u).expect("generation never fails on 4 KiB");
125 let text = crate::to_string(&v).expect("every generated Value serialises");
126 #[cfg(feature = "lossless-u64")]
130 let cfg = crate::ParserConfig::new().lossless_u64_integers(true);
131 #[cfg(not(feature = "lossless-u64"))]
132 let cfg = crate::ParserConfig::new();
133 let back: Value =
134 crate::from_str_with_config(&text, &cfg).expect("emitted YAML re-parses");
135 if !text.to_ascii_lowercase().contains("nan") {
136 assert_eq!(back, v, "round-trip drift on:\n{text}");
137 }
138 }
139 }
140
141 #[test]
142 fn empty_entropy_yields_a_scalar() {
143 let mut u = Unstructured::new(&[]);
144 let v = Value::arbitrary(&mut u).expect("no entropy is still a value");
145 assert!(
146 !matches!(v, Value::Sequence(_) | Value::Mapping(_) | Value::Tagged(_)),
147 "{v:?}"
148 );
149 }
150}