From 0e8eb4e568dd02c91af7127f596d727ebc20d7fe Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Sun, 5 Aug 2018 11:03:48 -0400 Subject: [PATCH 1/8] Add `PageIter` to abstract over iterating over pages --- src/entities/mod.rs | 2 + src/entities/pageiter.rs | 94 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/entities/pageiter.rs diff --git a/src/entities/mod.rs b/src/entities/mod.rs index c82ae5a..e45057c 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -6,6 +6,7 @@ pub mod instance; pub mod list; pub mod mention; pub mod notification; +pub mod pageiter; pub mod relationship; pub mod report; pub mod search_result; @@ -27,6 +28,7 @@ pub mod prelude { pub use super::list::List; pub use super::mention::Mention; pub use super::notification::Notification; + pub use super::pageiter::PageIter; pub use super::relationship::Relationship; pub use super::report::Report; pub use super::search_result::SearchResult; diff --git a/src/entities/pageiter.rs b/src/entities/pageiter.rs new file mode 100644 index 0000000..52c92dc --- /dev/null +++ b/src/entities/pageiter.rs @@ -0,0 +1,94 @@ +use page::Page; +use entities::{ + account::Account, + notification::Notification, + relationship::Relationship, + report::Report, + status::{Emoji, Status} +}; +use serde::Deserialize; + +macro_rules! into_pageiter { + ($typ:ty) => { + impl<'a> IntoIterator for Page<'a, $typ> { + type Item = $typ; + type IntoIter = PageIter<'a, $typ>; + + fn into_iter(self) -> PageIter<'a, $typ> { + PageIter::new(self) + } + } + } +} + +into_pageiter!(Status); +into_pageiter!(Account); +into_pageiter!(String); +into_pageiter!(Emoji); +into_pageiter!(Notification); +into_pageiter!(Report); +into_pageiter!(Relationship); + +pub struct PageIter<'a, T: Clone + for<'de> Deserialize<'de>> { + page: Page<'a, T>, + buffer: Vec, + cur_idx: usize, + use_initial: bool, +} + +impl<'a, T: Clone + for<'de> Deserialize<'de>> PageIter<'a, T> { + fn new(page: Page<'a, T>) -> PageIter<'a, T> { + PageIter { + page: page, + buffer: vec![], + cur_idx: 0, + use_initial: true, + } + } + + fn need_next_page(&self) -> bool { + self.buffer.is_empty() || + self.cur_idx == self.buffer.len() + } + + fn fill_next_page(&mut self) -> Option<()> { + let items = if let Ok(items) = self.page.next_page() { + items + } else { + return None; + }; + if let Some(items) = items { + self.buffer = items; + self.cur_idx = 0; + Some(()) + } else { + None + } + } +} + +impl<'a, T: Clone+ for<'de> Deserialize<'de>> Iterator for PageIter<'a, T> { + type Item = T; + + fn next(&mut self) -> Option { + if self.use_initial { + let idx = self.cur_idx; + if self.cur_idx == self.page.initial_items.len() - 1 { + self.cur_idx = 0; + self.use_initial = false; + } else { + self.cur_idx += 1; + } + Some(self.page.initial_items[idx].clone()) + } else { + if self.need_next_page() { + if self.fill_next_page().is_none() { + return None; + } + } + let idx = self.cur_idx; + self.cur_idx += 1; + Some(self.buffer[idx].clone()) + } + } +} From 6d67e403bc62b18f311793d5b151e1fad7e29648 Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Sun, 5 Aug 2018 11:20:48 -0400 Subject: [PATCH 2/8] Don't expose pageiter module, and add some docs to PageIter --- src/entities/mod.rs | 2 +- src/entities/pageiter.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/entities/mod.rs b/src/entities/mod.rs index e45057c..80ddc51 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -6,7 +6,7 @@ pub mod instance; pub mod list; pub mod mention; pub mod notification; -pub mod pageiter; +mod pageiter; pub mod relationship; pub mod report; pub mod search_result; diff --git a/src/entities/pageiter.rs b/src/entities/pageiter.rs index 52c92dc..5a61f7e 100644 --- a/src/entities/pageiter.rs +++ b/src/entities/pageiter.rs @@ -29,6 +29,17 @@ into_pageiter!(Notification); into_pageiter!(Report); into_pageiter!(Relationship); +/// Abstracts away the `next_page` logic into a single stream of items +/// +/// ```ignore +/// # extern crate mammut +/// # use mammut::Mastodon; +/// let client = Mastodon::from_data(data); +/// let statuses = client.statuses("user-id", None); +/// for status in statuses.into_iter() { +/// // do something with `status` +/// } +/// ``` pub struct PageIter<'a, T: Clone + for<'de> Deserialize<'de>> { page: Page<'a, T>, buffer: Vec, From b4cbcfa1a7119ff6911f7f566f3f0ba04a6f1528 Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Sun, 5 Aug 2018 20:56:22 -0400 Subject: [PATCH 3/8] into_iter -> items_iter --- src/entities/mod.rs | 2 +- src/entities/pageiter.rs | 30 +----------------------------- src/page.rs | 6 ++++++ 3 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 80ddc51..a2ec942 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -6,7 +6,7 @@ pub mod instance; pub mod list; pub mod mention; pub mod notification; -mod pageiter; +pub(crate) mod pageiter; pub mod relationship; pub mod report; pub mod search_result; diff --git a/src/entities/pageiter.rs b/src/entities/pageiter.rs index 5a61f7e..d4fa8b8 100644 --- a/src/entities/pageiter.rs +++ b/src/entities/pageiter.rs @@ -1,34 +1,6 @@ use page::Page; -use entities::{ - account::Account, - notification::Notification, - relationship::Relationship, - report::Report, - status::{Emoji, Status} -}; use serde::Deserialize; -macro_rules! into_pageiter { - ($typ:ty) => { - impl<'a> IntoIterator for Page<'a, $typ> { - type Item = $typ; - type IntoIter = PageIter<'a, $typ>; - - fn into_iter(self) -> PageIter<'a, $typ> { - PageIter::new(self) - } - } - } -} - -into_pageiter!(Status); -into_pageiter!(Account); -into_pageiter!(String); -into_pageiter!(Emoji); -into_pageiter!(Notification); -into_pageiter!(Report); -into_pageiter!(Relationship); - /// Abstracts away the `next_page` logic into a single stream of items /// /// ```ignore @@ -48,7 +20,7 @@ pub struct PageIter<'a, T: Clone + for<'de> Deserialize<'de>> { } impl<'a, T: Clone + for<'de> Deserialize<'de>> PageIter<'a, T> { - fn new(page: Page<'a, T>) -> PageIter<'a, T> { + pub(crate) fn new(page: Page<'a, T>) -> PageIter<'a, T> { PageIter { page: page, buffer: vec![], diff --git a/src/page.rs b/src/page.rs index 1a9cf98..94a8ae1 100644 --- a/src/page.rs +++ b/src/page.rs @@ -3,6 +3,7 @@ use reqwest::Response; use reqwest::header::{Link, RelationType}; use serde::Deserialize; use url::Url; +use entities::pageiter::PageIter; pub struct Page<'a, T: for<'de> Deserialize<'de>> { mastodon: &'a Mastodon, @@ -53,6 +54,11 @@ impl<'a, T: for<'de> Deserialize<'de>> Page<'a, T> { } } +impl<'a, T: Clone + for<'de> Deserialize<'de>> Page<'a, T> { + pub fn items_iter(self) -> PageIter<'a, T> { + PageIter::new(self) + } +} fn get_links(response: &Response) -> Result<(Option, Option)> { let mut prev = None; From 68f8766affaa81de485b3ec8e8825ab29ba7d2bb Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Sat, 18 Aug 2018 08:00:47 -0400 Subject: [PATCH 4/8] Return `None` if there are no items --- src/entities/pageiter.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/entities/pageiter.rs b/src/entities/pageiter.rs index d4fa8b8..93bcdd9 100644 --- a/src/entities/pageiter.rs +++ b/src/entities/pageiter.rs @@ -55,6 +55,9 @@ impl<'a, T: Clone+ for<'de> Deserialize<'de>> Iterator for PageIter<'a, T> { fn next(&mut self) -> Option { if self.use_initial { + if self.page.initial_items.is_empty() { + return None; + } let idx = self.cur_idx; if self.cur_idx == self.page.initial_items.len() - 1 { self.cur_idx = 0; From 650147031210a338d296d7adc968e7a36abe5d6c Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Tue, 21 Aug 2018 15:26:34 -0400 Subject: [PATCH 5/8] Change `PageIter` to `ItemsIter` --- src/entities/{pageiter.rs => itemsiter.rs} | 10 +++++----- src/entities/mod.rs | 3 +-- src/page.rs | 8 +++++--- 3 files changed, 11 insertions(+), 10 deletions(-) rename src/entities/{pageiter.rs => itemsiter.rs} (86%) diff --git a/src/entities/pageiter.rs b/src/entities/itemsiter.rs similarity index 86% rename from src/entities/pageiter.rs rename to src/entities/itemsiter.rs index 93bcdd9..5217827 100644 --- a/src/entities/pageiter.rs +++ b/src/entities/itemsiter.rs @@ -12,16 +12,16 @@ use serde::Deserialize; /// // do something with `status` /// } /// ``` -pub struct PageIter<'a, T: Clone + for<'de> Deserialize<'de>> { +pub struct ItemsIter<'a, T: Clone + for<'de> Deserialize<'de>> { page: Page<'a, T>, buffer: Vec, cur_idx: usize, use_initial: bool, } -impl<'a, T: Clone + for<'de> Deserialize<'de>> PageIter<'a, T> { - pub(crate) fn new(page: Page<'a, T>) -> PageIter<'a, T> { - PageIter { +impl<'a, T: Clone + for<'de> Deserialize<'de>> ItemsIter<'a, T> { + pub(crate) fn new(page: Page<'a, T>) -> ItemsIter<'a, T> { + ItemsIter { page: page, buffer: vec![], cur_idx: 0, @@ -50,7 +50,7 @@ impl<'a, T: Clone + for<'de> Deserialize<'de>> PageIter<'a, T> { } } -impl<'a, T: Clone+ for<'de> Deserialize<'de>> Iterator for PageIter<'a, T> { +impl<'a, T: Clone+ for<'de> Deserialize<'de>> Iterator for ItemsIter<'a, T> { type Item = T; fn next(&mut self) -> Option { diff --git a/src/entities/mod.rs b/src/entities/mod.rs index a2ec942..9da56b3 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -6,7 +6,7 @@ pub mod instance; pub mod list; pub mod mention; pub mod notification; -pub(crate) mod pageiter; +pub(crate) mod itemsiter; pub mod relationship; pub mod report; pub mod search_result; @@ -28,7 +28,6 @@ pub mod prelude { pub use super::list::List; pub use super::mention::Mention; pub use super::notification::Notification; - pub use super::pageiter::PageIter; pub use super::relationship::Relationship; pub use super::report::Report; pub use super::search_result::SearchResult; diff --git a/src/page.rs b/src/page.rs index 94a8ae1..6648be9 100644 --- a/src/page.rs +++ b/src/page.rs @@ -3,7 +3,7 @@ use reqwest::Response; use reqwest::header::{Link, RelationType}; use serde::Deserialize; use url::Url; -use entities::pageiter::PageIter; +use entities::itemsiter::ItemsIter; pub struct Page<'a, T: for<'de> Deserialize<'de>> { mastodon: &'a Mastodon, @@ -55,8 +55,10 @@ impl<'a, T: for<'de> Deserialize<'de>> Page<'a, T> { } impl<'a, T: Clone + for<'de> Deserialize<'de>> Page<'a, T> { - pub fn items_iter(self) -> PageIter<'a, T> { - PageIter::new(self) + pub fn items_iter(self) -> impl Iterator + 'a + where T: 'a + { + ItemsIter::new(self) } } From 664cb2797f96b89de72aed954d8c6e38abafa225 Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Tue, 21 Aug 2018 15:41:08 -0400 Subject: [PATCH 6/8] fix the test so it actually tests something --- src/entities/itemsiter.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/entities/itemsiter.rs b/src/entities/itemsiter.rs index 5217827..1083e77 100644 --- a/src/entities/itemsiter.rs +++ b/src/entities/itemsiter.rs @@ -3,14 +3,25 @@ use serde::Deserialize; /// Abstracts away the `next_page` logic into a single stream of items /// -/// ```ignore -/// # extern crate mammut -/// # use mammut::Mastodon; +/// ```no_run +/// # extern crate mammut; +/// # use mammut::{Data, Mastodon}; +/// # use std::error::Error; +/// # fn main() -> Result<(), Box> { +/// # let data = Data { +/// # base: "".into(), +/// # client_id: "".into(), +/// # client_secret: "".into(), +/// # redirect: "".into(), +/// # token: "".into(), +/// # }; /// let client = Mastodon::from_data(data); -/// let statuses = client.statuses("user-id", None); -/// for status in statuses.into_iter() { +/// let statuses = client.statuses("user-id", false, false)?; +/// for status in statuses.items_iter() { /// // do something with `status` /// } +/// # Ok(()) +/// # } /// ``` pub struct ItemsIter<'a, T: Clone + for<'de> Deserialize<'de>> { page: Page<'a, T>, From d14d50070e176081a958b042ce8e1eac6cbd6982 Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Tue, 21 Aug 2018 15:51:57 -0400 Subject: [PATCH 7/8] fix test --- src/entities/itemsiter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entities/itemsiter.rs b/src/entities/itemsiter.rs index 1083e77..f01ddb1 100644 --- a/src/entities/itemsiter.rs +++ b/src/entities/itemsiter.rs @@ -16,7 +16,7 @@ use serde::Deserialize; /// # token: "".into(), /// # }; /// let client = Mastodon::from_data(data); -/// let statuses = client.statuses("user-id", false, false)?; +/// let statuses = client.statuses("user-id", None)?; /// for status in statuses.items_iter() { /// // do something with `status` /// } From 5c339fd41f76f7aac2ad3470c46dfec4eecb6f97 Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Tue, 21 Aug 2018 16:09:54 -0400 Subject: [PATCH 8/8] compile (but don't run) two more tests --- src/lib.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 466e93f..5ac1986 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -587,16 +587,42 @@ impl Mastodon { /// /// # Example /// - /// ```ignore + /// ```no_run + /// # extern crate mammut; + /// # use mammut::{Data, Mastodon}; + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # let data = Data { + /// # base: "".into(), + /// # client_id: "".into(), + /// # client_secret: "".into(), + /// # redirect: "".into(), + /// # token: "".into(), + /// # }; /// let client = Mastodon::from_data(data); /// let statuses = client.statuses("user-id", None)?; + /// # Ok(()) + /// # } /// ``` /// - /// ```ignore + /// ```no_run + /// # extern crate mammut; + /// # use mammut::{Data, Mastodon, StatusesRequest}; + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # let data = Data { + /// # base: "".into(), + /// # client_id: "".into(), + /// # client_secret: "".into(), + /// # redirect: "".into(), + /// # token: "".into(), + /// # }; /// let client = Mastodon::from_data(data); /// let request = StatusesRequest::default() /// .only_media(); /// let statuses = client.statuses("user-id", request)?; + /// # Ok(()) + /// # } /// ``` pub fn statuses<'a, S>(&self, id: &str, request: S) -> Result> where S: Into>>