effect-rs 0.1.0

A high-performance, strictly-typed, functional effect system for Rust.
Documentation
use effect_rs::{Effect, Exit, Ref, Runtime, Schedule};
use std::time::Duration;

#[test]
fn test_schedule_exponential() {
    let rt = Runtime::new();
    let counter = Ref::new(0);

    // Fail 3 times, succeed on 4th.
    let program = counter
        .update(|x| x + 1)
        .map_error(|_| "err")
        .flat_map(|val| {
            if val < 4 {
                Effect::fail("error")
            } else {
                Effect::succeed(val)
            }
        })
        .retry(Schedule::<&str, Duration>::exponential(
            Duration::from_millis(10),
            2.0,
        ));

    let result = rt.block_on(program, ());
    match result {
        Exit::Success(val) => assert_eq!(val, 4),
        Exit::Failure(_) => panic!("Expected success"),
    }
}

#[test]
fn test_schedule_jitter() {
    let rt = Runtime::new();
    let counter = Ref::new(0);

    let program = counter
        .update(|x| x + 1)
        .map_error(|_| "err")
        .flat_map(|val| {
            if val < 4 {
                Effect::fail("error")
            } else {
                Effect::succeed(val)
            }
        })
        .retry(Schedule::<&str, ()>::spaced(Duration::from_millis(50)).jittered(0.0, 0.1));

    let result = rt.block_on(program, ());
    match result {
        Exit::Success(val) => assert_eq!(val, 4),
        Exit::Failure(_) => panic!("Expected success"),
    }
}

#[test]
fn test_schedule_intersect() {
    let rt = Runtime::new();
    let counter = Ref::new(0);

    // recurs(3) (stops after 3 retries, total 4 runs) AND spaced(1ms)
    // Should stop after 3 replays (total runs 4).
    let program = counter
        .update(|x| x + 1)
        .map_error(|_| "err")
        .flat_map(|val| {
            if val <= 10 {
                // Always fail
                Effect::fail("error")
            } else {
                Effect::succeed(val)
            }
        })
        .retry(
            Schedule::<&str, usize>::recurs(3)
                .intersect(Schedule::<&str, ()>::spaced(Duration::from_millis(1))),
        );

    let result = rt.block_on(program, ());
    match result {
        Exit::Failure(_) => {
            // Verify count is 4 (initial + 3 retries)
            let c = rt.block_on(counter.get(), ());
            match c {
                Exit::Success(val) => assert_eq!(val, 4),
                _ => panic!("Failed to get counter"),
            }
        }
        Exit::Success(_) => panic!("Expected failure after 3 retries"),
    }
}

#[test]
fn test_schedule_union() {
    let rt = Runtime::new();
    let counter = Ref::new(0);

    // recurs(2) || recurs(4). Should continue until 4 retries.
    let program = counter
        .update(|x| x + 1)
        .map_error(|_| "err")
        .flat_map(|val| {
            if val <= 10 {
                // Always fail
                Effect::fail("error")
            } else {
                Effect::succeed(val)
            }
        })
        .retry(Schedule::<&str, usize>::recurs(2).union(Schedule::<&str, usize>::recurs(4)));

    let result = rt.block_on(program, ());
    match result {
        Exit::Failure(_) => {
            // 4 retries + 1 initial = 5 runs.
            let c = rt.block_on(counter.get(), ());
            match c {
                Exit::Success(val) => assert_eq!(val, 5),
                _ => panic!("Failed to get counter"),
            }
        }
        Exit::Success(_) => panic!("Expected failure"),
    }
}