1use crate::builder::{
48 CompoundFilter, Filter, FilterExpr, LogicalOp, Operator, SortDir, SortField, Value,
49};
50
51const MAX_CURSOR_SIZE: usize = 4 * 1024;
54
55const MAX_CURSOR_FIELDS: usize = 16;
59
60#[derive(Debug, Clone, PartialEq)]
77pub struct Cursor {
78 pub fields: Vec<(String, Value)>,
80}
81
82impl Cursor {
83 #[must_use]
85 pub fn new() -> Self {
86 Self { fields: Vec::new() }
87 }
88
89 pub fn field(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
91 self.fields.push((name.into(), value.into()));
92 self
93 }
94
95 pub fn int(self, name: impl Into<String>, value: i64) -> Self {
97 self.field(name, Value::Int(value))
98 }
99
100 pub fn string(self, name: impl Into<String>, value: impl Into<String>) -> Self {
102 self.field(name, Value::String(value.into()))
103 }
104
105 #[must_use]
109 pub fn encode(&self) -> String {
110 let json = self.to_json();
111 base64_encode(&json)
112 }
113
114 pub fn decode(encoded: &str) -> Result<Self, CursorError> {
118 if encoded.len() > MAX_CURSOR_SIZE {
120 return Err(CursorError::TooLarge);
121 }
122 let json = base64_decode(encoded).map_err(|()| CursorError::InvalidBase64)?;
123 Self::from_json(&json)
124 }
125
126 fn to_json(&self) -> String {
128 let mut parts = Vec::new();
129 for (name, value) in &self.fields {
130 let val_str = match value {
131 Value::Null => "null".to_string(),
132 Value::Bool(b) => b.to_string(),
133 Value::Int(i) => i.to_string(),
134 Value::Float(f) => f.to_string(),
135 Value::String(s) => format!("\"{}\"", escape_json(s)),
136 Value::Array(_) => continue, };
138 parts.push(format!("\"{name}\":{val_str}"));
139 }
140 format!("{{{}}}", parts.join(","))
141 }
142
143 fn from_json(json: &str) -> Result<Self, CursorError> {
145 let mut cursor = Cursor::new();
146 let json = json.trim();
147
148 if !json.starts_with('{') || !json.ends_with('}') {
149 return Err(CursorError::InvalidFormat);
150 }
151
152 let inner = &json[1..json.len() - 1];
153 if inner.is_empty() {
154 return Ok(cursor);
155 }
156
157 for pair in split_json_pairs(inner) {
159 let pair = pair.trim();
160 if pair.is_empty() {
161 continue;
162 }
163
164 let colon_idx = pair.find(':').ok_or(CursorError::InvalidFormat)?;
165 let key = pair[..colon_idx].trim();
166 let value = pair[colon_idx + 1..].trim();
167
168 if !key.starts_with('"') || !key.ends_with('"') {
170 return Err(CursorError::InvalidFormat);
171 }
172 let key = &key[1..key.len() - 1];
173
174 let parsed_value = if value == "null" {
176 Value::Null
177 } else if value == "true" {
178 Value::Bool(true)
179 } else if value == "false" {
180 Value::Bool(false)
181 } else if value.starts_with('"') && value.ends_with('"') {
182 Value::String(unescape_json(&value[1..value.len() - 1]))
183 } else if value.contains('.') {
184 value
185 .parse::<f64>()
186 .map(Value::Float)
187 .map_err(|_| CursorError::InvalidFormat)?
188 } else {
189 value
190 .parse::<i64>()
191 .map(Value::Int)
192 .map_err(|_| CursorError::InvalidFormat)?
193 };
194
195 cursor.fields.push((key.to_string(), parsed_value));
196
197 if cursor.fields.len() > MAX_CURSOR_FIELDS {
199 return Err(CursorError::TooManyFields);
200 }
201 }
202
203 Ok(cursor)
204 }
205}
206
207impl Default for Cursor {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213#[derive(Debug, Clone, PartialEq)]
215pub enum CursorError {
216 InvalidBase64,
218 InvalidFormat,
220 TooLarge,
222 TooManyFields,
224}
225
226impl std::fmt::Display for CursorError {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 match self {
229 Self::InvalidBase64 => write!(f, "Invalid base64 encoding"),
230 Self::InvalidFormat => write!(f, "Invalid cursor format"),
231 Self::TooLarge => write!(f, "Cursor exceeds maximum size"),
232 Self::TooManyFields => write!(f, "Cursor has too many fields"),
233 }
234 }
235}
236
237impl std::error::Error for CursorError {}
238
239pub trait IntoCursor {
256 fn into_cursor(self) -> Option<Cursor>;
259}
260
261impl IntoCursor for Cursor {
262 fn into_cursor(self) -> Option<Cursor> {
263 if self.fields.is_empty() {
265 None
266 } else {
267 Some(self)
268 }
269 }
270}
271
272impl IntoCursor for &str {
273 fn into_cursor(self) -> Option<Cursor> {
274 if self.is_empty() || self.len() > MAX_CURSOR_SIZE {
275 return None;
276 }
277 Cursor::decode(self).ok()
278 }
279}
280
281impl IntoCursor for String {
282 fn into_cursor(self) -> Option<Cursor> {
283 self.as_str().into_cursor()
284 }
285}
286
287impl IntoCursor for &String {
288 fn into_cursor(self) -> Option<Cursor> {
289 self.as_str().into_cursor()
290 }
291}
292
293impl<T: IntoCursor> IntoCursor for Option<T> {
294 fn into_cursor(self) -> Option<Cursor> {
295 self.and_then(IntoCursor::into_cursor)
296 }
297}
298
299#[derive(Debug, Clone, Default)]
319pub struct PageInfo {
320 pub has_next: bool,
322 pub has_prev: bool,
324 pub next_cursor: Option<String>,
326 pub prev_cursor: Option<String>,
328 pub total: Option<u64>,
330}
331
332impl PageInfo {
333 #[must_use]
337 pub fn new(count: usize, limit: usize) -> Self {
338 Self {
339 has_next: count >= limit,
340 has_prev: false,
341 next_cursor: None,
342 prev_cursor: None,
343 total: None,
344 }
345 }
346
347 #[must_use]
349 pub fn with_has_prev(mut self, has_prev: bool) -> Self {
350 self.has_prev = has_prev;
351 self
352 }
353
354 #[must_use]
356 pub fn with_next_cursor(mut self, cursor: Option<String>) -> Self {
357 self.next_cursor = cursor;
358 if self.next_cursor.is_some() {
359 self.has_next = true;
360 }
361 self
362 }
363
364 #[must_use]
366 pub fn with_prev_cursor(mut self, cursor: Option<String>) -> Self {
367 self.prev_cursor = cursor;
368 if self.prev_cursor.is_some() {
369 self.has_prev = true;
370 }
371 self
372 }
373
374 #[must_use]
376 pub fn with_total(mut self, total: u64) -> Self {
377 self.total = Some(total);
378 self
379 }
380
381 pub fn cursor_from<T, F>(item: Option<&T>, builder: F) -> Option<String>
383 where
384 F: FnOnce(&T) -> Cursor,
385 {
386 item.map(|item| builder(item).encode())
387 }
388}
389
390#[derive(Debug, Clone)]
395pub struct KeysetCondition {
396 pub sort_fields: Vec<SortField>,
398 pub cursor_values: Vec<Value>,
400 pub forward: bool,
402}
403
404impl KeysetCondition {
405 #[must_use]
407 pub fn after(sorts: &[SortField], cursor: &Cursor) -> Option<Self> {
408 Self::new(sorts, cursor, true)
409 }
410
411 #[must_use]
413 pub fn before(sorts: &[SortField], cursor: &Cursor) -> Option<Self> {
414 Self::new(sorts, cursor, false)
415 }
416
417 fn new(sorts: &[SortField], cursor: &Cursor, forward: bool) -> Option<Self> {
418 if sorts.is_empty() {
419 return None;
420 }
421
422 let mut cursor_values = Vec::new();
424 for sort in sorts {
425 let value = cursor
426 .fields
427 .iter()
428 .find(|(name, _)| name == &sort.field)
429 .map(|(_, v)| v.clone())?;
430 cursor_values.push(value);
431 }
432
433 Some(Self {
434 sort_fields: sorts.to_vec(),
435 cursor_values,
436 forward,
437 })
438 }
439
440 #[must_use]
452 pub fn to_filter_expr(&self) -> FilterExpr {
453 if self.sort_fields.is_empty() || self.cursor_values.is_empty() {
454 return FilterExpr::Simple(Filter {
456 field: "1".to_string(),
457 op: Operator::Eq,
458 value: Value::Int(1),
459 });
460 }
461
462 if self.sort_fields.len() == 1 {
463 let sort = &self.sort_fields[0];
465 let value = &self.cursor_values[0];
466 let op = self.get_operator(sort.dir);
467
468 return FilterExpr::Simple(Filter {
469 field: sort.field.clone(),
470 op,
471 value: value.clone(),
472 });
473 }
474
475 let mut or_conditions: Vec<FilterExpr> = Vec::new();
481
482 for i in 0..self.sort_fields.len() {
483 let mut and_conditions: Vec<FilterExpr> = Vec::new();
485
486 for j in 0..i {
488 and_conditions.push(FilterExpr::Simple(Filter {
489 field: self.sort_fields[j].field.clone(),
490 op: Operator::Eq,
491 value: self.cursor_values[j].clone(),
492 }));
493 }
494
495 let sort = &self.sort_fields[i];
497 let value = &self.cursor_values[i];
498 let op = self.get_operator(sort.dir);
499 and_conditions.push(FilterExpr::Simple(Filter {
500 field: sort.field.clone(),
501 op,
502 value: value.clone(),
503 }));
504
505 let condition = if and_conditions.len() == 1 {
507 and_conditions.into_iter().next().unwrap()
508 } else {
509 FilterExpr::Compound(CompoundFilter {
510 op: LogicalOp::And,
511 filters: and_conditions,
512 })
513 };
514
515 or_conditions.push(condition);
516 }
517
518 if or_conditions.len() == 1 {
520 or_conditions.into_iter().next().unwrap()
521 } else {
522 FilterExpr::Compound(CompoundFilter {
523 op: LogicalOp::Or,
524 filters: or_conditions,
525 })
526 }
527 }
528
529 fn get_operator(&self, dir: SortDir) -> Operator {
530 match (self.forward, dir) {
531 (true, SortDir::Asc) => Operator::Gt,
532 (true, SortDir::Desc) => Operator::Lt,
533 (false, SortDir::Asc) => Operator::Lt,
534 (false, SortDir::Desc) => Operator::Gt,
535 }
536 }
537}
538
539fn base64_encode(input: &str) -> String {
555 const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
556
557 let bytes = input.as_bytes();
558 let mut result = String::new();
559
560 for chunk in bytes.chunks(3) {
561 let b0 = u32::from(chunk[0]);
562 let b1 = u32::from(chunk.get(1).copied().unwrap_or(0));
563 let b2 = u32::from(chunk.get(2).copied().unwrap_or(0));
564
565 let n = (b0 << 16) | (b1 << 8) | b2;
566
567 result.push(ALPHABET[((n >> 18) & 0x3F) as usize] as char);
568 result.push(ALPHABET[((n >> 12) & 0x3F) as usize] as char);
569
570 if chunk.len() > 1 {
571 result.push(ALPHABET[((n >> 6) & 0x3F) as usize] as char);
572 }
573 if chunk.len() > 2 {
574 result.push(ALPHABET[(n & 0x3F) as usize] as char);
575 }
576 }
577
578 result
579}
580
581fn base64_decode(input: &str) -> Result<String, ()> {
586 const DECODE: [i8; 128] = {
587 let mut table = [-1i8; 128];
588 let mut i = 0u8;
589 while i < 26 {
590 table[(b'A' + i) as usize] = i as i8;
591 table[(b'a' + i) as usize] = (i + 26) as i8;
592 i += 1;
593 }
594 let mut i = 0u8;
595 while i < 10 {
596 table[(b'0' + i) as usize] = (i + 52) as i8;
597 i += 1;
598 }
599 table[b'-' as usize] = 62;
600 table[b'_' as usize] = 63;
601 table[b'+' as usize] = 62;
603 table[b'/' as usize] = 63;
604 table
605 };
606
607 let bytes: Vec<u8> = input.bytes().collect();
608 let mut result = Vec::new();
609
610 for chunk in bytes.chunks(4) {
611 let mut n = 0u32;
612 let mut valid_chars = 0;
613
614 for (i, &b) in chunk.iter().enumerate() {
615 if b as usize >= 128 {
616 return Err(());
617 }
618 let val = DECODE[b as usize];
619 if val < 0 {
620 return Err(());
621 }
622 n |= (val as u32) << (18 - i * 6);
623 valid_chars += 1;
624 }
625
626 result.push((n >> 16) as u8);
627 if valid_chars > 2 {
628 result.push((n >> 8) as u8);
629 }
630 if valid_chars > 3 {
631 result.push(n as u8);
632 }
633 }
634
635 String::from_utf8(result).map_err(|_| ())
636}
637
638fn escape_json(s: &str) -> String {
645 let mut result = String::with_capacity(s.len());
646 for c in s.chars() {
647 match c {
648 '"' => result.push_str("\\\""),
649 '\\' => result.push_str("\\\\"),
650 '\n' => result.push_str("\\n"),
651 '\r' => result.push_str("\\r"),
652 '\t' => result.push_str("\\t"),
653 '\x08' => result.push_str("\\b"), '\x0C' => result.push_str("\\f"), c if c.is_control() && (c as u32) < 0x20 => {
657 result.push_str(&format!("\\u{:04x}", c as u32));
658 },
659 c => result.push(c),
660 }
661 }
662 result
663}
664
665fn unescape_json(s: &str) -> String {
667 let mut result = String::with_capacity(s.len());
668 let mut chars = s.chars().peekable();
669 while let Some(c) = chars.next() {
670 if c == '\\' {
671 match chars.next() {
672 Some('"') => result.push('"'),
673 Some('\\') => result.push('\\'),
674 Some('/') => result.push('/'),
675 Some('n') => result.push('\n'),
676 Some('r') => result.push('\r'),
677 Some('t') => result.push('\t'),
678 Some('b') => result.push('\x08'),
679 Some('f') => result.push('\x0C'),
680 Some('u') => {
681 let mut hex = String::with_capacity(4);
683 for _ in 0..4 {
684 if let Some(h) = chars.next() {
685 hex.push(h);
686 }
687 }
688 if let Ok(code) = u32::from_str_radix(&hex, 16)
689 && let Some(ch) = char::from_u32(code)
690 {
691 result.push(ch);
692 }
693 },
694 Some(c) => {
695 result.push('\\');
696 result.push(c);
697 },
698 None => result.push('\\'),
699 }
700 } else {
701 result.push(c);
702 }
703 }
704 result
705}
706
707fn split_json_pairs(s: &str) -> Vec<&str> {
709 let mut pairs = Vec::new();
710 let mut start = 0;
711 let mut depth = 0;
712 let mut in_string = false;
713 let mut escape = false;
714
715 for (i, c) in s.char_indices() {
716 if escape {
717 escape = false;
718 continue;
719 }
720
721 match c {
722 '\\' if in_string => escape = true,
723 '"' => in_string = !in_string,
724 '{' | '[' if !in_string => depth += 1,
725 '}' | ']' if !in_string => depth -= 1,
726 ',' if !in_string && depth == 0 => {
727 pairs.push(&s[start..i]);
728 start = i + 1;
729 },
730 _ => {},
731 }
732 }
733
734 if start < s.len() {
735 pairs.push(&s[start..]);
736 }
737
738 pairs
739}
740
741impl From<i64> for Value {
746 fn from(v: i64) -> Self {
747 Value::Int(v)
748 }
749}
750
751impl From<i32> for Value {
752 fn from(v: i32) -> Self {
753 Value::Int(i64::from(v))
754 }
755}
756
757impl From<f64> for Value {
758 fn from(v: f64) -> Self {
759 Value::Float(v)
760 }
761}
762
763impl From<String> for Value {
764 fn from(v: String) -> Self {
765 Value::String(v)
766 }
767}
768
769impl From<&str> for Value {
770 fn from(v: &str) -> Self {
771 Value::String(v.to_string())
772 }
773}
774
775impl From<bool> for Value {
776 fn from(v: bool) -> Self {
777 Value::Bool(v)
778 }
779}
780
781#[cfg(test)]
782mod tests {
783 use super::*;
784
785 #[test]
786 fn test_cursor_encode_decode() {
787 let cursor = Cursor::new().int("id", 100).string("name", "Alice");
788
789 let encoded = cursor.encode();
790 let decoded = Cursor::decode(&encoded).unwrap();
791
792 assert_eq!(cursor.fields, decoded.fields);
793 }
794
795 #[test]
796 fn test_cursor_empty() {
797 let cursor = Cursor::new();
798 let encoded = cursor.encode();
799 let decoded = Cursor::decode(&encoded).unwrap();
800 assert!(decoded.fields.is_empty());
801 }
802
803 #[test]
804 fn test_cursor_with_special_chars() {
805 let cursor = Cursor::new().string("name", "Hello \"World\"");
806
807 let encoded = cursor.encode();
808 let decoded = Cursor::decode(&encoded).unwrap();
809
810 assert_eq!(cursor.fields, decoded.fields);
811 }
812
813 #[test]
814 fn test_cursor_with_float() {
815 let cursor = Cursor::new().field("score", 1.234f64);
816
817 let encoded = cursor.encode();
818 let decoded = Cursor::decode(&encoded).unwrap();
819
820 assert_eq!(decoded.fields.len(), 1);
821 match &decoded.fields[0].1 {
822 Value::Float(f) => assert!((f - 1.234).abs() < 0.001),
823 _ => panic!("Expected float"),
824 }
825 }
826
827 #[test]
828 fn test_cursor_invalid_base64() {
829 let result = Cursor::decode("not valid base64!!!");
830 assert!(matches!(result, Err(CursorError::InvalidBase64)));
831 }
832
833 #[test]
834 fn test_cursor_too_large() {
835 let oversized = "a".repeat(5 * 1024);
837 let result = Cursor::decode(&oversized);
838 assert!(matches!(result, Err(CursorError::TooLarge)));
839
840 let cursor: Option<Cursor> = oversized.as_str().into_cursor();
842 assert!(cursor.is_none());
843 }
844
845 #[test]
846 fn test_cursor_too_many_fields() {
847 let mut fields = Vec::new();
849 for i in 0..20 {
850 fields.push(format!("\"f{}\":1", i));
851 }
852 let json = format!("{{{}}}", fields.join(","));
853 let encoded = base64_encode(&json);
854
855 let result = Cursor::decode(&encoded);
856 assert!(matches!(result, Err(CursorError::TooManyFields)));
857
858 let cursor: Option<Cursor> = encoded.as_str().into_cursor();
860 assert!(cursor.is_none());
861 }
862
863 #[test]
864 fn test_page_info_basic() {
865 let info = PageInfo::new(20, 20);
866 assert!(info.has_next);
867 assert!(!info.has_prev);
868
869 let info = PageInfo::new(15, 20);
870 assert!(!info.has_next);
871 }
872
873 #[test]
874 fn test_page_info_with_cursors() {
875 let info = PageInfo::new(20, 20)
876 .with_next_cursor(Some("abc".to_string()))
877 .with_prev_cursor(Some("xyz".to_string()))
878 .with_total(100);
879
880 assert!(info.has_next);
881 assert!(info.has_prev);
882 assert_eq!(info.next_cursor, Some("abc".to_string()));
883 assert_eq!(info.prev_cursor, Some("xyz".to_string()));
884 assert_eq!(info.total, Some(100));
885 }
886
887 #[test]
888 fn test_keyset_condition_asc() {
889 let sorts = vec![SortField::new("id", SortDir::Asc)];
890 let cursor = Cursor::new().int("id", 100);
891
892 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
893 let expr = condition.to_filter_expr();
894
895 match expr {
896 FilterExpr::Simple(f) => {
897 assert_eq!(f.field, "id");
898 assert_eq!(f.op, Operator::Gt);
899 },
900 _ => panic!("Expected simple filter"),
901 }
902 }
903
904 #[test]
905 fn test_keyset_condition_desc() {
906 let sorts = vec![SortField::new("created_at", SortDir::Desc)];
907 let cursor = Cursor::new().string("created_at", "2024-01-01");
908
909 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
910 let expr = condition.to_filter_expr();
911
912 match expr {
913 FilterExpr::Simple(f) => {
914 assert_eq!(f.op, Operator::Lt);
915 },
916 _ => panic!("Expected simple filter"),
917 }
918 }
919
920 #[test]
921 fn test_keyset_condition_before() {
922 let sorts = vec![SortField::new("id", SortDir::Asc)];
923 let cursor = Cursor::new().int("id", 100);
924
925 let condition = KeysetCondition::before(&sorts, &cursor).unwrap();
926 let expr = condition.to_filter_expr();
927
928 match expr {
929 FilterExpr::Simple(f) => {
930 assert_eq!(f.op, Operator::Lt);
931 },
932 _ => panic!("Expected simple filter"),
933 }
934 }
935
936 #[test]
937 fn test_keyset_condition_multi_field_asc_asc() {
938 let sorts = vec![
941 SortField::new("created_at", SortDir::Asc),
942 SortField::new("id", SortDir::Asc),
943 ];
944 let cursor = Cursor::new()
945 .string("created_at", "2024-01-01")
946 .int("id", 100);
947
948 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
949 let expr = condition.to_filter_expr();
950
951 match expr {
953 FilterExpr::Compound(compound) => {
954 assert_eq!(compound.op, LogicalOp::Or);
955 assert_eq!(compound.filters.len(), 2);
956
957 match &compound.filters[0] {
959 FilterExpr::Simple(f) => {
960 assert_eq!(f.field, "created_at");
961 assert_eq!(f.op, Operator::Gt);
962 },
963 _ => panic!("Expected simple filter for first condition"),
964 }
965
966 match &compound.filters[1] {
968 FilterExpr::Compound(and_compound) => {
969 assert_eq!(and_compound.op, LogicalOp::And);
970 assert_eq!(and_compound.filters.len(), 2);
971 },
972 _ => panic!("Expected compound AND filter for second condition"),
973 }
974 },
975 _ => panic!("Expected compound OR filter for multi-field keyset"),
976 }
977 }
978
979 #[test]
980 fn test_keyset_condition_multi_field_desc_asc() {
981 let sorts = vec![
983 SortField::new("created_at", SortDir::Desc),
984 SortField::new("id", SortDir::Asc),
985 ];
986 let cursor = Cursor::new()
987 .string("created_at", "2024-01-01")
988 .int("id", 100);
989
990 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
991 let expr = condition.to_filter_expr();
992
993 match expr {
994 FilterExpr::Compound(compound) => {
995 assert_eq!(compound.op, LogicalOp::Or);
996
997 match &compound.filters[0] {
999 FilterExpr::Simple(f) => {
1000 assert_eq!(f.field, "created_at");
1001 assert_eq!(f.op, Operator::Lt); },
1003 _ => panic!("Expected simple filter"),
1004 }
1005 },
1006 _ => panic!("Expected compound filter"),
1007 }
1008 }
1009
1010 #[test]
1011 fn test_keyset_condition_three_fields() {
1012 let sorts = vec![
1017 SortField::new("a", SortDir::Asc),
1018 SortField::new("b", SortDir::Asc),
1019 SortField::new("c", SortDir::Asc),
1020 ];
1021 let cursor = Cursor::new().int("a", 1).int("b", 2).int("c", 3);
1022
1023 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
1024 let expr = condition.to_filter_expr();
1025
1026 match expr {
1027 FilterExpr::Compound(compound) => {
1028 assert_eq!(compound.op, LogicalOp::Or);
1029 assert_eq!(compound.filters.len(), 3);
1030
1031 match &compound.filters[0] {
1033 FilterExpr::Simple(f) => {
1034 assert_eq!(f.field, "a");
1035 assert_eq!(f.op, Operator::Gt);
1036 },
1037 _ => panic!("Expected simple filter"),
1038 }
1039
1040 match &compound.filters[1] {
1042 FilterExpr::Compound(and_compound) => {
1043 assert_eq!(and_compound.filters.len(), 2);
1044 },
1045 _ => panic!("Expected compound filter"),
1046 }
1047
1048 match &compound.filters[2] {
1050 FilterExpr::Compound(and_compound) => {
1051 assert_eq!(and_compound.filters.len(), 3);
1052 },
1053 _ => panic!("Expected compound filter"),
1054 }
1055 },
1056 _ => panic!("Expected compound filter"),
1057 }
1058 }
1059
1060 #[test]
1061 fn test_base64_roundtrip() {
1062 let original = "{\"id\":100,\"name\":\"test\"}";
1063 let encoded = base64_encode(original);
1064 let decoded = base64_decode(&encoded).unwrap();
1065 assert_eq!(original, decoded);
1066 }
1067
1068 #[test]
1069 fn test_cursor_from_helper() {
1070 #[derive(Debug)]
1071 struct User {
1072 id: i64,
1073 }
1074
1075 let user = User { id: 42 };
1076 let cursor = PageInfo::cursor_from(Some(&user), |u| Cursor::new().int("id", u.id));
1077
1078 assert!(cursor.is_some());
1079 let decoded = Cursor::decode(&cursor.unwrap()).unwrap();
1080 assert_eq!(decoded.fields[0], ("id".to_string(), Value::Int(42)));
1081 }
1082
1083 #[test]
1084 fn test_value_from_conversions() {
1085 let _: Value = 42i64.into();
1086 let _: Value = 42i32.into();
1087 let _: Value = 1.234f64.into();
1088 let _: Value = "hello".into();
1089 let _: Value = String::from("world").into();
1090 let _: Value = true.into();
1091 }
1092
1093 #[test]
1098 fn test_cursor_exactly_at_max_fields() {
1099 let mut fields = Vec::new();
1101 for i in 0..16 {
1102 fields.push(format!("\"f{}\":1", i));
1103 }
1104 let json = format!("{{{}}}", fields.join(","));
1105 let encoded = base64_encode(&json);
1106
1107 let result = Cursor::decode(&encoded);
1108 assert!(
1109 result.is_ok(),
1110 "Cursor with exactly 16 fields should succeed"
1111 );
1112 assert_eq!(result.unwrap().fields.len(), 16);
1113 }
1114
1115 #[test]
1116 fn test_cursor_one_under_max_fields() {
1117 let mut fields = Vec::new();
1119 for i in 0..15 {
1120 fields.push(format!("\"f{}\":1", i));
1121 }
1122 let json = format!("{{{}}}", fields.join(","));
1123 let encoded = base64_encode(&json);
1124
1125 let result = Cursor::decode(&encoded);
1126 assert!(result.is_ok(), "Cursor with 15 fields should succeed");
1127 assert_eq!(result.unwrap().fields.len(), 15);
1128 }
1129
1130 #[test]
1131 fn test_cursor_one_over_max_fields() {
1132 let mut fields = Vec::new();
1134 for i in 0..17 {
1135 fields.push(format!("\"f{}\":1", i));
1136 }
1137 let json = format!("{{{}}}", fields.join(","));
1138 let encoded = base64_encode(&json);
1139
1140 let result = Cursor::decode(&encoded);
1141 assert!(matches!(result, Err(CursorError::TooManyFields)));
1142 }
1143
1144 #[test]
1145 fn test_cursor_near_max_size() {
1146 let long_value = "x".repeat(200);
1150 let cursor = Cursor::new()
1151 .string("f1", &long_value)
1152 .string("f2", &long_value)
1153 .string("f3", &long_value)
1154 .string("f4", &long_value);
1155
1156 let encoded = cursor.encode();
1157 assert!(encoded.len() < 4096, "Cursor should be under 4KB limit");
1158
1159 let decoded = Cursor::decode(&encoded);
1161 assert!(decoded.is_ok());
1162 }
1163
1164 #[test]
1165 fn test_cursor_exactly_at_max_size_boundary() {
1166 let oversized = "a".repeat(4097);
1169 let result = Cursor::decode(&oversized);
1170 assert!(matches!(result, Err(CursorError::TooLarge)));
1171
1172 let at_limit = "a".repeat(4096);
1174 let result = Cursor::decode(&at_limit);
1175 assert!(!matches!(result, Err(CursorError::TooLarge)));
1177 }
1178
1179 #[test]
1180 fn test_into_cursor_boundary_behavior() {
1181 let cursor: Option<Cursor> = "".into_cursor();
1183 assert!(cursor.is_none(), "Empty string should return None");
1184
1185 let oversized = "a".repeat(4097);
1187 let cursor: Option<Cursor> = oversized.as_str().into_cursor();
1188 assert!(cursor.is_none(), "Oversized cursor should return None");
1189 }
1190
1191 #[test]
1192 fn test_cursor_with_various_value_types() {
1193 let cursor = Cursor::new()
1195 .int("int_field", 42)
1196 .string("str_field", "hello")
1197 .field("float_field", 1.234f64)
1198 .field("bool_field", true);
1199
1200 let encoded = cursor.encode();
1201 let decoded = Cursor::decode(&encoded).unwrap();
1202
1203 assert_eq!(decoded.fields.len(), 4);
1204
1205 assert!(matches!(
1207 decoded.fields.iter().find(|(k, _)| k == "int_field"),
1208 Some((_, Value::Int(42)))
1209 ));
1210 assert!(matches!(
1211 decoded.fields.iter().find(|(k, _)| k == "str_field"),
1212 Some((_, Value::String(s))) if s == "hello"
1213 ));
1214 }
1215
1216 #[test]
1217 fn test_cursor_with_special_json_characters() {
1218 let cursor = Cursor::new()
1220 .string("quotes", "say \"hello\"")
1221 .string("backslash", "path\\to\\file")
1222 .string("newline", "line1\nline2");
1223
1224 let encoded = cursor.encode();
1225 let decoded = Cursor::decode(&encoded).unwrap();
1226
1227 assert_eq!(decoded.fields.len(), 3);
1228 }
1229
1230 #[test]
1231 fn test_keyset_with_missing_cursor_field() {
1232 let sorts = vec![SortField::new("missing_field", SortDir::Asc)];
1234 let cursor = Cursor::new().int("id", 100);
1235
1236 let condition = KeysetCondition::after(&sorts, &cursor);
1237 assert!(
1238 condition.is_none(),
1239 "Should return None when cursor missing required field"
1240 );
1241 }
1242
1243 #[test]
1244 fn test_keyset_with_empty_sorts() {
1245 let cursor = Cursor::new().int("id", 100);
1246 let condition = KeysetCondition::after(&[], &cursor);
1247 assert!(
1248 condition.is_none(),
1249 "Should return None for empty sort list"
1250 );
1251 }
1252}