paginator_utils/
cursor.rs

1use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
5pub struct Cursor {
6    pub field: String,
7    pub value: CursorValue,
8    pub direction: CursorDirection,
9}
10
11#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
12#[serde(rename_all = "lowercase")]
13pub enum CursorDirection {
14    After,
15    Before,
16}
17
18#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
19#[serde(untagged)]
20pub enum CursorValue {
21    String(String),
22    Int(i64),
23    Float(f64),
24    /// UUID value stored as string, will be cast to UUID in SQL
25    Uuid(String),
26}
27
28impl Cursor {
29    pub fn new(field: String, value: CursorValue, direction: CursorDirection) -> Self {
30        Self {
31            field,
32            value,
33            direction,
34        }
35    }
36
37    pub fn encode(&self) -> Result<String, String> {
38        let json = serde_json::to_string(self).map_err(|e| e.to_string())?;
39        Ok(BASE64.encode(json.as_bytes()))
40    }
41
42    pub fn decode(encoded: &str) -> Result<Self, String> {
43        let decoded = BASE64.decode(encoded).map_err(|e| e.to_string())?;
44        let json = String::from_utf8(decoded).map_err(|e| e.to_string())?;
45        serde_json::from_str(&json).map_err(|e| e.to_string())
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_cursor_encode_decode_string() {
55        let cursor = Cursor::new(
56            "id".to_string(),
57            CursorValue::String("abc123".to_string()),
58            CursorDirection::After,
59        );
60        let encoded = cursor.encode().unwrap();
61        let decoded = Cursor::decode(&encoded).unwrap();
62        assert_eq!(cursor, decoded);
63    }
64
65    #[test]
66    fn test_cursor_encode_decode_int() {
67        let cursor = Cursor::new(
68            "id".to_string(),
69            CursorValue::Int(12345),
70            CursorDirection::Before,
71        );
72        let encoded = cursor.encode().unwrap();
73        let decoded = Cursor::decode(&encoded).unwrap();
74        assert_eq!(cursor, decoded);
75    }
76
77    #[test]
78    fn test_cursor_encode_decode_float() {
79        let cursor = Cursor::new(
80            "timestamp".to_string(),
81            CursorValue::Float(1234567890.123),
82            CursorDirection::After,
83        );
84        let encoded = cursor.encode().unwrap();
85        let decoded = Cursor::decode(&encoded).unwrap();
86        assert_eq!(cursor, decoded);
87    }
88}