1use super::{Mastodon, Result};
2use crate::{entities::itemsiter::ItemsIter, helpers::read_response::read_response, Error};
3use futures::Stream;
4use log::{as_debug, as_serde, debug, error, trace};
5use reqwest::{header::LINK, Response, Url};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9macro_rules! pages {
10 ($($direction:ident: $fun:ident),*) => {
11
12 $(
13 doc_comment!(concat!(
14 "Method to retrieve the ", stringify!($direction), " page of results",
15 "Returns Ok(None) if there is no data in the ", stringify!($direction), " page.\n",
16 "Returns Ok(Some(Vec<T>)) if there are results.\n",
17 "Returns Err(Error) if there is an error.\n",
18 "If there are results, the next and previous page urls are stored.\n",
19 "If there are no results, the next and previous page urls are not stored.\n",
20 "This allows for the next page to be retrieved in the future even when\n",
21 "there are no results.",
22 ),
23 pub async fn $fun(&mut self) -> Result<Option<Vec<T>>> {
24 let Some(ref url) = self.$direction else {
25 return Ok(None);
26 };
27
28 debug!(
29 url = url.as_str(), method = "get",
30 call_id = as_debug!(self.call_id),
31 direction = stringify!($direction);
32 "making API request"
33 );
34 let url: String = url.to_string();
35 let response = self.mastodon.authenticated(self.mastodon.client.get(&url)).send().await?;
36 match response.error_for_status() {
37 Ok(response) => {
38 let (prev, next) = get_links(&response, self.call_id)?;
39 let response: Vec<T> = read_response(response).await?;
40 if response.is_empty() && prev.is_none() && next.is_none() {
41 debug!(
42 url = url, method = "get", call_id = as_debug!(self.call_id),
43 direction = stringify!($direction);
44 "received an empty page with no links"
45 );
46 return Ok(None);
47 }
48 debug!(
49 url = url, method = "get",call_id = as_debug!(self.call_id),
50 direction = stringify!($direction),
51 prev = as_debug!(prev),
52 next = as_debug!(next),
53 response = as_serde!(response);
54 "received next pages from API"
55 );
56 self.next = next;
57 self.prev = prev;
58 Ok(Some(response))
59 }
60 Err(err) => {
61 error!(
62 err = as_debug!(err), url = url,
63 method = stringify!($method),
64 call_id = as_debug!(self.call_id);
65 "error making API request"
66 );
67 Err(err.into())
68 }
69 }
70
71 });
72 )*
73 }
74}
75
76#[derive(Debug, Clone)]
106pub struct Page<T: for<'de> Deserialize<'de> + Serialize> {
107 mastodon: Mastodon,
108 pub next: Option<Url>,
110 pub prev: Option<Url>,
112 pub initial_items: Vec<T>,
114 pub(crate) call_id: Uuid,
115}
116
117impl<'a, T: for<'de> Deserialize<'de> + Serialize> Page<T> {
118 pages! {
119 next: next_page,
120 prev: prev_page
121 }
122
123 pub(crate) async fn new(mastodon: Mastodon, response: Response, call_id: Uuid) -> Result<Self> {
125 let status = response.status();
126 if status.is_success() {
127 let (prev, next) = get_links(&response, call_id)?;
128 let initial_items = read_response(response).await?;
129 debug!(
130 initial_items = as_serde!(initial_items), prev = as_debug!(prev),
131 next = as_debug!(next), call_id = as_debug!(call_id);
132 "received first page from API call"
133 );
134 Ok(Page {
135 initial_items,
136 next,
137 prev,
138 mastodon,
139 call_id,
140 })
141 } else {
142 let response = response.json().await?;
143 Err(Error::Api { status, response })
144 }
145 }
146}
147
148impl<T: Clone + for<'de> Deserialize<'de> + Serialize> Page<T> {
149 pub fn items_iter(self) -> impl Stream<Item = T> {
177 ItemsIter::new(self).stream()
178 }
179}
180
181fn get_links(response: &Response, call_id: Uuid) -> Result<(Option<Url>, Option<Url>)> {
182 let mut prev = None;
183 let mut next = None;
184
185 if let Some(link_header) = response.headers().get(LINK) {
186 let link_header = link_header.to_str()?;
187 let raw_link_header = link_header.to_string();
188 trace!(link_header = link_header, call_id = as_debug!(call_id); "parsing link header");
189 let link_header = parse_link_header::parse(link_header)?;
190 for (rel, link) in link_header.iter() {
191 match rel.as_ref().map(|it| it.as_str()) {
192 Some("next") => next = Some(link.uri.clone()),
193 Some("prev") => prev = Some(link.uri.clone()),
194 None => debug!(link = as_debug!(link); "link header with no rel specified"),
195 Some(other) => {
196 return Err(Error::UnrecognizedRel {
197 rel: other.to_string(),
198 link: raw_link_header,
199 })
200 }
201 }
202 }
203 }
204
205 Ok((prev, next))
206}