diff options
author | Paul Woolcock <paul@woolcock.us> | 2020-10-07 05:47:39 -0400 |
---|---|---|
committer | Paul Woolcock <paul@woolcock.us> | 2020-10-07 09:06:13 -0400 |
commit | 02ca0a89515413ac9fb3b655de2f21f6a711e0f2 (patch) | |
tree | 004bcd9f88eca168e10e1ac85c5987fdd6769fcf /src/async/page.rs | |
parent | 04b5b54212629f058bdab1ba55c89a3d417e0454 (diff) |
Add basic async client
This adds a module, accessible by compiling with `--features async`,
that provides an `elefren::async::Client`. The client is
runtime-agnostic, and currently only provides unauthenticated access,
see the docs for the full list of methods that can be performed* with
this client.
* note that some API calls are publicly available by default, but can be
changed via instance settings to not be publicly accessible
Diffstat (limited to 'src/async/page.rs')
-rw-r--r-- | src/async/page.rs | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/src/async/page.rs b/src/async/page.rs new file mode 100644 index 0000000..4ef4161 --- /dev/null +++ b/src/async/page.rs @@ -0,0 +1,95 @@ +use super::{client, deserialize, Authenticate}; +use crate::{ + entities::{account::Account, card::Card, context::Context, status::Status}, + errors::{Error, Result}, +}; +use http_types::{Method, Request, Response}; +use hyper_old_types::header::{parsing, Link, RelationType}; +use smol::{prelude::*, Async}; +use std::{ + fmt::Debug, + net::{TcpStream, ToSocketAddrs}, +}; +use url::Url; + +// link header name +const LINK: &str = "link"; + +#[derive(Debug)] +pub struct Page<'client, T, A: Authenticate + Debug + 'client> { + next: Option<Request>, + prev: Option<Request>, + auth: &'client A, + _marker: std::marker::PhantomData<T>, +} +impl<'client, T: serde::de::DeserializeOwned, A: Authenticate + Debug + 'client> + Page<'client, T, A> +{ + pub fn new(next: Request, auth: &'client A) -> Page<'client, T, A> { + Page { + next: Some(next), + prev: None, + auth, + _marker: std::marker::PhantomData, + } + } + + pub async fn next_page(&mut self) -> Result<Option<Vec<T>>> { + let mut req = if let Some(next) = self.next.take() { + next + } else { + return Ok(None); + }; + Ok(self.send(req).await?) + } + + pub async fn prev_page(&mut self) -> Result<Option<Vec<T>>> { + let req = if let Some(prev) = self.prev.take() { + prev + } else { + return Ok(None); + }; + Ok(self.send(req).await?) + } + + async fn send(&mut self, mut req: Request) -> Result<Option<Vec<T>>> { + self.auth.authenticate(&mut req).await?; + log::trace!("Request: {:?}", req); + let response = client::fetch(req).await?; + log::trace!("Response: {:?}", response); + self.fill_links_from_resp(&response)?; + let items = deserialize(response).await?; + Ok(items) + } + + fn fill_links_from_resp(&mut self, response: &Response) -> Result<()> { + let (prev, next) = get_links(&response)?; + self.prev = prev.map(|url| Request::new(Method::Get, url)); + self.next = next.map(|url| Request::new(Method::Get, url)); + Ok(()) + } +} + +fn get_links(response: &Response) -> Result<(Option<Url>, Option<Url>)> { + let mut prev = None; + let mut next = None; + + if let Some(link_header) = response.header(LINK) { + let link_header = link_header.as_str(); + let link_header = link_header.as_bytes(); + let link_header: Link = parsing::from_raw_str(&link_header)?; + for value in link_header.values() { + if let Some(relations) = value.rel() { + if relations.contains(&RelationType::Next) { + next = Some(Url::parse(value.link())?); + } + + if relations.contains(&RelationType::Prev) { + prev = Some(Url::parse(value.link())?); + } + } + } + } + + Ok((prev, next)) +} |