use crate::comm::ExecuteContext;
use crate::errors::Result;
use crate::interceptor::shared::InterceptorBase;
use crate::interceptor::{InterceptorType, OperationType};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorDirection {
Forward,
Backward,
}
#[derive(Debug, Clone)]
pub struct CursorPaginationRequest {
pub cursor: Option<String>,
pub limit: u64,
pub direction: CursorDirection,
pub cursor_column: String,
}
impl CursorPaginationRequest {
pub fn new(cursor: Option<String>, limit: u64, direction: CursorDirection) -> Self {
Self {
cursor,
limit,
direction,
cursor_column: "id".to_string(),
}
}
pub fn with_cursor_column(mut self, column: &str) -> Self {
self.cursor_column = column.to_string();
self
}
}
#[derive(Debug, Clone)]
pub struct CursorPaginationResult {
pub has_more: bool,
pub next_cursor: Option<String>,
pub prev_cursor: Option<String>,
}
impl CursorPaginationResult {
pub fn new(has_more: bool, next_cursor: Option<String>, prev_cursor: Option<String>) -> Self {
Self {
has_more,
next_cursor,
prev_cursor,
}
}
}
pub struct CursorPaginationInterceptor {
default_limit: u64,
max_limit: u64,
}
impl Default for CursorPaginationInterceptor {
fn default() -> Self {
Self::new()
}
}
impl CursorPaginationInterceptor {
pub fn new() -> Self {
Self {
default_limit: 10,
max_limit: 1000,
}
}
pub fn with_default_limit(mut self, limit: u64) -> Self {
self.default_limit = limit;
self
}
pub fn with_max_limit(mut self, limit: u64) -> Self {
self.max_limit = limit;
self
}
fn normalize_request(&self, req: &CursorPaginationRequest) -> CursorPaginationRequest {
let limit = if req.limit == 0 {
self.default_limit
} else {
req.limit.min(self.max_limit)
};
CursorPaginationRequest {
cursor: req.cursor.clone(),
limit,
direction: req.direction,
cursor_column: req.cursor_column.clone(),
}
}
}
impl InterceptorBase for CursorPaginationInterceptor {
fn name(&self) -> &'static str {
"cursor_pagination"
}
fn interceptor_type(&self) -> InterceptorType {
InterceptorType::Pagination
}
fn order(&self) -> i32 {
35 }
fn supports_operation(&self, operation: &OperationType) -> bool {
matches!(operation, OperationType::Select)
}
}
#[cfg(any(
feature = "mysql-sync",
feature = "postgres-sync",
feature = "sqlite-sync",
feature = "oracle-sync",
feature = "mssql-sync"
))]
impl crate::interceptor::blocking::AkitaInterceptor for CursorPaginationInterceptor {
fn before_execute(&self, ctx: &mut ExecuteContext) -> Result<()> {
let cursor_pagination_json = ctx.get_metadata("cursor_pagination");
if let Some(_cursor_pagination) = cursor_pagination_json {
ctx.set_metadata("cursor_pagination_active".to_string(), "true".to_string());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cursor_pagination_request() {
let req =
CursorPaginationRequest::new(Some("123".to_string()), 10, CursorDirection::Forward);
assert_eq!(req.cursor, Some("123".to_string()));
assert_eq!(req.limit, 10);
assert_eq!(req.direction, CursorDirection::Forward);
assert_eq!(req.cursor_column, "id");
}
#[test]
fn test_cursor_pagination_request_first_page() {
let req = CursorPaginationRequest::new(None, 10, CursorDirection::Forward);
assert_eq!(req.cursor, None);
}
#[test]
fn test_cursor_pagination_request_backward() {
let req =
CursorPaginationRequest::new(Some("123".to_string()), 10, CursorDirection::Backward);
assert_eq!(req.direction, CursorDirection::Backward);
}
#[test]
fn test_cursor_pagination_request_custom_column() {
let req =
CursorPaginationRequest::new(Some("123".to_string()), 10, CursorDirection::Forward)
.with_cursor_column("created_at");
assert_eq!(req.cursor_column, "created_at");
}
#[test]
fn test_cursor_pagination_result() {
let result =
CursorPaginationResult::new(true, Some("456".to_string()), Some("123".to_string()));
assert!(result.has_more);
assert_eq!(result.next_cursor, Some("456".to_string()));
assert_eq!(result.prev_cursor, Some("123".to_string()));
}
#[test]
fn test_cursor_pagination_interceptor() {
let interceptor = CursorPaginationInterceptor::new();
assert_eq!(interceptor.name(), "cursor_pagination");
assert_eq!(interceptor.interceptor_type(), InterceptorType::Pagination);
assert_eq!(interceptor.order(), 35);
assert!(interceptor.supports_operation(&OperationType::Select));
assert!(!interceptor
.supports_operation(&OperationType::Insert(akita_core::InsertType::SingleInsert)));
}
#[test]
fn test_normalize_request() {
let interceptor = CursorPaginationInterceptor::new()
.with_default_limit(20)
.with_max_limit(100);
let req = CursorPaginationRequest::new(None, 0, CursorDirection::Forward);
let normalized = interceptor.normalize_request(&req);
assert_eq!(normalized.limit, 20);
let req = CursorPaginationRequest::new(None, 200, CursorDirection::Forward);
let normalized = interceptor.normalize_request(&req);
assert_eq!(normalized.limit, 100);
}
}