use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Paginated<T> {
pub data: Vec<T>,
#[serde(default)]
pub has_more: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub first_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PaginatedNextPage<T> {
pub data: Vec<T>,
#[serde(default)]
pub has_more: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_page: Option<String>,
}
impl<T> PaginatedNextPage<T> {
pub fn next_cursor(&self) -> Option<&str> {
self.next_page.as_deref()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl<T> Paginated<T> {
pub fn next_after(&self) -> Option<&str> {
if self.has_more {
self.last_id.as_deref()
} else {
None
}
}
pub fn next_before(&self) -> Option<&str> {
self.first_id.as_deref()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use serde_json::json;
#[test]
fn paginated_round_trips_full_envelope() {
let p = Paginated {
data: vec!["a".to_owned(), "b".to_owned()],
has_more: true,
first_id: Some("first".into()),
last_id: Some("last".into()),
};
let v = serde_json::to_value(&p).unwrap();
assert_eq!(
v,
json!({
"data": ["a", "b"],
"has_more": true,
"first_id": "first",
"last_id": "last"
})
);
let parsed: Paginated<String> = serde_json::from_value(v).unwrap();
assert_eq!(parsed, p);
}
#[test]
fn paginated_tolerates_missing_optional_fields() {
let raw = json!({"data": [1, 2, 3]});
let p: Paginated<i32> = serde_json::from_value(raw).unwrap();
assert_eq!(p.data, vec![1, 2, 3]);
assert!(!p.has_more);
assert_eq!(p.first_id, None);
assert_eq!(p.last_id, None);
}
#[test]
fn next_after_returns_last_id_only_when_more_pages() {
let p = Paginated::<i32> {
data: vec![1],
has_more: true,
first_id: Some("f".into()),
last_id: Some("l".into()),
};
assert_eq!(p.next_after(), Some("l"));
let p_done = Paginated::<i32> {
has_more: false,
..p
};
assert_eq!(p_done.next_after(), None);
}
}