snafu 0.4.2

An ergonomic error handling library
//! Additions to the [`TryFuture`] trait.
//!
//! [`TryFuture`]: futures_core::future::TryFuture

use futures_core::future::TryFuture;
use pin_project::unsafe_project;
use std::{
    marker::PhantomData,
    pin::Pin,
    task::{Context as TaskContext, Poll},
};
use {ErrorCompat, IntoError};

/// Additions to [`TryFuture`].
pub trait TryFutureExt: TryFuture + Sized {
    /// Extend a [`TryFuture`]'s error with additional context-sensitive
    /// information.
    ///
    /// ```rust
    /// # extern crate futures;
    /// use futures::future::TryFuture;
    /// use snafu::{futures::TryFutureExt, Snafu};
    ///
    /// #[derive(Debug, Snafu)]
    /// enum Error {
    ///     Authenticating {
    ///         user_name: String,
    ///         user_id: i32,
    ///         source: ApiError,
    ///     },
    /// }
    ///
    /// fn example() -> impl TryFuture<Ok = i32, Error = Error> {
    ///     another_function().context(Authenticating {
    ///         user_name: "admin",
    ///         user_id: 42,
    ///     })
    /// }
    ///
    /// # type ApiError = Box<dyn std::error::Error>;
    /// fn another_function() -> impl TryFuture<Ok = i32, Error = ApiError> {
    ///     /* ... */
    /// # futures::future::ok(42)
    /// }
    /// ```
    ///
    /// Note that the context selector will call [`Into::into`] on
    /// each field, so the types are not required to exactly match.
    fn context<C, E>(self, context: C) -> Context<Self, C, E>
    where
        C: IntoError<E, Source = Self::Error>,
        E: std::error::Error + ErrorCompat;

    /// Extend a [`TryFuture`]'s error with lazily-generated context-sensitive
    /// information.
    ///
    /// ```rust
    /// # extern crate futures;
    /// use futures::future::TryFuture;
    /// use snafu::{futures::TryFutureExt, Snafu};
    ///
    /// #[derive(Debug, Snafu)]
    /// enum Error {
    ///     Authenticating {
    ///         user_name: String,
    ///         user_id: i32,
    ///         source: ApiError,
    ///     },
    /// }
    ///
    /// fn example() -> impl TryFuture<Ok = i32, Error = Error> {
    ///     another_function().with_context(|| Authenticating {
    ///         user_name: "admin".to_string(),
    ///         user_id: 42,
    ///     })
    /// }
    ///
    /// # type ApiError = Box<dyn std::error::Error>;
    /// fn another_function() -> impl TryFuture<Ok = i32, Error = ApiError> {
    ///     /* ... */
    /// # futures::future::ok(42)
    /// }
    /// ```
    ///
    /// Note that this *may not* be needed in many cases because the
    /// context selector will call [`Into::into`] on each field.
    fn with_context<F, C, E>(self, context: F) -> WithContext<Self, F, E>
    where
        F: FnOnce() -> C,
        C: IntoError<E, Source = Self::Error>,
        E: std::error::Error + ErrorCompat;
}

impl<Fut> TryFutureExt for Fut
where
    Fut: TryFuture,
{
    fn context<C, E>(self, context: C) -> Context<Self, C, E>
    where
        C: IntoError<E, Source = Self::Error>,
        E: std::error::Error + ErrorCompat,
    {
        Context {
            inner: self,
            context: Some(context),
            _e: PhantomData,
        }
    }

    fn with_context<F, C, E>(self, context: F) -> WithContext<Self, F, E>
    where
        F: FnOnce() -> C,
        C: IntoError<E, Source = Self::Error>,
        E: std::error::Error + ErrorCompat,
    {
        WithContext {
            inner: self,
            context: Some(context),
            _e: PhantomData,
        }
    }
}

/// Future for the [`context`](TryFutureExt::context) combinator.
///
/// See the [`TryFutureExt::context`] method for more details.
#[unsafe_project(Unpin)]
#[derive(Debug)]
#[must_use = "futures do nothing unless polled"]
pub struct Context<Fut, C, E> {
    #[pin]
    inner: Fut,
    context: Option<C>,
    _e: PhantomData<E>,
}

impl<Fut, C, E> TryFuture for Context<Fut, C, E>
where
    Fut: TryFuture,
    C: IntoError<E, Source = Fut::Error>,
    E: std::error::Error + ErrorCompat,
{
    type Ok = Fut::Ok;
    type Error = E;

    fn try_poll(
        self: Pin<&mut Self>,
        ctx: &mut TaskContext,
    ) -> Poll<Result<Self::Ok, Self::Error>> {
        let this = self.project();
        let inner = this.inner;
        let context = this.context;

        inner.try_poll(ctx).map_err(|error| {
            context
                .take()
                .expect("Cannot poll Context after it resolves")
                .into_error(error)
        })
    }
}

/// Future for the [`with_context`](TryFutureExt::with_context) combinator.
///
/// See the [`TryFutureExt::with_context`] method for more details.
#[unsafe_project(Unpin)]
#[derive(Debug)]
#[must_use = "futures do nothing unless polled"]
pub struct WithContext<Fut, F, E> {
    #[pin]
    inner: Fut,
    context: Option<F>,
    _e: PhantomData<E>,
}

impl<Fut, F, C, E> TryFuture for WithContext<Fut, F, E>
where
    Fut: TryFuture,
    F: FnOnce() -> C,
    C: IntoError<E, Source = Fut::Error>,
    E: std::error::Error + ErrorCompat,
{
    type Ok = Fut::Ok;
    type Error = E;

    fn try_poll(
        self: Pin<&mut Self>,
        ctx: &mut TaskContext,
    ) -> Poll<Result<Self::Ok, Self::Error>> {
        let this = self.project();
        let inner = this.inner;
        let context = this.context;

        inner.try_poll(ctx).map_err(|error| {
            let context = context
                .take()
                .expect("Cannot poll WithContext after it resolves");

            context().into_error(error)
        })
    }
}