use juniper_relay_helpers::{Cursor, OffsetCursor, PageInfo, PageRequest};
#[derive(Debug, Clone)]
pub struct PaginationMetadata {
pub total_count: i32,
pub page_request: Option<PageRequest>,
}
pub trait CursorProvider {
fn get_cursor_for_item<T>(
&self,
metadata: &PaginationMetadata,
item_idx: i32,
item: &T
) -> impl Cursor;
fn get_page_info<T>(&self, metadata: &PaginationMetadata, items: &[T]) -> PageInfo;
}
pub struct OffsetCursorProvider;
impl CursorProvider for OffsetCursorProvider {
fn get_cursor_for_item<T>(&self, metadata: &PaginationMetadata, item_idx: i32, _item: &T) -> impl Cursor {
let mut offset_adjust = 0;
let default_cursor = OffsetCursor::default();
let current_cursor = match &metadata.page_request {
Some(pr) => match pr.parsed_cursor() {
Ok(c) => match c {
Some(cc) => {
offset_adjust = 1;
cc
},
None => default_cursor
},
Err(_) => default_cursor
},
None => default_cursor
};
OffsetCursor {
offset: current_cursor.offset + offset_adjust + item_idx,
first: current_cursor.first
}
}
fn get_page_info<T>(&self, metadata: &PaginationMetadata, items: &[T]) -> PageInfo {
let default_cursor = OffsetCursor::default();
let current_cursor = match &metadata.page_request {
Some(pr) => match pr.parsed_cursor() {
Ok(c) => c.unwrap_or(default_cursor),
Err(_) => default_cursor
},
None => default_cursor
};
let has_next_page = if let Some(pr) = &metadata.page_request {
if let Some(first) = pr.first {
current_cursor.offset + first < metadata.total_count
} else {
false
}
} else {
false
};
let last_index = items.len() - 1;
PageInfo {
has_prev_page: current_cursor.offset > 0,
has_next_page,
start_cursor: if !items.is_empty() {
Some(
self.get_cursor_for_item(metadata, 0, &items[0])
.to_encoded_string()
)
} else {
None
},
end_cursor: if !items.is_empty() {
Some(
self.get_cursor_for_item(metadata, last_index as i32, &items[last_index])
.to_encoded_string()
)
} else {
None
},
}
}
}
impl Default for OffsetCursorProvider {
fn default() -> Self {
Self::new()
}
}
impl OffsetCursorProvider {
pub fn new() -> Self {
OffsetCursorProvider
}
}
#[cfg(test)]
mod tests {
mod offset_cursor_provider {
use crate::{OffsetCursorProvider, PaginationMetadata, CursorProvider, Cursor, OffsetCursor, PageRequest};
#[derive(Debug, Clone)]
struct Location {
#[allow(dead_code)]
name: String
}
fn data() -> Vec<Location> {
vec![
Location { name: "LumiƩre".to_owned() },
Location { name: "Flying Waters".to_owned() }
]
}
#[test]
fn test_page_info_no_request() {
let p = OffsetCursorProvider::new();
let pi = p.get_page_info(&PaginationMetadata {
total_count: 2,
page_request: None
}, &data());
assert!(!pi.has_prev_page);
assert!(!pi.has_next_page);
assert_eq!(pi.start_cursor, Some(OffsetCursor { offset: 0, first: None }.to_encoded_string()));
assert_eq!(pi.end_cursor, Some(OffsetCursor { offset: 1, first: None }.to_encoded_string()));
}
#[test]
fn test_page_info_no_request_mismatch_results_count() {
let p = OffsetCursorProvider::new();
let pi = p.get_page_info(&PaginationMetadata {
total_count: 27,
page_request: None
}, &data());
assert!(!pi.has_prev_page);
assert!(!pi.has_next_page);
assert_eq!(pi.start_cursor, Some(OffsetCursor { offset: 0, first: None }.to_encoded_string()));
assert_eq!(pi.end_cursor, Some(OffsetCursor { offset: 1, first: None }.to_encoded_string()));
}
#[test]
fn test_page_info_has_request_first_page() {
let p = OffsetCursorProvider::new();
let pi = p.get_page_info(&PaginationMetadata {
total_count: 27,
page_request: Some(
PageRequest {
first: Some(10),
after: None
}
)
}, &data());
assert!(!pi.has_prev_page);
assert!(pi.has_next_page);
assert_eq!(pi.start_cursor, Some(OffsetCursor { offset: 0, first: None }.to_encoded_string()));
assert_eq!(pi.end_cursor, Some(OffsetCursor { offset: 1, first: None }.to_encoded_string()));
}
#[test]
fn test_page_info_paginating_through_set() {
let p = OffsetCursorProvider::new();
let total_items = 13;
let data = vec![
Location { name: "LumiƩre".to_owned() },
Location { name: "Spring Meadows".to_owned() },
Location { name: "Flying Waters".to_owned() },
Location { name: "Gestral Village".to_owned() },
Location { name: "Stone Wave Cliffs".to_owned() }
];
let pi1 = p.get_page_info(&PaginationMetadata {
total_count: total_items,
page_request: Some(
PageRequest {
first: Some(5),
after: None
}
)
}, &data);
assert!(!pi1.has_prev_page);
assert!(pi1.has_next_page);
assert_eq!(pi1.start_cursor, Some(OffsetCursor { offset: 0, first: None }.to_encoded_string()));
assert_eq!(pi1.end_cursor, Some(OffsetCursor { offset: 4, first: None }.to_encoded_string()));
let pi2 = p.get_page_info(&PaginationMetadata {
total_count: total_items,
page_request: Some(
PageRequest {
first: Some(5),
after: pi1.end_cursor.clone()
}
)
}, &data);
assert!(pi2.has_prev_page);
assert!(pi2.has_next_page);
assert_eq!(pi2.start_cursor, Some(OffsetCursor { offset: 5, first: None }.to_encoded_string()));
assert_eq!(pi2.end_cursor, Some(OffsetCursor { offset: 9, first: None }.to_encoded_string()));
let pi3 = p.get_page_info(&PaginationMetadata {
total_count: total_items,
page_request: Some(
PageRequest {
first: Some(5),
after: pi2.end_cursor.clone()
}
)
}, &[data[0].clone(), data[1].clone(), data[2].clone()]);
assert!(pi3.has_prev_page);
assert!(!pi3.has_next_page);
assert_eq!(pi3.start_cursor, Some(OffsetCursor { offset: 10, first: None }.to_encoded_string()));
assert_eq!(pi3.end_cursor, Some(OffsetCursor { offset: 12, first: None }.to_encoded_string()));
}
}
}