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(¶ms));
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}