use std::future::Future;
use super::operation::cancellable as interruptible;
use crate::{Cancellation, Error};
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) enum Phase {
Before,
Call,
After,
Complete,
}
pub(crate) struct Methods<B, C, A, K> {
pub before: B,
pub call: C,
pub after: A,
pub commit: K,
}
pub(crate) async fn advance<I, O, E, B, BF, C, CF, A, AF, K, KF>(
from: Phase,
until: Phase,
mut input: I,
mut output: Option<O>,
mut methods: Methods<B, C, A, K>,
cancellation: &Cancellation,
) -> Result<(I, Option<O>), E>
where
I: Clone,
E: From<Error>,
B: FnMut(I) -> BF,
BF: Future<Output = Result<I, E>>,
C: FnMut(I) -> CF,
CF: Future<Output = Result<O, E>>,
A: FnMut(I, O) -> AF,
AF: Future<Output = Result<O, E>>,
K: FnMut(Phase, &I, Option<&O>) -> KF,
KF: Future<Output = Result<(), E>>,
{
if from > until || (from >= Phase::After) != output.is_some() {
return Err(Error::new("INVALID_STATE", "invalid lifecycle phase or result").into());
}
check(cancellation)?;
let mut phase = from;
while phase < until {
check(cancellation)?;
match phase {
Phase::Before => {
input = interruptible(Box::pin((methods.before)(input)), cancellation).await?;
phase = Phase::Call;
}
Phase::Call => {
output = Some(
interruptible(Box::pin((methods.call)(input.clone())), cancellation).await?,
);
phase = Phase::After;
}
Phase::After => {
output = Some(
interruptible(
Box::pin((methods.after)(
input.clone(),
output.take().expect("validated result"),
)),
cancellation,
)
.await?,
);
phase = Phase::Complete;
}
Phase::Complete => unreachable!("complete is the last phase"),
}
interruptible(
Box::pin((methods.commit)(phase, &input, output.as_ref())),
cancellation,
)
.await?;
}
Ok((input, output))
}
pub(crate) async fn prepare<I, E, B, BF, K, KF>(
from: Phase,
input: I,
before: B,
mut commit: K,
cancellation: &Cancellation,
) -> Result<I, E>
where
I: Clone,
E: From<Error>,
B: FnMut(I) -> BF,
BF: Future<Output = Result<I, E>>,
K: FnMut(I) -> KF,
KF: Future<Output = Result<(), E>>,
{
advance(
from,
Phase::Call,
input,
None::<()>,
Methods {
before,
call: |_| async { Ok(()) },
after: |_, ()| async { Ok(()) },
commit: move |_: Phase, input: &I, _: Option<&()>| commit(input.clone()),
},
cancellation,
)
.await
.map(|(input, _)| input)
}
fn check<E: From<Error>>(cancellation: &Cancellation) -> Result<(), E> {
super::operation::check(cancellation).map_err(Into::into)
}
#[cfg(test)]
#[path = "../../tests/unit/lifecycle.rs"]
mod tests;