use reqwest::header::HeaderMap; /// A single page of items along with parsed pagination headers. Forgejo /// follows Gitea's convention: `Link` header in RFC 5988 style plus /// `X-Total-Count`. pub struct Page { pub items: Vec, #[allow(dead_code)] pub next: Option, #[allow(dead_code)] pub prev: Option, #[allow(dead_code)] pub last: Option, #[allow(dead_code)] pub first: Option, #[allow(dead_code)] pub total: Option, } impl Page { pub fn from_headers(items: Vec, headers: &HeaderMap) -> Self { let mut next = None; let mut prev = None; let mut last = None; let mut first = None; if let Some(link) = headers.get(reqwest::header::LINK) { if let Ok(link_str) = link.to_str() { for (url, rel) in parse_link_header(link_str) { match rel.as_str() { "next" => next = Some(url), "prev" => prev = Some(url), "last" => last = Some(url), "first" => first = Some(url), _ => {} } } } } let total = headers .get("x-total-count") .and_then(|v| v.to_str().ok()) .and_then(|v| v.parse().ok()); Self { items, next, prev, last, first, total, } } } fn parse_link_header(value: &str) -> Vec<(String, String)> { let mut out = Vec::new(); for part in value.split(',') { let part = part.trim(); // `; rel="name"` let Some((url_part, params)) = part.split_once(';') else { continue; }; let url = url_part.trim().trim_start_matches('<').trim_end_matches('>'); let rel = params .split(';') .map(str::trim) .find_map(|p| { let (k, v) = p.split_once('=')?; if k.trim().eq_ignore_ascii_case("rel") { Some(v.trim().trim_matches('"').to_string()) } else { None } }); if let Some(rel) = rel { out.push((url.to_string(), rel)); } } out }