contrafact 0.2.0-rc.1

A trait for highly composable constraints ("facts") which can be used both to verify data and to generate arbitrary data within those constraints
Documentation
use arbitrary::*;
use contrafact::{facts::*, utils::random_generator, *};

#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, std::hash::Hash)]
enum Color {
    Cyan,
    Magenta,
    Yellow,
    Black,
}

#[derive(Arbitrary, Debug, PartialEq, Clone)]
struct Link {
    prev: u32,
    author: String,
}

#[derive(Arbitrary, Debug, PartialEq, Clone)]
struct Wrapper {
    color: Color,
    link: Link,
}

/// Fact: all Links in a chain are by the same `author`, and any chain link has
/// consecutive `prev` values starting with 0.
fn chain_fact<'a>(author: String) -> impl Fact<'a, Link> {
    facts![
        lens1("Link::author", |o: &mut Link| &mut o.author, eq(author),),
        lens1(
            "Link::prev",
            |o: &mut Link| &mut o.prev,
            consecutive_int("increasing prev", 0),
        ),
    ]
}

/// Fact: the Links within each wrapper form a valid chain, and the color
/// of the wrapper is in the given set.
fn wrapper_fact<'a>(author: String, valid_colors: &'a [Color]) -> impl Fact<'a, Wrapper> {
    facts![
        lens1(
            "Wrapper::color",
            |o: &mut Wrapper| &mut o.color,
            in_slice("valid color", valid_colors),
        ),
        lens1(
            "Wrapper::link",
            |o: &mut Wrapper| &mut o.link,
            chain_fact(author),
        ),
    ]
}

#[test]
fn test_link() {
    observability::test_run().ok();
    let mut g = random_generator();

    const NUM: u32 = 10;
    let author = "alice".to_string();
    let fact = move || vec_of_length(NUM as usize, chain_fact(author.clone()));

    let chain = fact().build(&mut g);
    dbg!(&chain);
    fact().check(&chain).unwrap();

    assert!(chain.iter().all(|c| c.author == "alice"));
    assert_eq!(chain.iter().last().unwrap().prev, NUM - 1);
}

#[test]
fn test_wrapper() {
    observability::test_run().ok();
    let mut g = random_generator();

    const NUM: u32 = 10;
    let author = "alice".to_string();
    let fact = move || {
        vec_of_length(
            NUM as usize,
            wrapper_fact(author.clone(), &[Color::Cyan, Color::Magenta]),
        )
    };

    let chain = fact().build(&mut g);
    dbg!(&chain);
    fact().check(&chain).unwrap();

    assert!(chain.iter().all(|c| c.link.author == "alice"));
    assert!(chain.iter().all(|c| c.color != Color::Black));
    assert_eq!(chain.iter().last().unwrap().link.prev, NUM - 1);

    // there is a high probability that this will be true
    assert!(chain.iter().any(|c| c.color == Color::Magenta));
}