asex 0.3.0

Library that helps you to simulate exception without `panic` in async Rust.
Documentation
/// `ExceptionContext` that is not `Sync`. Using it in an async function will make
/// the return future not `Send` or `Sync`.
pub struct ExceptionContext<E> {
    exception: core::cell::Cell<Option<E>>,
}

impl<E> Default for ExceptionContext<E> {
    fn default() -> Self {
        Self {
            exception: core::cell::Cell::new(None),
        }
    }
}

impl<E> ExceptionContext<E> {
    /// Create a new exception context.
    pub fn new() -> Self {
        core::default::Default::default()
    }

    /// Throws an exception. You should always `await` the result.
    ///
    /// Example:
    ///
    /// ```rust
    /// tokio_test::block_on(async {
    ///     let r = asex::sync::ExceptionContext::<String>::new()
    ///         .catching(|ctx| async move {
    ///             ctx.throw("failed".to_string()).await;
    ///             unreachable!()
    ///         }).await;
    ///     assert_eq!(Err("failed".to_string()), r)
    /// })
    /// ```
    pub async fn throw(&self, exception: E) -> ! {
        self.exception.set(Some(exception));
        core::future::pending().await
    }

    /// Executes the function `f` providing the context, then returns a Future that
    /// catches the thrown exception.
    ///
    /// Example:
    ///
    /// ```rust
    /// tokio_test::block_on(async {
    ///     let r = asex::unsync::ExceptionContext::<String>::new()
    ///         .catching(|_| async {
    ///             "success".to_string()
    ///         }).await;
    ///     assert_eq!(Ok("success".to_string()), r);
    ///
    ///     let r = asex::unsync::ExceptionContext::<String>::new()
    ///         .catching(|ctx| async move {
    ///             ctx.throw("failed".to_string()).await;
    ///             unreachable!()
    ///         }).await;
    ///     assert_eq!(Err("failed".to_string()), r)
    /// })
    /// ```
    pub fn catching<'a, Fu: core::future::Future, F: Fn(&'a Self) -> Fu>(
        &'a self,
        f: F,
    ) -> Catching<'a, E, Fu> {
        Catching {
            ctx: self,
            future: f(self),
        }
    }
}

pin_project_lite::pin_project! {
    /// A wrapper future that catches the exception.
    ///
    /// It outputs a result with the exception as error.
    pub struct Catching<'a, E, F> {
        ctx: &'a ExceptionContext<E>,
        #[pin]
        future: F,
    }
}

impl<'a, E, F: core::future::Future> core::future::Future for Catching<'a, E, F> {
    type Output = Result<F::Output, E>;

    fn poll(
        self: core::pin::Pin<&mut Self>,
        cx: &mut core::task::Context<'_>,
    ) -> core::task::Poll<Self::Output> {
        let this = self.project();
        let p = this.future.poll(cx);
        if let Some(exception) = this.ctx.exception.take() {
            core::task::Poll::Ready(Err(exception))
        } else {
            p.map(Ok)
        }
    }
}