use crate::effect::Effect;
pub struct Ensure<E, P, Err> {
pub(crate) inner: E,
pub(crate) predicate: P,
pub(crate) error: Err,
}
impl<E, P, Err> std::fmt::Debug for Ensure<E, P, Err> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Ensure")
.field("inner", &"<effect>")
.field("predicate", &"<function>")
.field("error", &"<error>")
.finish()
}
}
impl<E, P, Err> Ensure<E, P, Err> {
pub fn new(inner: E, predicate: P, error: Err) -> Self {
Ensure {
inner,
predicate,
error,
}
}
}
impl<E, P, Err> Effect for Ensure<E, P, Err>
where
E: Effect,
P: FnOnce(&E::Output) -> bool + Send,
Err: Into<E::Error> + Send,
{
type Output = E::Output;
type Error = E::Error;
type Env = E::Env;
async fn run(self, env: &Self::Env) -> Result<Self::Output, Self::Error> {
let value = self.inner.run(env).await?;
if (self.predicate)(&value) {
Ok(value)
} else {
Err(self.error.into())
}
}
}
#[cfg(test)]
mod tests {
use crate::effect::constructors::{fail, pure};
use crate::effect::EffectExt;
#[tokio::test]
async fn test_ensure_passes_when_true() {
let effect = pure::<_, String, ()>(5).ensure(|x| *x > 0, "must be positive".to_string());
assert_eq!(effect.execute(&()).await, Ok(5));
}
#[tokio::test]
async fn test_ensure_fails_when_false() {
let effect = pure::<_, String, ()>(-5).ensure(|x| *x > 0, "must be positive".to_string());
assert_eq!(
effect.execute(&()).await,
Err("must be positive".to_string())
);
}
#[tokio::test]
async fn test_ensure_short_circuits_on_prior_error() {
let effect = fail::<i32, _, ()>("prior error".to_string())
.ensure(|_| panic!("should not be called"), "other".to_string());
assert_eq!(effect.execute(&()).await, Err("prior error".to_string()));
}
#[tokio::test]
async fn test_chained_ensures() {
let effect = pure::<_, String, ()>(50)
.ensure(|x| *x > 0, "must be positive".to_string())
.ensure(|x| *x < 100, "must be less than 100".to_string())
.ensure(|x| *x % 2 == 0, "must be even".to_string());
assert_eq!(effect.execute(&()).await, Ok(50));
}
#[tokio::test]
async fn test_chained_ensures_first_fails() {
let effect = pure::<_, String, ()>(-5)
.ensure(|x| *x > 0, "must be positive".to_string())
.ensure(|_| panic!("should not reach"), "other".to_string());
assert_eq!(
effect.execute(&()).await,
Err("must be positive".to_string())
);
}
#[tokio::test]
async fn test_ensure_with_map() {
let effect = pure::<_, String, ()>(5)
.map(|x| x * 2)
.ensure(|x| *x > 5, "must be greater than 5".to_string())
.map(|x| x + 1);
assert_eq!(effect.execute(&()).await, Ok(11));
}
#[tokio::test]
async fn test_ensure_with_and_then() {
let effect = pure::<_, String, ()>(5)
.ensure(|x| *x > 0, "must be positive".to_string())
.and_then(|x| pure(x * 2));
assert_eq!(effect.execute(&()).await, Ok(10));
}
}