use crate::{BoxedEffect, Semigroup, Validation};
pub fn traverse<T, U, E, F, I>(iter: I, f: F) -> Validation<Vec<U>, E>
where
I: IntoIterator<Item = T>,
F: Fn(T) -> Validation<U, E>,
E: Semigroup,
{
let validations: Vec<_> = iter.into_iter().map(f).collect();
Validation::all_vec(validations)
}
pub fn sequence<T, E, I>(iter: I) -> Validation<Vec<T>, E>
where
I: IntoIterator<Item = Validation<T, E>>,
E: Semigroup,
{
Validation::all_vec(iter.into_iter().collect())
}
pub fn traverse_effect<T, U, E, Env, F, I>(iter: I, f: F) -> BoxedEffect<Vec<U>, E, Env>
where
I: IntoIterator<Item = T>,
F: Fn(T) -> BoxedEffect<U, E, Env> + Clone + Send + 'static,
T: Send + 'static,
U: Send + 'static,
E: Send + 'static,
Env: Clone + Send + Sync + 'static,
{
use crate::effect::prelude::*;
let items: Vec<_> = iter.into_iter().collect();
let effects: Vec<BoxedEffect<U, E, Env>> = items.into_iter().map(f).collect();
from_async(move |env: &Env| {
let env = env.clone();
async move { par_try_all(effects, &env).await }
})
.boxed()
}
pub fn sequence_effect<T, E, Env, I>(iter: I) -> BoxedEffect<Vec<T>, E, Env>
where
I: IntoIterator<Item = BoxedEffect<T, E, Env>> + Send + 'static,
I::IntoIter: Send,
T: Send + 'static,
E: Send + 'static,
Env: Clone + Send + Sync + 'static,
{
use crate::effect::prelude::*;
let effects: Vec<BoxedEffect<T, E, Env>> = iter.into_iter().collect();
from_async(move |env: &Env| {
let env = env.clone();
async move { par_try_all(effects, &env).await }
})
.boxed()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_traverse_all_success() {
fn validate_positive(x: i32) -> Validation<i32, Vec<String>> {
if x > 0 {
Validation::success(x)
} else {
Validation::failure(vec![format!("{} is not positive", x)])
}
}
let result = traverse(vec![1, 2, 3], validate_positive);
assert_eq!(result, Validation::Success(vec![1, 2, 3]));
}
#[test]
fn test_traverse_with_failures() {
fn validate_positive(x: i32) -> Validation<i32, Vec<String>> {
if x > 0 {
Validation::success(x)
} else {
Validation::failure(vec![format!("{} is not positive", x)])
}
}
let result = traverse(vec![1, -2, -3], validate_positive);
assert!(result.is_failure());
match result {
Validation::Failure(errors) => {
assert_eq!(errors.len(), 2);
}
_ => panic!("Expected failure"),
}
}
#[test]
fn test_traverse_empty() {
fn validate_positive(x: i32) -> Validation<i32, Vec<String>> {
if x > 0 {
Validation::success(x)
} else {
Validation::failure(vec![format!("{} is not positive", x)])
}
}
let result = traverse(Vec::<i32>::new(), validate_positive);
assert_eq!(result, Validation::Success(vec![]));
}
#[test]
fn test_sequence_all_success() {
let vals = vec![
Validation::<_, Vec<String>>::success(1),
Validation::success(2),
Validation::success(3),
];
let result = sequence(vals);
assert_eq!(result, Validation::Success(vec![1, 2, 3]));
}
#[test]
fn test_sequence_with_failures() {
let vals = vec![
Validation::<i32, _>::failure(vec!["error1".to_string()]),
Validation::success(2),
Validation::failure(vec!["error2".to_string()]),
];
let result = sequence(vals);
assert!(result.is_failure());
}
#[test]
fn test_sequence_empty() {
let vals: Vec<Validation<i32, Vec<String>>> = vec![];
let result = sequence(vals);
assert_eq!(result, Validation::Success(vec![]));
}
#[tokio::test]
async fn test_traverse_effect_all_success() {
use crate::effect::prelude::*;
fn double(x: i32) -> BoxedEffect<i32, String, ()> {
pure(x * 2).boxed()
}
let result = traverse_effect(vec![1, 2, 3], double);
assert_eq!(result.run(&()).await, Ok(vec![2, 4, 6]));
}
#[tokio::test]
async fn test_traverse_effect_with_failure() {
use crate::effect::prelude::*;
fn check_positive(x: i32) -> BoxedEffect<i32, String, ()> {
if x > 0 {
pure(x).boxed()
} else {
fail(format!("{} is not positive", x)).boxed()
}
}
let result = traverse_effect(vec![1, -2, 3], check_positive);
assert!(result.run(&()).await.is_err());
}
#[tokio::test]
async fn test_traverse_effect_empty() {
use crate::effect::prelude::*;
fn double(x: i32) -> BoxedEffect<i32, String, ()> {
pure(x * 2).boxed()
}
let result = traverse_effect(Vec::<i32>::new(), double);
assert_eq!(result.run(&()).await, Ok(vec![]));
}
#[tokio::test]
async fn test_sequence_effect_all_success() {
use crate::effect::prelude::*;
let effects = vec![
pure::<_, String, ()>(1).boxed(),
pure(2).boxed(),
pure(3).boxed(),
];
let result = sequence_effect(effects);
assert_eq!(result.run(&()).await, Ok(vec![1, 2, 3]));
}
#[tokio::test]
async fn test_sequence_effect_with_failure() {
use crate::effect::prelude::*;
let effects = vec![
pure::<_, String, ()>(1).boxed(),
fail("error".to_string()).boxed(),
pure(3).boxed(),
];
let result = sequence_effect(effects);
assert!(result.run(&()).await.is_err());
}
#[tokio::test]
async fn test_sequence_effect_empty() {
use crate::effect::prelude::*;
let effects: Vec<BoxedEffect<i32, String, ()>> = vec![];
let result = sequence_effect(effects);
assert_eq!(result.run(&()).await, Ok(vec![]));
}
#[test]
fn test_traverse_with_parse() {
fn parse_number(s: &str) -> Validation<i32, Vec<String>> {
s.parse()
.map(Validation::success)
.unwrap_or_else(|_| Validation::failure(vec![format!("Invalid number: {}", s)]))
}
let numbers = vec!["1", "2", "3"];
let result = traverse(numbers, parse_number);
assert_eq!(result, Validation::Success(vec![1, 2, 3]));
let mixed = vec!["1", "invalid", "3"];
let result = traverse(mixed, parse_number);
assert!(result.is_failure());
}
#[tokio::test]
async fn test_traverse_effect_with_env() {
use crate::effect::prelude::*;
#[derive(Clone)]
struct Env {
multiplier: i32,
}
fn multiply(x: i32) -> BoxedEffect<i32, String, Env> {
asks(move |env: &Env| x * env.multiplier).boxed()
}
let env = Env { multiplier: 3 };
let result = traverse_effect(vec![1, 2, 3], multiply);
assert_eq!(result.run(&env).await, Ok(vec![3, 6, 9]));
}
}