phabricator 0.0.4

Crate to work with Phabricator web APIs
Documentation
use crate::Client;
use futures::future::BoxFuture;
use futures::prelude::*;
use phabricator_api::types::Cursor;
use phabricator_api::RequestError;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use thiserror::Error;

pub type QueryData<T> = (Vec<T>, Option<Cursor>);

#[derive(Debug, Error)]
pub enum QueryError {
    #[error("Request Failed: {0}")]
    RequestError(#[from] reqwest::Error),
}

enum QueryState<'f, T> {
    Cached(Vec<T>),
    Data(Vec<T>, Option<Cursor>),
    Next(BoxFuture<'f, Result<QueryData<T>, RequestError>>),
    Finished,
}

type Getter<S, T> = Box<
    dyn Fn(&Client, Arc<S>, Option<Cursor>) -> BoxFuture<Result<QueryData<T>, RequestError>> + Send,
>;
pub struct Search<'c, S, T> {
    client: &'c Client,
    search: Option<Arc<S>>,
    state: QueryState<'c, T>,
    getter: Getter<S, T>,
}

impl<'c, S, T> Search<'c, S, T> {
    pub fn new(
        client: &'c Client,
        cached: Vec<T>,
        search: Option<S>,
        getter: Getter<S, T>,
    ) -> Self {
        let search = search.map(Arc::new);

        Search {
            client,
            search,
            state: QueryState::Cached(cached),
            getter,
        }
    }
}

impl<'c, S, T> Stream for Search<'c, S, T>
where
    T: Unpin,
    S: 'c,
{
    type Item = Result<T, RequestError>;

    fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
        let me = self.get_mut();

        loop {
            match me.state {
                QueryState::Cached(ref mut tasks) => {
                    if let Some(task) = tasks.pop() {
                        return Poll::Ready(Some(Ok(task)));
                    }

                    me.state = if let Some(s) = &me.search {
                        let f = (me.getter)(&me.client, s.clone(), None);
                        QueryState::Next(f)
                    } else {
                        QueryState::Finished
                    };
                }
                QueryState::Data(ref mut data, ref mut cursor) => {
                    if let Some(t) = data.pop() {
                        return Poll::Ready(Some(Ok(t)));
                    }
                    me.state = if let Some(cursor) = cursor.take() {
                        let f = (me.getter)(
                            me.client,
                            me.search.as_ref().unwrap().clone(),
                            Some(cursor),
                        );
                        QueryState::Next(f)
                    } else {
                        QueryState::Finished
                    };
                }
                QueryState::Next(ref mut f) => {
                    let (tasks, cursor) = futures::ready!(f.as_mut().poll(cx))?;
                    me.state = QueryState::Data(tasks, cursor);
                }
                QueryState::Finished => return Poll::Ready(None),
            }
        }
    }
}