wiki 0.0.3

Library for interacting with MediaWiki APIs
Documentation
use std::future::Future;
use std::marker::PhantomData;
use std::mem::take;
use std::pin::Pin;
use std::task::{Context, Poll};

use futures_util::future::BoxFuture;
use futures_util::Stream;
use reqwest::{Client, Url};
use serde::de::DeserializeOwned;
use serde_json::Value;
use tracing::{trace, trace_span};

use crate::api::{
    BasicSearchResult, MaybeContinue, RecentChangesResult, RequestBuilderExt, Revisions, SlotsMain,
};
use crate::req::rc::ListRc;
use crate::req::{self, ListSearch, Main, Query, QueryList};
use crate::{api, Access};

pub mod rcpatrol;

pub type BoxReqFuture = BoxFuture<'static, reqwest::Result<reqwest::Response>>;
pub type BoxRecvFuture =
    BoxFuture<'static, reqwest::Result<api::QueryResponse<Revisions<SlotsMain>>>>;

pub type ResponseFuture<G> = Pin<
    Box<
        dyn Future<Output = crate::Result<MaybeContinue<<G as WikiGenerator>::Response>>>
            + Send
            + Sync,
    >,
>;

#[derive(Default)]
#[pin_project::pin_project(project = StateProj)]
pub enum State<G: WikiGenerator> {
    #[default]
    Init,
    Fut(#[pin] ResponseFuture<G>),
    Values(Vec<G::Item>, Option<Value>),
    Cont(Value),
    Done,
}

impl<G: WikiGenerator> State<G> {
    pub fn values(v: Vec<G::Item>, cont: Option<Value>) -> Self {
        if v.is_empty() {
            if let Some(c) = cont {
                Self::Cont(c)
            } else {
                Self::Done
            }
        } else {
            Self::Values(v, cont)
        }
    }
}

#[pin_project::pin_project]
pub struct GeneratorStream<G: WikiGenerator> {
    pub generator: G,
    #[pin]
    state: State<G>,
    span: tracing::span::Span,
}

impl<G: WikiGenerator> Stream for GeneratorStream<G> {
    type Item = crate::Result<G::Item>;
    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        let mut this = self.as_mut().project();
        let entered = this.span.enter();
        macro_rules! tryit {
            ($e:expr) => {
                match $e {
                    Ok(very_well) => very_well,
                    Err(e) => {
                        this.state.set(State::Done);
                        return Poll::Ready(Some(Err(e.into())));
                    }
                }
            };
        }

        let url = match this.state.as_mut().project() {
            StateProj::Init => {
                let main = this.generator.create_request();
                trace!("created request");
                let u = crate::api::mkurl(this.generator.url().clone(), main);
                trace!("created url");
                u
            }
            StateProj::Cont(v) => {
                let main = this.generator.create_request();
                trace!("created request");
                let u = tryit!(crate::api::mkurl_with_ext(
                    this.generator.url().clone(),
                    main,
                    v.take()
                ));
                trace!("created url");
                u
            }
            StateProj::Values(v, cont) => {
                let value = v.pop().expect("must always have value");
                let state = State::values(take(v), take(cont));
                this.state.set(state);
                return Poll::Ready(Some(Ok(value)));
            }
            StateProj::Fut(f) => match f.poll(cx) {
                Poll::Pending => return Poll::Pending,
                Poll::Ready(res) => {
                    trace!("received request");
                    let res = tryit!(res);
                    let mut items = tryit!(this.generator.untangle_response(res.inner));
                    trace!("parsed response");
                    if let Some(item) = items.pop() {
                        this.state.set(State::values(items, res.cont));
                        return Poll::Ready(Some(Ok(item)));
                    } else {
                        assert!(res.cont.is_none(), "Cannot continue without return value");
                        return Poll::Ready(None);
                    }
                }
            },
            StateProj::Done => return Poll::Ready(None),
        };

        let req = this.generator.client().get(url).send_parse();
        trace!("sent request");

        drop(entered);
        this.state.set(State::Fut(req));

        self.poll_next(cx)
    }
}

