structdiff 0.7.3

zero-dependency crate for generating and applying partial diffs between struct instances
Documentation
use std::collections::BTreeMap;

use generators::{fill, rand_bool, rand_string};
use nanorand::{Rng, WyRand};
#[cfg(feature = "nanoserde")]
use nanoserde::{DeBin, SerBin};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use structdiff::{Difference, StructDiff};

pub trait RandValue
where
    Self: Sized,
{
    fn next() -> Self {
        let mut rng = WyRand::new();
        Self::next_seeded(&mut rng)
    }

    fn next_seeded(rng: &mut WyRand) -> Self;
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "nanoserde", derive(SerBin, DeBin))]
#[derive(Debug, PartialEq, Clone, Difference, Default)]
#[difference(setters)]
pub struct Test {
    pub test1: i32,
    pub test2: String,
    pub test3: Vec<i32>,
    pub test4: f32,
    pub test5: Option<usize>,
}

#[derive(Debug, PartialEq, Clone, Difference)]
#[cfg_attr(feature = "nanoserde", derive(SerBin, DeBin))]
#[difference(setters)]
pub struct TestSkip<A>
where
    A: PartialEq,
{
    pub test1: A,
    pub test2: String,
    #[difference(skip)]
    pub test3skip: Vec<i32>,
    pub test4: f32,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "nanoserde", derive(SerBin, DeBin))]
#[derive(Debug, PartialEq, Clone, Difference, Default)]
pub enum TestEnum {
    #[default]
    F0,
    F1(bool),
    F2(String),
    F3 {
        field1: String,
        field2: bool,
    },
    F4(Test),
}

impl RandValue for Test {
    fn next_seeded(rng: &mut WyRand) -> Self {
        Test {
            test1: rng.generate(),
            test2: rand_string(rng),
            test3: fill(rng),
            test4: match f32::from_bits(rng.generate::<u32>()) {
                val if val.is_nan() => 0.0,
                val => val,
            },
            test5: match rng.generate::<bool>() {
                true => Some(rng.generate()),
                false => None,
            },
        }
    }
}

impl RandValue for TestEnum {
    fn next_seeded(rng: &mut WyRand) -> Self {
        match rng.generate_range(0..5) {
            0 => Self::F0,
            1 => Self::F1(rand_bool(rng)),
            2 => Self::F2(rand_string(rng)),
            3 => Self::F3 {
                field1: rand_string(rng),
                field2: rand_bool(rng),
            },
            _ => Self::F4(Test::next()),
        }
    }
}

#[derive(Difference, Default, PartialEq, Debug, Clone)]
#[difference(setters)]
pub struct TestSetters {
    #[difference(setter_name = "testing123", recurse)]
    pub f0: Test,
    pub f1: Test,
    pub f2: TestEnum,
    #[difference(recurse)]
    pub f3: Option<Test>,
    #[difference(collection_strategy = "unordered_array_like")]
    pub f4: Vec<i32>,
    #[difference(collection_strategy = "unordered_map_like", map_equality = "key_only")]
    pub f5: BTreeMap<i32, Test>,
    #[difference(
        collection_strategy = "unordered_map_like",
        map_equality = "key_and_value"
    )]
    pub f6: BTreeMap<i32, Test>,
}

impl RandValue for TestSetters {
    fn next_seeded(rng: &mut WyRand) -> Self {
        TestSetters {
            f0: Test::next(),
            f1: Test::next(),
            f2: TestEnum::next(),
            f3: if rng.generate::<bool>() {
                Some(Test::next_seeded(rng))
            } else {
                None
            },
            f4: generators::fill(rng),
            f5: generators::fill::<i32, Vec<i32>>(rng)
                .into_iter()
                .map(|x| (x, Test::next_seeded(rng)))
                .take(10)
                .collect(),
            f6: generators::fill::<i32, Vec<i32>>(rng)
                .into_iter()
                .map(|x| (x, Test::next_seeded(rng)))
                .take(10)
                .collect(),
        }
    }
}

mod generators {
    use nanorand::{Rng, WyRand};

    pub(super) fn rand_bool(rng: &mut WyRand) -> bool {
        let base = rng.generate::<u8>() as usize;
        base % 2 == 0
    }

    pub(super) fn rand_string(rng: &mut WyRand) -> String {
        let base = vec![(); rng.generate::<u8>() as usize];
        base.into_iter()
            .map(|_| rng.generate::<u8>() as u32)
            .filter_map(char::from_u32)
            .collect::<String>()
    }

    pub(super) fn fill<V, T>(rng: &mut WyRand) -> T
    where
        V: nanorand::RandomGen<nanorand::WyRand, 8>,
        T: FromIterator<V>,
    {
        let base = vec![(); rng.generate::<u8>() as usize];
        base.into_iter().map(|_| rng.generate::<V>()).collect::<T>()
    }
}