egg_mode/
cursor.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5//! Types and traits to navigate cursored collections.
6//!
7//! Much of this module can be considered an implementation detail; the main intended entry point
8//! to this code is `CursorIter`, and that can just be used as a stream to ignore the rest of this
9//! module. The rest of it is available to make sure consumers of the API can understand precisely
10//! what types come out of functions that return `CursorIter`.
11
12use futures::Stream;
13use serde::{de::DeserializeOwned, Deserialize};
14use std::future::Future;
15use std::pin::Pin;
16use std::task::{Context, Poll};
17
18use crate::common::*;
19use crate::error::Result;
20use crate::{auth, list, user};
21
22///Trait to generalize over paginated views of API results.
23///
24///Types that implement Cursor are used as intermediate steps in [`CursorIter`][]'s Stream
25///implementation, to properly load the data from Twitter. Most of the time you don't need to deal
26///with Cursor structs directly, but you can get them via `CursorIter`'s manual paging
27///functionality.
28///
29///[`CursorIter`]: struct.CursorIter.html
30pub trait Cursor {
31    ///What type is being returned by the API call?
32    type Item;
33
34    ///Returns a numeric reference to the previous page of results.
35    fn previous_cursor_id(&self) -> i64;
36    ///Returns a numeric reference to the next page of results.
37    fn next_cursor_id(&self) -> i64;
38    ///Unwraps the cursor, returning the collection of results from inside.
39    fn into_inner(self) -> Vec<Self::Item>;
40}
41
42///Represents a single-page view into a list of users.
43///
44///This type is intended to be used in the background by [`CursorIter`][] to hold an intermediate
45///list of users to iterate over. See that struct's documentation for details.
46///
47///[`CursorIter`]: struct.CursorIter.html
48#[derive(Deserialize)]
49pub struct UserCursor {
50    ///Numeric reference to the previous page of results.
51    pub previous_cursor: i64,
52    ///Numeric reference to the next page of results.
53    pub next_cursor: i64,
54    ///The list of users in this page of results.
55    pub users: Vec<user::TwitterUser>,
56}
57
58impl Cursor for UserCursor {
59    type Item = user::TwitterUser;
60
61    fn previous_cursor_id(&self) -> i64 {
62        self.previous_cursor
63    }
64
65    fn next_cursor_id(&self) -> i64 {
66        self.next_cursor
67    }
68
69    fn into_inner(self) -> Vec<Self::Item> {
70        self.users
71    }
72}
73
74///Represents a single-page view into a list of IDs.
75///
76///This type is intended to be used in the background by [`CursorIter`][] to hold an intermediate
77///list of IDs to iterate over. See that struct's documentation for details.
78///
79///[`CursorIter`]: struct.CursorIter.html
80#[derive(Deserialize)]
81pub struct IDCursor {
82    ///Numeric reference to the previous page of results.
83    pub previous_cursor: i64,
84    ///Numeric reference to the next page of results.
85    pub next_cursor: i64,
86    ///The list of user IDs in this page of results.
87    pub ids: Vec<u64>,
88}
89
90impl Cursor for IDCursor {
91    type Item = u64;
92
93    fn previous_cursor_id(&self) -> i64 {
94        self.previous_cursor
95    }
96
97    fn next_cursor_id(&self) -> i64 {
98        self.next_cursor
99    }
100
101    fn into_inner(self) -> Vec<Self::Item> {
102        self.ids
103    }
104}
105
106///Represents a single-page view into a list of lists.
107///
108///This type is intended to be used in the background by [`CursorIter`][] to hold an intermediate
109///list of lists to iterate over. See that struct's documentation for details.
110///
111///[`CursorIter`]: struct.CursorIter.html
112#[derive(Deserialize)]
113pub struct ListCursor {
114    ///Numeric reference to the previous page of results.
115    pub previous_cursor: i64,
116    ///Numeric reference to the next page of results.
117    pub next_cursor: i64,
118    ///The list of lists in this page of results.
119    pub lists: Vec<list::List>,
120}
121
122impl Cursor for ListCursor {
123    type Item = list::List;
124
125    fn previous_cursor_id(&self) -> i64 {
126        self.previous_cursor
127    }
128
129    fn next_cursor_id(&self) -> i64 {
130        self.next_cursor
131    }
132
133    fn into_inner(self) -> Vec<Self::Item> {
134        self.lists
135    }
136}
137
138/// Represents a paginated list of results, such as the users who follow a specific user or the
139/// lists owned by that user.
140///
141/// This struct is given by several methods in this library, whenever Twitter would return a
142/// cursored list of items. It implements the `Stream` trait, loading items in batches so that
143/// several can be immedately returned whenever a single network call completes.
144///
145/// ```rust,no_run
146/// # use egg_mode::Token;
147/// use futures::{StreamExt, TryStreamExt};
148///
149/// # #[tokio::main]
150/// # async fn main() {
151/// # let token: Token = unimplemented!();
152/// egg_mode::user::followers_of("rustlang", &token).take(10).try_for_each(|resp| {
153///     println!("{}", resp.screen_name);
154///     futures::future::ok(())
155/// }).await.unwrap();
156/// # }
157/// ```
158///
159/// You can even collect the results, letting you get one set of rate-limit information for the
160/// entire search setup:
161///
162/// ```rust,no_run
163/// # use egg_mode::Token;
164/// # #[tokio::main]
165/// # async fn main() {
166/// # let token: Token = unimplemented!();
167/// use futures::{StreamExt, TryStreamExt};
168/// use egg_mode::Response;
169/// use egg_mode::user::TwitterUser;
170/// use egg_mode::error::Result;
171///
172/// // Because Streams don't have a FromIterator adaptor, we load all the responses first, then
173/// // collect them into the final Vec
174/// let names: Result<Vec<TwitterUser>> =
175///     egg_mode::user::followers_of("rustlang", &token)
176///         .take(10)
177///         .map_ok(|r| r.response)
178///         .try_collect::<Vec<_>>()
179///         .await;
180/// # }
181/// ```
182///
183/// `CursorIter` has an adaptor of its own, `with_page_size`, that you can use before consuming it.
184/// `with_page_size` will let you set how many users are pulled in with a single network call.
185/// Calling it after starting iteration will clear any current results.
186///
187/// (A note about `with_page_size`/`page_size`: While the `CursorIter` struct always has this method
188/// and field available, not every cursored call supports changing page size. Check the individual
189/// method documentation for notes on what page sizes are allowed.)
190///
191/// The `Stream` implementation yields `Response<T::Item>` on a successful iteration, and `Error`
192/// for errors, so network errors, rate-limit errors and other issues are passed directly through
193/// in `poll()`. The `Stream` implementation will allow you to poll again after an error to
194/// re-initiate the late network call; this way, you can wait for your network connection to return
195/// or for your rate limit to refresh and try again with the same state.
196///
197/// ## Manual paging
198///
199/// The `Stream` implementation works by loading in a page of results (with size set by the
200/// method's default or by `with_page_size`/the `page_size` field) when it's polled, and serving
201/// the individual elements from that locally-cached page until it runs out. This can be nice, but
202/// it also means that your only warning that something involves a network call is that the stream
203/// returns `Poll::Pending`, by which time the network call has already started. If you want
204/// to know that ahead of time, that's where the `call()` method comes in. By using `call()`, you
205/// can get the cursor struct directly from Twitter. With that you can iterate over the results and
206/// page forward and backward as needed:
207///
208/// ```rust,no_run
209/// # use egg_mode::Token;
210/// # #[tokio::main]
211/// # async fn main() {
212/// # let token: Token = unimplemented!();
213/// let mut list = egg_mode::user::followers_of("rustlang", &token).with_page_size(20);
214/// let resp = list.call().await.unwrap();
215///
216/// for user in resp.response.users {
217///     println!("{} (@{})", user.name, user.screen_name);
218/// }
219///
220/// list.next_cursor = resp.response.next_cursor;
221/// let resp = list.call().await.unwrap();
222///
223/// for user in resp.response.users {
224///     println!("{} (@{})", user.name, user.screen_name);
225/// }
226/// # }
227/// ```
228#[must_use = "cursor iterators are lazy and do nothing unless consumed"]
229pub struct CursorIter<T>
230where
231    T: Cursor + DeserializeOwned,
232{
233    link: &'static str,
234    token: auth::Token,
235    params_base: Option<ParamList>,
236    ///The number of results returned in one network call.
237    ///
238    ///Certain calls set their own minimums and maximums for what this value can be. Furthermore,
239    ///some calls don't allow you to set the size of the pages at all. Refer to the individual
240    ///methods' documentation for specifics.
241    pub page_size: Option<i32>,
242    ///Numeric reference to the previous page of results. A value of zero indicates that the
243    ///current page of results is the first page of the cursor.
244    ///
245    ///This value is intended to be automatically set and used as part of this struct's Iterator
246    ///implementation. It is made available for those who wish to manually manage network calls and
247    ///pagination.
248    pub previous_cursor: i64,
249    ///Numeric reference to the next page of results. A value of zero indicates that the current
250    ///page of results is the last page of the cursor.
251    ///
252    ///This value is intended to be automatically set and used as part of this struct's Iterator
253    ///implementation. It is made available for those who wish to manually manage network calls and
254    ///pagination.
255    pub next_cursor: i64,
256    loader: Option<FutureResponse<T>>,
257    iter: Option<Box<dyn Iterator<Item = Response<T::Item>> + Send>>,
258}
259
260impl<T> CursorIter<T>
261where
262    T: Cursor + DeserializeOwned,
263{
264    ///Sets the number of results returned in a single network call.
265    ///
266    ///Certain calls set their own minimums and maximums for what this value can be. Furthermore,
267    ///some calls don't allow you to set the size of the pages at all. Refer to the individual
268    ///methods' documentation for specifics. If this method is called for a response that does not
269    ///accept changing the page size, no change to the underlying struct will occur.
270    ///
271    ///Calling this function will invalidate any current results, if any were previously loaded.
272    pub fn with_page_size(self, page_size: i32) -> CursorIter<T> {
273        if self.page_size.is_some() {
274            CursorIter {
275                page_size: Some(page_size),
276                previous_cursor: -1,
277                next_cursor: -1,
278                loader: None,
279                iter: None,
280                ..self
281            }
282        } else {
283            self
284        }
285    }
286
287    ///Loads the next page of results.
288    ///
289    ///This is intended to be used as part of this struct's Iterator implementation. It is provided
290    ///as a convenience for those who wish to manage network calls and pagination manually.
291    pub fn call(&self) -> impl Future<Output = Result<Response<T>>> {
292        let params = self
293            .params_base
294            .as_ref()
295            .cloned()
296            .unwrap_or_default()
297            .add_param("cursor", self.next_cursor.to_string())
298            .add_opt_param("count", self.page_size.map_string());
299
300        let req = get(self.link, &self.token, Some(&params));
301        request_with_json_response(req)
302    }
303
304    ///Creates a new instance of CursorIter, with the given parameters and empty initial results.
305    ///
306    ///This is essentially an internal infrastructure function, not meant to be used from consumer
307    ///code.
308    pub(crate) fn new(
309        link: &'static str,
310        token: &auth::Token,
311        params_base: Option<ParamList>,
312        page_size: Option<i32>,
313    ) -> CursorIter<T> {
314        CursorIter {
315            link,
316            token: token.clone(),
317            params_base,
318            page_size,
319            previous_cursor: -1,
320            next_cursor: -1,
321            loader: None,
322            iter: None,
323        }
324    }
325}
326
327impl<T> Stream for CursorIter<T>
328where
329    T: Cursor + DeserializeOwned + 'static,
330    T::Item: Unpin + Send,
331{
332    type Item = Result<Response<T::Item>>;
333
334    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
335        if let Some(mut fut) = self.loader.take() {
336            match Pin::new(&mut fut).poll(cx) {
337                Poll::Pending => {
338                    self.loader = Some(fut);
339                    return Poll::Pending;
340                }
341                Poll::Ready(Ok(resp)) => {
342                    self.previous_cursor = resp.previous_cursor_id();
343                    self.next_cursor = resp.next_cursor_id();
344
345                    let resp = Response::map(resp, |r| r.into_inner());
346                    let rate = resp.rate_limit_status;
347
348                    let mut iter = Box::new(resp.response.into_iter().map(move |item| Response {
349                        rate_limit_status: rate,
350                        response: item,
351                    }));
352                    let first = iter.next();
353                    self.iter = Some(iter);
354
355                    match first {
356                        Some(item) => return Poll::Ready(Some(Ok(item))),
357                        None => return Poll::Ready(None),
358                    }
359                }
360                Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e))),
361            }
362        }
363
364        if let Some(ref mut results) = self.iter {
365            if let Some(item) = results.next() {
366                return Poll::Ready(Some(Ok(item)));
367            } else if self.next_cursor == 0 {
368                return Poll::Ready(None);
369            }
370        }
371
372        self.loader = Some(Box::pin(self.call()));
373        self.poll_next(cx)
374    }
375}