pub trait WikiGenerator {
    type Item: 'static;
    type Response: DeserializeOwned;
    fn url(&self) -> &Url;
    fn client(&self) -> &Client;
    fn create_request(&self) -> Main;
    fn untangle_response(&self, res: Self::Response) -> crate::Result<Vec<Self::Item>>;
    fn into_stream(self) -> GeneratorStream<Self>
    where
        Self: Sized,
    {
        GeneratorStream {
            generator: self,
            state: State::Init,
            span: trace_span!("stream"),
        }
    }
}

/// GENeric GENerator, use this to create your own continuable requests
pub struct GenGen<Access, State, C, U, Response, Item> {
    pub access: Access,
    pub state: State,
    create_request: C,
    untangle_response: U,
    _phtm: PhantomData<fn() -> (Response, Item)>,
}

impl<A, State, C, U, Response, Item> GenGen<A, State, C, U, Response, Item>
where
    A: Access,
    C: Fn(&Url, &Client, &State) -> Main,
    U: Fn(&Url, &Client, &State, Response) -> crate::Result<Vec<Item>>,
    Response: DeserializeOwned,
{
    pub fn new(access: A, state: State, create_request: C, untangle_response: U) -> Self {
        Self {
            access,
            state,
            create_request,
            untangle_response,
            _phtm: PhantomData,
        }
    }
}

impl<A, State, C, U, Response, Item> WikiGenerator for GenGen<A, State, C, U, Response, Item>
where
    A: Access,
    C: Fn(&Url, &Client, &State) -> Main,
    U: Fn(&Url, &Client, &State, Response) -> crate::Result<Vec<Item>>,
    Response: DeserializeOwned,
    Item: 'static,
{
    type Item = Item;
    type Response = Response;

    fn url(&self) -> &Url {
        self.access.url()
    }

    fn client(&self) -> &Client {
        self.access.client()
    }

    fn create_request(&self) -> Main {
        (self.create_request)(self.url(), self.client(), &self.state)
    }

    fn untangle_response(&self, res: Self::Response) -> crate::Result<Vec<Self::Item>> {
        (self.untangle_response)(self.url(), self.client(), &self.state, res)
    }
}

pub struct SearchGenerator<A> {
    access: A,
    search: String,
}

impl<A: Access> WikiGenerator for SearchGenerator<A> {
    type Item = BasicSearchResult;
    type Response = api::QueryResponse<api::Search<BasicSearchResult>>;

    fn url(&self) -> &Url {
        self.access.url()
    }

    fn client(&self) -> &Client {
        self.access.client()
    }

    fn create_request(&self) -> Main {
        Main::query(Query {
            list: Some(
                QueryList::Search(ListSearch {
                    search: self.search.clone(),
                    limit: req::Limit::Max,
                })
                .into(),
            ),
            ..Default::default()
        })
    }

    fn untangle_response(&self, res: Self::Response) -> crate::Result<Vec<Self::Item>> {
        Ok(res.query.search)
    }
}

impl<A: Access> SearchGenerator<A> {
    pub fn new(access: A, search: String) -> Self {
        Self { search, access }
    }
}

pub struct RecentChangesGenerator<A> {
    access: A,
    rc: ListRc,
}

impl<A: Access> RecentChangesGenerator<A> {
    pub fn new(access: A, rc: ListRc) -> Self {
        Self { access, rc }
    }
}

impl<A: Access> WikiGenerator for RecentChangesGenerator<A> {
    type Item = RecentChangesResult;
    type Response = api::QueryResponse<api::RecentChanges<RecentChangesResult>>;
    fn url(&self) -> &Url {
        self.access.url()
    }
    fn client(&self) -> &Client {
        self.access.client()
    }
    fn create_request(&self) -> Main {
        Main::query(Query {
            list: Some(QueryList::RecentChanges(self.rc.clone()).into()),
            ..Default::default()
        })
    }
    fn untangle_response(&self, res: Self::Response) -> crate::Result<Vec<Self::Item>> {
        Ok(res.query.recent_changes)
    }
}