//! Wrapper model for progressively fetched items use std::ops::Not; use serde::{Deserialize, Serialize}; /// Wrapper around progressively fetched items /// /// The paginator is a wrapper around a list of items that are fetched /// in pages from the YouTube API (e.g. playlist items, /// video recommendations or comments). #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[non_exhaustive] pub struct Paginator { /// Total number of items if finite and known. /// /// Note that this number may not be 100% accurate, as this is the /// number returned by the YouTube API at the initial fetch. /// /// It is intended to be shown to the user (e.g. 1261 comments, /// 18 Videos) and for progress estimation. /// /// Don't use this number to check if all items were fetched or for /// iterating over the items. pub count: Option, /// Content of the paginator pub items: Vec, /// The continuation token is passed to the YouTube API to fetch /// more items. /// /// If it is None, it means that no more items can be fetched. pub ctoken: Option, /// YouTube visitor data. Required for fetching the startpage #[serde(skip_serializing_if = "Option::is_none")] pub visitor_data: Option, /// YouTube API endpoint to fetch continuations from pub endpoint: ContinuationEndpoint, /// True if the paginator should be fetched with YouTube credentials #[serde(default, skip_serializing_if = "<&bool>::not")] pub authenticated: bool, } impl Default for Paginator { fn default() -> Self { Self { count: Some(0), items: Vec::new(), ctoken: None, visitor_data: None, endpoint: ContinuationEndpoint::Browse, authenticated: false, } } } /// YouTube API endpoint to fetch continuations from #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] #[non_exhaustive] #[allow(missing_docs)] pub enum ContinuationEndpoint { Browse, Search, Next, MusicBrowse, MusicSearch, MusicNext, } impl ContinuationEndpoint { pub(crate) fn as_str(self) -> &'static str { match self { ContinuationEndpoint::Browse | ContinuationEndpoint::MusicBrowse => "browse", ContinuationEndpoint::Search | ContinuationEndpoint::MusicSearch => "search", ContinuationEndpoint::Next | ContinuationEndpoint::MusicNext => "next", } } pub(crate) fn is_music(self) -> bool { matches!( self, ContinuationEndpoint::MusicBrowse | ContinuationEndpoint::MusicSearch | ContinuationEndpoint::MusicNext ) } } impl Paginator { pub(crate) fn new(count: Option, items: Vec, ctoken: Option) -> Self { Self::new_ext( count, items, ctoken, None, ContinuationEndpoint::Browse, false, ) } pub(crate) fn new_ext( count: Option, items: Vec, ctoken: Option, visitor_data: Option, endpoint: ContinuationEndpoint, authenticated: bool, ) -> Self { Self { count: match ctoken { Some(_) => count, None => items.len().try_into().ok(), }, items, ctoken, visitor_data, endpoint, authenticated, } } /// Check if the paginator is exhausted, meaning that no more /// items can be fetched. /// /// Equivalent to `paginator.ctoken.is_none()`. pub fn is_exhausted(&self) -> bool { self.ctoken.is_none() } /// Check if the paginator does not contain any data, meaning that it /// is exhausted and does not contain any items. pub fn is_empty(&self) -> bool { self.items.is_empty() && self.is_exhausted() } }