Skip to main content

threads_rs/
pagination.rs

1use crate::client::Client;
2use crate::types::ids::{PostId, UserId};
3use crate::types::pagination::{PostsOptions, RepliesOptions};
4use crate::types::post::{Post, PostsResponse, RepliesResponse};
5use crate::types::search::SearchOptions;
6
7// ---------------------------------------------------------------------------
8// Helper: extract next cursor from a Paging struct
9// ---------------------------------------------------------------------------
10
11fn next_cursor(paging: &crate::types::pagination::Paging) -> Option<String> {
12    paging
13        .cursors
14        .as_ref()
15        .and_then(|c| c.after.clone())
16        .or_else(|| paging.after.clone())
17}
18
19// ---------------------------------------------------------------------------
20// PostIterator
21// ---------------------------------------------------------------------------
22
23/// Iterator for paginating through a user's posts.
24pub struct PostIterator<'a> {
25    client: &'a Client,
26    user_id: UserId,
27    options: PostsOptions,
28    cursor: Option<String>,
29    done: bool,
30}
31
32impl<'a> PostIterator<'a> {
33    /// Create a new post iterator.
34    pub fn new(client: &'a Client, user_id: UserId, options: Option<PostsOptions>) -> Self {
35        Self {
36            client,
37            user_id,
38            options: options.unwrap_or_default(),
39            cursor: None,
40            done: false,
41        }
42    }
43
44    /// Fetch the next page. Returns `None` when exhausted.
45    pub async fn next(&mut self) -> crate::Result<Option<PostsResponse>> {
46        if self.done {
47            return Ok(None);
48        }
49
50        let mut opts = self.options.clone();
51        if let Some(ref c) = self.cursor {
52            opts.after = Some(c.clone());
53        }
54
55        let resp = self
56            .client
57            .get_user_posts(&self.user_id, Some(&opts))
58            .await?;
59
60        if let Some(c) = next_cursor(&resp.paging) {
61            self.cursor = Some(c);
62        } else {
63            self.done = true;
64        }
65
66        if resp.data.is_empty() {
67            self.done = true;
68            return Ok(None);
69        }
70
71        Ok(Some(resp))
72    }
73
74    /// Returns `true` if there are more pages.
75    pub fn has_next(&self) -> bool {
76        !self.done
77    }
78
79    /// Reset to the first page.
80    pub fn reset(&mut self) {
81        self.cursor = None;
82        self.done = false;
83    }
84
85    /// Collect all remaining pages into a single `Vec<Post>`.
86    pub async fn collect_all(&mut self) -> crate::Result<Vec<Post>> {
87        let mut all = Vec::new();
88        while self.has_next() {
89            if let Some(resp) = self.next().await? {
90                all.extend(resp.data);
91            }
92        }
93        Ok(all)
94    }
95}
96
97// ---------------------------------------------------------------------------
98// ReplyIterator
99// ---------------------------------------------------------------------------
100
101/// Iterator for paginating through replies to a post.
102pub struct ReplyIterator<'a> {
103    client: &'a Client,
104    post_id: PostId,
105    options: RepliesOptions,
106    cursor: Option<String>,
107    done: bool,
108}
109
110impl<'a> ReplyIterator<'a> {
111    /// Create a new reply iterator.
112    pub fn new(client: &'a Client, post_id: PostId, options: Option<RepliesOptions>) -> Self {
113        Self {
114            client,
115            post_id,
116            options: options.unwrap_or_default(),
117            cursor: None,
118            done: false,
119        }
120    }
121
122    /// Fetch the next page. Returns `None` when exhausted.
123    pub async fn next(&mut self) -> crate::Result<Option<RepliesResponse>> {
124        if self.done {
125            return Ok(None);
126        }
127
128        let mut opts = self.options.clone();
129        if let Some(ref c) = self.cursor {
130            opts.after = Some(c.clone());
131        }
132
133        let resp = self.client.get_replies(&self.post_id, Some(&opts)).await?;
134
135        if let Some(c) = next_cursor(&resp.paging) {
136            self.cursor = Some(c);
137        } else {
138            self.done = true;
139        }
140
141        if resp.data.is_empty() {
142            self.done = true;
143            return Ok(None);
144        }
145
146        Ok(Some(resp))
147    }
148
149    /// Returns `true` if there are more pages.
150    pub fn has_next(&self) -> bool {
151        !self.done
152    }
153
154    /// Reset to the first page.
155    pub fn reset(&mut self) {
156        self.cursor = None;
157        self.done = false;
158    }
159
160    /// Collect all remaining pages into a single `Vec<Post>`.
161    pub async fn collect_all(&mut self) -> crate::Result<Vec<Post>> {
162        let mut all = Vec::new();
163        while self.has_next() {
164            if let Some(resp) = self.next().await? {
165                all.extend(resp.data);
166            }
167        }
168        Ok(all)
169    }
170}
171
172// ---------------------------------------------------------------------------
173// SearchIterator
174// ---------------------------------------------------------------------------
175
176/// Iterator for paginating through search results.
177pub struct SearchIterator<'a> {
178    client: &'a Client,
179    query: String,
180    options: SearchOptions,
181    cursor: Option<String>,
182    done: bool,
183}
184
185impl<'a> SearchIterator<'a> {
186    /// Create a new search iterator.
187    pub fn new(
188        client: &'a Client,
189        query: impl Into<String>,
190        options: Option<SearchOptions>,
191    ) -> Self {
192        Self {
193            client,
194            query: query.into(),
195            options: options.unwrap_or_default(),
196            cursor: None,
197            done: false,
198        }
199    }
200
201    /// Fetch the next page. Returns `None` when exhausted.
202    pub async fn next(&mut self) -> crate::Result<Option<PostsResponse>> {
203        if self.done {
204            return Ok(None);
205        }
206
207        let mut opts = self.options.clone();
208        if let Some(ref c) = self.cursor {
209            opts.after = Some(c.clone());
210        }
211
212        let resp = self.client.keyword_search(&self.query, Some(&opts)).await?;
213
214        if let Some(c) = next_cursor(&resp.paging) {
215            self.cursor = Some(c);
216        } else {
217            self.done = true;
218        }
219
220        if resp.data.is_empty() {
221            self.done = true;
222            return Ok(None);
223        }
224
225        Ok(Some(resp))
226    }
227
228    /// Returns `true` if there are more pages.
229    pub fn has_next(&self) -> bool {
230        !self.done
231    }
232
233    /// Reset to the first page.
234    pub fn reset(&mut self) {
235        self.cursor = None;
236        self.done = false;
237    }
238
239    /// Collect all remaining pages into a single `Vec<Post>`.
240    pub async fn collect_all(&mut self) -> crate::Result<Vec<Post>> {
241        let mut all = Vec::new();
242        while self.has_next() {
243            if let Some(resp) = self.next().await? {
244                all.extend(resp.data);
245            }
246        }
247        Ok(all)
248    }
249}