use std::ops::Not;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct Paginator<T> {
pub count: Option<u64>,
pub items: Vec<T>,
pub ctoken: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub visitor_data: Option<String>,
pub endpoint: ContinuationEndpoint,
#[serde(default, skip_serializing_if = "<&bool>::not")]
pub authenticated: bool,
}
impl<T> Default for Paginator<T> {
fn default() -> Self {
Self {
count: Some(0),
items: Vec::new(),
ctoken: None,
visitor_data: None,
endpoint: ContinuationEndpoint::Browse,
authenticated: false,
}
}
}
#[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<T> Paginator<T> {
pub(crate) fn new(count: Option<u64>, items: Vec<T>, ctoken: Option<String>) -> Self {
Self::new_ext(
count,
items,
ctoken,
None,
ContinuationEndpoint::Browse,
false,
)
}
pub(crate) fn new_ext(
count: Option<u64>,
items: Vec<T>,
ctoken: Option<String>,
visitor_data: Option<String>,
endpoint: ContinuationEndpoint,
authenticated: bool,
) -> Self {
Self {
count: match ctoken {
Some(_) => count,
None => items.len().try_into().ok(),
},
items,
ctoken,
visitor_data,
endpoint,
authenticated,
}
}
pub fn is_exhausted(&self) -> bool {
self.ctoken.is_none()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty() && self.is_exhausted()
}
}