use crate::expr::Seg;
pub use dataflow_corpus::Rng;
use datavalue::OwnedDataValue;
pub const KEYS: &[&str] = &["a", "b", "c", "0", "1", "20", "#h", ""];
pub const WILD: &[&str] = &[
"a", "x", "0", "1", "2", "5", "20", "#20", "#0", "##h", "#h", "#a", "01", "+1", "",
];
pub fn scalar(rng: &mut Rng) -> OwnedDataValue {
match rng.below(5) {
0 => OwnedDataValue::Null,
1 => OwnedDataValue::Bool(rng.chance(1, 2)),
2 => OwnedDataValue::from(rng.below(100) as i64),
3 => OwnedDataValue::from(rng.below(1000) as f64 / 8.0),
_ => OwnedDataValue::String(format!("s{}", rng.below(50))),
}
}
pub fn value_at(rng: &mut Rng, depth: usize) -> OwnedDataValue {
if depth == 0 || rng.chance(2, 5) {
return scalar(rng);
}
if rng.chance(1, 2) {
let n = rng.below(5);
OwnedDataValue::Array((0..n).map(|_| value_at(rng, depth - 1)).collect())
} else {
object(rng, depth)
}
}
fn object(rng: &mut Rng, depth: usize) -> OwnedDataValue {
let mut pairs: Vec<(String, OwnedDataValue)> = Vec::new();
for _ in 0..rng.below(5) {
let key = rng.pick(KEYS).to_string();
if !pairs.iter().any(|(k, _)| *k == key) {
pairs.push((key, value_at(rng, depth - 1)));
}
}
OwnedDataValue::Object(pairs)
}
pub fn tree(rng: &mut Rng, depth: usize) -> OwnedDataValue {
object(rng, depth.max(1))
}
pub fn value(rng: &mut Rng) -> OwnedDataValue {
value_at(rng, 2)
}
pub fn path(rng: &mut Rng, tree: &OwnedDataValue) -> String {
if rng.chance(1, 200) {
return String::new();
}
walk(rng, tree)
}
fn walk(rng: &mut Rng, tree: &OwnedDataValue) -> String {
let len = 1 + rng.below(5);
let mut segs: Vec<String> = Vec::with_capacity(len);
let mut at = Some(tree);
for _ in 0..len {
let from_tree = rng.chance(3, 4);
let seg = match (at, from_tree) {
(Some(OwnedDataValue::Object(pairs)), true) if !pairs.is_empty() => {
let (key, child) = &pairs[rng.below(pairs.len())];
at = Some(child);
if key.starts_with('#') || rng.chance(1, 4) {
format!("#{key}")
} else {
key.clone()
}
}
(Some(OwnedDataValue::Array(items)), true) if !items.is_empty() => {
let i = rng.below(items.len() + 1);
at = items.get(i);
i.to_string()
}
_ => {
at = None;
rng.pick(WILD).to_string()
}
};
segs.push(seg);
}
segs.join(".")
}
pub fn exact(v: &OwnedDataValue) -> String {
serde_json::to_string(v).expect("a value serializes")
}
pub fn diff(a: &str, b: &str) -> String {
let at = a
.bytes()
.zip(b.bytes())
.position(|(x, y)| x != y)
.unwrap_or(a.len().min(b.len()));
let from = at.saturating_sub(120);
let clip = |s: &str| {
s.get(from..(at + 120).min(s.len()))
.unwrap_or("")
.to_string()
};
format!("at byte {at}:\n got …{}…\n want …{}…", clip(a), clip(b))
}
pub const ROOTS: [&str; 3] = ["data", "metadata", "temp_data"];
pub fn rooted_tree(rng: &mut Rng, depth: usize) -> OwnedDataValue {
OwnedDataValue::Object(
ROOTS
.iter()
.map(|root| ((*root).to_string(), object(rng, depth.max(2))))
.collect(),
)
}
pub fn rooted_path(rng: &mut Rng, ctx: &OwnedDataValue) -> String {
let root = *rng.pick(&ROOTS);
if rng.chance(1, 50) {
return root.to_string();
}
let below = match ctx {
OwnedDataValue::Object(pairs) => pairs.iter().find(|(k, _)| k == root).map(|(_, v)| v),
_ => None,
};
let rest = match below {
Some(subtree) => walk(rng, subtree),
None => walk(rng, &OwnedDataValue::Null),
};
format!("{root}.{rest}")
}
pub fn segs(dotted: &str) -> Vec<Seg> {
if dotted.is_empty() {
Vec::new()
} else {
dotted.split('.').map(Seg::parse).collect()
}
}
pub fn reference_merge_root(context: &mut OwnedDataValue, path: &str, new_value: OwnedDataValue) {
let OwnedDataValue::Object(ctx_pairs) = context else {
*context = OwnedDataValue::Object(vec![(path.to_string(), new_value)]);
return;
};
let slot_idx = ctx_pairs.iter().position(|(k, _)| k == path);
match slot_idx {
Some(idx) => {
let slot = &mut ctx_pairs[idx].1;
match (slot, new_value) {
(OwnedDataValue::Object(existing), OwnedDataValue::Object(new_pairs)) => {
for (k, v) in new_pairs {
if let Some(s) = existing.iter_mut().find(|(ek, _)| ek == &k) {
s.1 = v;
} else {
existing.push((k, v));
}
}
}
(slot, new) => *slot = new,
}
}
None => ctx_pairs.push((path.to_string(), new_value)),
}
}
pub fn seed(default: u64) -> u64 {
std::env::var("DATAFLOW_PROPTEST_SEED")
.ok()
.and_then(|raw| {
let trimmed = raw.trim();
if let Some(hex) = trimmed
.strip_prefix("0x")
.or_else(|| trimmed.strip_prefix("0X"))
{
u64::from_str_radix(hex, 16).ok()
} else {
trimmed.parse::<u64>().ok()
}
})
.unwrap_or(default)
}
pub fn deep() -> bool {
std::env::var_os("DATAFLOW_DEEP").is_some() || std::env::var_os("DATAFLOW_V4_DEEP").is_some()
}