1use crate::value::Value;
51use std::fmt::Debug;
52use std::sync::RwLock;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum WhereOp {
61 Eq,
63 Ne,
65 Gt,
67 Ge,
69 Lt,
71 Le,
73 Like,
75 In,
77 NotIn,
79 IsNull,
81 IsNotNull,
83 Between,
85}
86
87impl WhereOp {
88 pub fn name(&self) -> &'static str {
90 match self {
91 WhereOp::Eq => "eq",
92 WhereOp::Ne => "ne",
93 WhereOp::Gt => "gt",
94 WhereOp::Ge => "ge",
95 WhereOp::Lt => "lt",
96 WhereOp::Le => "le",
97 WhereOp::Like => "like",
98 WhereOp::In => "in",
99 WhereOp::NotIn => "not_in",
100 WhereOp::IsNull => "is_null",
101 WhereOp::IsNotNull => "is_not_null",
102 WhereOp::Between => "between",
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct WhereCondition {
110 pub field: String,
111 pub op: WhereOp,
112 pub value: Value,
113 pub extra_values: Vec<Value>,
115}
116
117impl WhereCondition {
118 pub fn new(field: impl Into<String>, op: WhereOp, value: Value) -> Self {
120 Self {
121 field: field.into(),
122 op,
123 value,
124 extra_values: Vec::new(),
125 }
126 }
127
128 pub fn null_check(field: impl Into<String>, op: WhereOp) -> Self {
130 Self {
131 field: field.into(),
132 op,
133 value: Value::Null,
134 extra_values: Vec::new(),
135 }
136 }
137
138 pub fn in_op(field: impl Into<String>, op: WhereOp, values: Vec<Value>) -> Self {
140 Self {
141 field: field.into(),
142 op,
143 value: Value::Null,
144 extra_values: values,
145 }
146 }
147
148 pub fn between(field: impl Into<String>, low: Value, high: Value) -> Self {
150 Self {
151 field: field.into(),
152 op: WhereOp::Between,
153 value: low,
154 extra_values: vec![high],
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
165pub struct PageResult<T> {
166 pub items: Vec<T>,
167 pub total: u64,
168 pub page: u64,
169 pub page_size: u64,
170}
171
172impl<T> PageResult<T> {
173 pub fn new(items: Vec<T>, total: u64, page: u64, page_size: u64) -> Self {
175 Self {
176 items,
177 total,
178 page,
179 page_size,
180 }
181 }
182
183 pub fn total_pages(&self) -> u64 {
185 if self.page_size == 0 {
186 return 0;
187 }
188 self.total.div_ceil(self.page_size)
189 }
190
191 pub fn has_next(&self) -> bool {
193 self.page < self.total_pages()
194 }
195
196 pub fn has_prev(&self) -> bool {
198 self.page > 1
199 }
200
201 pub fn is_empty(&self) -> bool {
203 self.items.is_empty()
204 }
205
206 pub fn len(&self) -> usize {
208 self.items.len()
209 }
210
211 pub fn map<U, F: Fn(T) -> U>(self, f: F) -> PageResult<U> {
213 PageResult {
214 items: self.items.into_iter().map(f).collect(),
215 total: self.total,
216 page: self.page,
217 page_size: self.page_size,
218 }
219 }
220}
221
222#[derive(Debug, Clone)]
242pub struct BatchUpdateResult<E> {
243 pub updated: Vec<E>,
245 pub skipped: usize,
247}
248
249impl<E> BatchUpdateResult<E> {
250 pub fn new(updated: Vec<E>, skipped: usize) -> Self {
252 Self { updated, skipped }
253 }
254
255 pub fn updated_count(&self) -> usize {
257 self.updated.len()
258 }
259
260 pub fn has_skipped(&self) -> bool {
262 self.skipped > 0
263 }
264
265 pub fn all_updated(&self) -> bool {
267 self.skipped == 0
268 }
269
270 pub fn total(&self) -> usize {
272 self.updated.len() + self.skipped
273 }
274
275 pub fn map<U, F: Fn(E) -> U>(self, f: F) -> BatchUpdateResult<U> {
277 BatchUpdateResult {
278 updated: self.updated.into_iter().map(f).collect(),
279 skipped: self.skipped,
280 }
281 }
282}
283
284impl<E> Default for BatchUpdateResult<E> {
285 fn default() -> Self {
286 Self {
287 updated: Vec::new(),
288 skipped: 0,
289 }
290 }
291}
292
293#[derive(Debug, Clone, PartialEq)]
299pub enum RepositoryError {
300 NotFound,
302 DatabaseError(String),
304 InvalidEntity(String),
306 Other(String),
308}
309
310impl std::fmt::Display for RepositoryError {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 match self {
313 RepositoryError::NotFound => write!(f, "entity not found"),
314 RepositoryError::DatabaseError(msg) => write!(f, "database error: {}", msg),
315 RepositoryError::InvalidEntity(msg) => write!(f, "invalid entity: {}", msg),
316 RepositoryError::Other(msg) => write!(f, "repository error: {}", msg),
317 }
318 }
319}
320
321impl std::error::Error for RepositoryError {}
322
323pub type RepositoryResult<T> = Result<T, RepositoryError>;
325
326pub trait Repository<E>: Send + Sync {
335 type Key: Clone + Debug + PartialEq + Send + Sync;
337
338 fn key_of(&self, entity: &E) -> Self::Key;
340
341 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>>;
343
344 fn find_all(&self) -> RepositoryResult<Vec<E>>;
346
347 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>>;
349
350 fn find_one_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Option<E>> {
352 let mut items = self.find_by(conditions)?;
353 if items.is_empty() {
354 Ok(None)
355 } else {
356 Ok(Some(items.remove(0)))
357 }
358 }
359
360 fn save(&self, entity: E) -> RepositoryResult<E>;
364
365 fn save_many(&self, entities: Vec<E>) -> RepositoryResult<Vec<E>> {
367 let mut saved = Vec::with_capacity(entities.len());
368 for e in entities {
369 saved.push(self.save(e)?);
370 }
371 Ok(saved)
372 }
373
374 fn batch_update(&self, entities: Vec<E>) -> RepositoryResult<BatchUpdateResult<E>> {
392 let mut updated = Vec::with_capacity(entities.len());
393 let mut skipped = 0usize;
394 for e in entities {
395 let key = self.key_of(&e);
396 if self.find_by_id(&key)?.is_some() {
397 updated.push(self.save(e)?);
398 } else {
399 skipped += 1;
400 }
401 }
402 Ok(BatchUpdateResult { updated, skipped })
403 }
404
405 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize>;
407
408 fn delete_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<usize> {
410 let items = self.find_by(conditions)?;
411 let mut count = 0;
412 for item in items {
413 let key = self.key_of(&item);
414 count += self.delete(&key)?;
415 }
416 Ok(count)
417 }
418
419 fn count(&self) -> RepositoryResult<u64>;
421
422 fn count_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<u64> {
424 let items = self.find_by(conditions)?;
425 Ok(items.len() as u64)
426 }
427
428 fn exists(&self, key: &Self::Key) -> RepositoryResult<bool> {
430 Ok(self.find_by_id(key)?.is_some())
431 }
432
433 fn paginate(&self, page: u64, page_size: u64) -> RepositoryResult<PageResult<E>>
435 where
436 E: Clone,
437 {
438 let all = self.find_all()?;
439 let total = all.len() as u64;
440 let start = ((page.saturating_sub(1)) * page_size) as usize;
441 let end = (start + page_size as usize).min(all.len());
442
443 let items = if start < all.len() {
444 all[start..end].to_vec()
445 } else {
446 Vec::new()
447 };
448
449 Ok(PageResult::new(items, total, page, page_size))
450 }
451
452 fn paginate_by(
454 &self,
455 conditions: &[WhereCondition],
456 page: u64,
457 page_size: u64,
458 ) -> RepositoryResult<PageResult<E>>
459 where
460 E: Clone,
461 {
462 let all = self.find_by(conditions)?;
463 let total = all.len() as u64;
464 let start = ((page.saturating_sub(1)) * page_size) as usize;
465 let end = (start + page_size as usize).min(all.len());
466
467 let items = if start < all.len() {
468 all[start..end].to_vec()
469 } else {
470 Vec::new()
471 };
472
473 Ok(PageResult::new(items, total, page, page_size))
474 }
475
476 fn find_by_with_or_filter(
512 &self,
513 and: &[WhereCondition],
514 or_filter: &[WhereCondition],
515 ) -> RepositoryResult<Vec<E>>
516 where
517 E: Clone + EntityAttributes,
518 {
519 let items = self.find_by(and)?;
520 if or_filter.is_empty() {
521 return Ok(items);
522 }
523 Ok(items
524 .into_iter()
525 .filter(|e| {
526 or_filter.iter().any(|c| {
527 let attr = e.get_attribute(&c.field);
528 match (attr, c.op) {
529 (None, WhereOp::IsNull) => true,
530 (None, _) => false,
531 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
532 }
533 })
534 })
535 .collect())
536 }
537}
538
539pub struct InMemoryRepository<E: Clone + Send + Sync + 'static> {
548 storage: RwLock<Vec<E>>,
549}
550
551impl<E: Clone + Send + Sync + 'static> InMemoryRepository<E> {
552 pub fn new() -> Self {
554 Self {
555 storage: RwLock::new(Vec::new()),
556 }
557 }
558
559 pub fn from_vec(items: Vec<E>) -> Self {
561 Self {
562 storage: RwLock::new(items),
563 }
564 }
565
566 pub fn len(&self) -> usize {
568 match self.storage.read() {
569 Ok(storage) => storage.len(),
570 Err(_) => 0,
571 }
572 }
573
574 pub fn is_empty(&self) -> bool {
576 self.len() == 0
577 }
578
579 pub fn clear(&self) {
581 if let Ok(mut storage) = self.storage.write() {
582 storage.clear();
583 }
584 }
585}
586
587impl<E: Clone + Send + Sync + 'static> Default for InMemoryRepository<E> {
588 fn default() -> Self {
589 Self::new()
590 }
591}
592
593fn value_matches(value: &Value, op: WhereOp, target: &Value, extras: &[Value]) -> bool {
595 use Value::*;
596 match op {
597 WhereOp::Eq => value == target,
598 WhereOp::Ne => value != target,
599 WhereOp::Gt => match (value, target) {
600 (I64(a), I64(b)) => a > b,
601 (F64(a), F64(b)) => a > b,
602 (F64(a), I64(b)) => a > &(*b as f64),
603 (I64(a), F64(b)) => a > &(*b as i64),
604 (String(a), String(b)) => a > b,
605 _ => false,
606 },
607 WhereOp::Ge => match (value, target) {
608 (I64(a), I64(b)) => a >= b,
609 (F64(a), F64(b)) => a >= b,
610 (String(a), String(b)) => a >= b,
611 _ => false,
612 },
613 WhereOp::Lt => match (value, target) {
614 (I64(a), I64(b)) => a < b,
615 (F64(a), F64(b)) => a < b,
616 (String(a), String(b)) => a < b,
617 _ => false,
618 },
619 WhereOp::Le => match (value, target) {
620 (I64(a), I64(b)) => a <= b,
621 (F64(a), F64(b)) => a <= b,
622 (String(a), String(b)) => a <= b,
623 _ => false,
624 },
625 WhereOp::Like => match (value, target) {
626 (String(a), String(b)) => {
627 let pattern = b.to_lowercase().replace('%', ".*").replace('_', ".");
630 let full_pattern = format!("^{}$", pattern);
631 if let Ok(re) = simple_regex::compile(&full_pattern) {
632 re.is_match(&a.to_lowercase())
633 } else {
634 false
635 }
636 }
637 _ => false,
638 },
639 WhereOp::In => extras.iter().any(|v| v == value),
640 WhereOp::NotIn => !extras.iter().any(|v| v == value),
641 WhereOp::IsNull => matches!(value, Null),
642 WhereOp::IsNotNull => !matches!(value, Null),
643 WhereOp::Between => {
644 if extras.is_empty() {
645 return false;
646 }
647 let low = target;
648 let high = &extras[0];
649 value_matches(value, WhereOp::Ge, low, &[])
651 && value_matches(value, WhereOp::Le, high, &[])
652 }
653 }
654}
655
656pub trait EntityAttributes: Send + Sync {
658 fn get_attribute(&self, field: &str) -> Option<Value>;
660}
661
662impl<E: Clone + Send + Sync + 'static + EntityAttributes> Repository<E> for InMemoryRepository<E> {
664 type Key = Value;
665
666 fn key_of(&self, entity: &E) -> Self::Key {
667 entity.get_attribute("id").unwrap_or(Value::Null)
668 }
669
670 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>> {
671 let storage = match self.storage.read() {
672 Ok(g) => g,
673 Err(_) => return Ok(None),
674 };
675 Ok(storage.iter().find(|e| self.key_of(e) == *key).cloned())
676 }
677
678 fn find_all(&self) -> RepositoryResult<Vec<E>> {
679 let storage = match self.storage.read() {
680 Ok(g) => g,
681 Err(_) => return Ok(Vec::new()),
682 };
683 Ok(storage.clone())
684 }
685
686 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>> {
687 let storage = match self.storage.read() {
688 Ok(g) => g,
689 Err(_) => return Ok(Vec::new()),
690 };
691 let result: Vec<E> = storage
692 .iter()
693 .filter(|e| {
694 conditions.iter().all(|c| {
695 let attr = e.get_attribute(&c.field);
696 match (attr, c.op) {
697 (None, WhereOp::IsNull) => true,
698 (None, _) => false,
699 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
700 }
701 })
702 })
703 .cloned()
704 .collect();
705 Ok(result)
706 }
707
708 fn save(&self, mut entity: E) -> RepositoryResult<E> {
709 let mut storage = match self.storage.write() {
710 Ok(g) => g,
711 Err(_) => return Ok(entity),
712 };
713 let key = self.key_of(&entity);
714
715 let existing_idx = storage.iter().position(|e| self.key_of(e) == key);
717
718 match existing_idx {
719 Some(idx) => {
720 storage[idx] = entity.clone();
721 }
722 None => {
723 storage.push(entity.clone());
724 }
725 }
726 let _ = &mut entity; Ok(entity)
729 }
730
731 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize> {
732 let mut storage = match self.storage.write() {
733 Ok(g) => g,
734 Err(_) => return Ok(0),
735 };
736 let before = storage.len();
737 storage.retain(|e| self.key_of(e) != *key);
738 Ok(before - storage.len())
739 }
740
741 fn count(&self) -> RepositoryResult<u64> {
742 Ok(self.len() as u64)
743 }
744}
745
746mod simple_regex {
751 pub struct Regex {
753 patterns: Vec<Pattern>,
754 }
755
756 enum Pattern {
757 AnyChars, Literal(char), Start, End, }
762
763 pub fn compile(pattern: &str) -> Result<Regex, String> {
764 let mut patterns = Vec::new();
765 let chars: Vec<char> = pattern.chars().collect();
766 let mut i = 0;
767 while i < chars.len() {
768 match chars[i] {
769 '^' => {
770 patterns.push(Pattern::Start);
771 i += 1;
772 }
773 '$' => {
774 patterns.push(Pattern::End);
775 i += 1;
776 }
777 '.' if i + 1 < chars.len() && chars[i + 1] == '*' => {
778 patterns.push(Pattern::AnyChars);
779 i += 2;
780 }
781 c => {
782 patterns.push(Pattern::Literal(c));
783 i += 1;
784 }
785 }
786 }
787 Ok(Regex { patterns })
788 }
789
790 impl Regex {
791 pub fn is_match(&self, text: &str) -> bool {
792 self.match_from(text, 0, 0)
793 }
794
795 fn match_from(&self, text: &str, text_idx: usize, pat_idx: usize) -> bool {
796 let chars: Vec<char> = text.chars().collect();
797 if pat_idx >= self.patterns.len() {
798 return text_idx == chars.len();
799 }
800 match &self.patterns[pat_idx] {
801 Pattern::Start => self.match_from(text, 0, pat_idx + 1),
802 Pattern::End => text_idx == chars.len(),
803 Pattern::Literal(c) => {
804 if text_idx < chars.len() && chars[text_idx] == *c {
805 self.match_from(text, text_idx + 1, pat_idx + 1)
806 } else {
807 false
808 }
809 }
810 Pattern::AnyChars => {
811 for skip in 0..=(chars.len() - text_idx) {
813 if self.match_from(text, text_idx + skip, pat_idx + 1) {
814 return true;
815 }
816 }
817 false
818 }
819 }
820 }
821 }
822}
823
824pub trait EntityKey<K>: Send + Sync {
830 fn key(&self) -> K;
832}
833
834pub struct GenericKeyRepository<E, K>
836where
837 E: Clone + Send + Sync + 'static,
838 K: Clone + Debug + PartialEq + Send + Sync + 'static,
839{
840 storage: RwLock<Vec<E>>,
841 _phantom: std::marker::PhantomData<K>,
842}
843
844impl<E, K> GenericKeyRepository<E, K>
845where
846 E: Clone + Send + Sync + 'static,
847 K: Clone + Debug + PartialEq + Send + Sync + 'static,
848{
849 pub fn new() -> Self {
850 Self {
851 storage: RwLock::new(Vec::new()),
852 _phantom: std::marker::PhantomData,
853 }
854 }
855
856 pub fn from_vec(items: Vec<E>) -> Self {
857 Self {
858 storage: RwLock::new(items),
859 _phantom: std::marker::PhantomData,
860 }
861 }
862
863 pub fn len(&self) -> usize {
864 match self.storage.read() {
865 Ok(g) => g.len(),
866 Err(_) => 0,
867 }
868 }
869
870 pub fn is_empty(&self) -> bool {
871 self.len() == 0
872 }
873
874 pub fn clear(&self) {
875 if let Ok(mut storage) = self.storage.write() {
876 storage.clear();
877 }
878 }
879}
880
881impl<E, K> Default for GenericKeyRepository<E, K>
882where
883 E: Clone + Send + Sync + 'static,
884 K: Clone + Debug + PartialEq + Send + Sync + 'static,
885{
886 fn default() -> Self {
887 Self::new()
888 }
889}
890
891impl<E, K> Repository<E> for GenericKeyRepository<E, K>
892where
893 E: Clone + Send + Sync + 'static + EntityKey<K> + EntityAttributes,
894 K: Clone + Debug + PartialEq + Send + Sync + 'static,
895{
896 type Key = K;
897
898 fn key_of(&self, entity: &E) -> Self::Key {
899 entity.key()
900 }
901
902 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>> {
903 let storage = match self.storage.read() {
904 Ok(g) => g,
905 Err(_) => return Ok(None),
906 };
907 Ok(storage.iter().find(|e| &e.key() == key).cloned())
908 }
909
910 fn find_all(&self) -> RepositoryResult<Vec<E>> {
911 let storage = match self.storage.read() {
912 Ok(g) => g,
913 Err(_) => return Ok(Vec::new()),
914 };
915 Ok(storage.clone())
916 }
917
918 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>> {
919 let storage = match self.storage.read() {
920 Ok(g) => g,
921 Err(_) => return Ok(Vec::new()),
922 };
923 let result: Vec<E> = storage
924 .iter()
925 .filter(|e| {
926 conditions.iter().all(|c| {
927 let attr = e.get_attribute(&c.field);
928 match (attr, c.op) {
929 (None, WhereOp::IsNull) => true,
930 (None, _) => false,
931 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
932 }
933 })
934 })
935 .cloned()
936 .collect();
937 Ok(result)
938 }
939
940 fn save(&self, entity: E) -> RepositoryResult<E> {
941 let mut storage = match self.storage.write() {
942 Ok(g) => g,
943 Err(_) => return Ok(entity),
944 };
945 let key = entity.key();
946 let existing_idx = storage.iter().position(|e| e.key() == key);
947 match existing_idx {
948 Some(idx) => {
949 storage[idx] = entity.clone();
950 }
951 None => {
952 storage.push(entity.clone());
953 }
954 }
955 Ok(entity)
956 }
957
958 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize> {
959 let mut storage = match self.storage.write() {
960 Ok(g) => g,
961 Err(_) => return Ok(0),
962 };
963 let before = storage.len();
964 storage.retain(|e| &e.key() != key);
965 Ok(before - storage.len())
966 }
967
968 fn count(&self) -> RepositoryResult<u64> {
969 Ok(self.len() as u64)
970 }
971}
972
973#[cfg(test)]
978mod tests {
979 use super::*;
980
981 #[derive(Debug, Clone, PartialEq)]
984 struct User {
985 id: i64,
986 name: String,
987 age: i64,
988 email: String,
989 }
990
991 impl User {
992 fn new(id: i64, name: &str, age: i64, email: &str) -> Self {
993 Self {
994 id,
995 name: name.to_string(),
996 age,
997 email: email.to_string(),
998 }
999 }
1000 }
1001
1002 impl EntityAttributes for User {
1003 fn get_attribute(&self, field: &str) -> Option<Value> {
1004 match field {
1005 "id" => Some(Value::I64(self.id)),
1006 "name" => Some(Value::String(self.name.clone())),
1007 "age" => Some(Value::I64(self.age)),
1008 "email" => Some(Value::String(self.email.clone())),
1009 _ => None,
1010 }
1011 }
1012 }
1013
1014 impl EntityKey<i64> for User {
1015 fn key(&self) -> i64 {
1016 self.id
1017 }
1018 }
1019
1020 #[test]
1023 fn test_where_op_name() {
1024 assert_eq!(WhereOp::Eq.name(), "eq");
1025 assert_eq!(WhereOp::Ne.name(), "ne");
1026 assert_eq!(WhereOp::Gt.name(), "gt");
1027 assert_eq!(WhereOp::Like.name(), "like");
1028 assert_eq!(WhereOp::In.name(), "in");
1029 assert_eq!(WhereOp::IsNull.name(), "is_null");
1030 assert_eq!(WhereOp::Between.name(), "between");
1031 }
1032
1033 #[test]
1034 fn test_where_condition_new() {
1035 let c = WhereCondition::new("age", WhereOp::Ge, Value::I64(18));
1036 assert_eq!(c.field, "age");
1037 assert_eq!(c.op, WhereOp::Ge);
1038 assert_eq!(c.value, Value::I64(18));
1039 assert!(c.extra_values.is_empty());
1040 }
1041
1042 #[test]
1043 fn test_where_condition_null_check() {
1044 let c = WhereCondition::null_check("deleted_at", WhereOp::IsNull);
1045 assert_eq!(c.field, "deleted_at");
1046 assert_eq!(c.op, WhereOp::IsNull);
1047 assert_eq!(c.value, Value::Null);
1048 }
1049
1050 #[test]
1051 fn test_where_condition_in() {
1052 let c = WhereCondition::in_op(
1053 "id",
1054 WhereOp::In,
1055 vec![Value::I64(1), Value::I64(2), Value::I64(3)],
1056 );
1057 assert_eq!(c.field, "id");
1058 assert_eq!(c.op, WhereOp::In);
1059 assert_eq!(c.extra_values.len(), 3);
1060 }
1061
1062 #[test]
1063 fn test_where_condition_between() {
1064 let c = WhereCondition::between("age", Value::I64(18), Value::I64(30));
1065 assert_eq!(c.field, "age");
1066 assert_eq!(c.op, WhereOp::Between);
1067 assert_eq!(c.value, Value::I64(18));
1068 assert_eq!(c.extra_values, vec![Value::I64(30)]);
1069 }
1070
1071 #[test]
1074 fn test_page_result_total_pages() {
1075 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1076 assert_eq!(pr.total_pages(), 10);
1077 }
1078
1079 #[test]
1080 fn test_page_result_total_pages_with_remainder() {
1081 let pr: PageResult<i32> = PageResult::new(vec![], 105, 1, 10);
1082 assert_eq!(pr.total_pages(), 11);
1083 }
1084
1085 #[test]
1086 fn test_page_result_total_pages_zero_size() {
1087 let pr: PageResult<i32> = PageResult::new(vec![], 100, 1, 0);
1088 assert_eq!(pr.total_pages(), 0);
1089 }
1090
1091 #[test]
1092 fn test_page_result_has_next() {
1093 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1094 assert!(pr.has_next());
1095 assert!(!pr.has_prev());
1096 }
1097
1098 #[test]
1099 fn test_page_result_has_prev() {
1100 let pr = PageResult::new(vec![1, 2, 3], 100, 5, 10);
1101 assert!(pr.has_prev());
1102 assert!(pr.has_next()); }
1104
1105 #[test]
1106 fn test_page_result_is_empty() {
1107 let pr: PageResult<i32> = PageResult::new(vec![], 0, 1, 10);
1108 assert!(pr.is_empty());
1109 assert_eq!(pr.len(), 0);
1110 }
1111
1112 #[test]
1113 fn test_page_result_map() {
1114 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1115 let mapped = pr.map(|x| x * 2);
1116 assert_eq!(mapped.items, vec![2, 4, 6]);
1117 assert_eq!(mapped.total, 100);
1118 }
1119
1120 #[test]
1123 fn test_repository_error_display() {
1124 let e = RepositoryError::NotFound;
1125 assert_eq!(e.to_string(), "entity not found");
1126
1127 let e = RepositoryError::DatabaseError("conn refused".to_string());
1128 assert_eq!(e.to_string(), "database error: conn refused");
1129
1130 let e = RepositoryError::InvalidEntity("missing id".to_string());
1131 assert_eq!(e.to_string(), "invalid entity: missing id");
1132
1133 let e = RepositoryError::Other("custom".to_string());
1134 assert_eq!(e.to_string(), "repository error: custom");
1135 }
1136
1137 #[test]
1138 fn test_repository_error_eq() {
1139 assert_eq!(RepositoryError::NotFound, RepositoryError::NotFound);
1140 assert_ne!(
1141 RepositoryError::NotFound,
1142 RepositoryError::Other("x".to_string())
1143 );
1144 }
1145
1146 #[test]
1149 fn test_inmemory_create_empty() {
1150 let repo = InMemoryRepository::<User>::new();
1151 assert!(repo.is_empty());
1152 assert_eq!(repo.len(), 0);
1153 }
1154
1155 #[test]
1156 fn test_inmemory_from_vec() {
1157 let repo = InMemoryRepository::from_vec(vec![
1158 User::new(1, "Alice", 30, "alice@example.com"),
1159 User::new(2, "Bob", 25, "bob@example.com"),
1160 ]);
1161 assert_eq!(repo.len(), 2);
1162 }
1163
1164 #[test]
1165 fn test_inmemory_clear() {
1166 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1167 assert_eq!(repo.len(), 1);
1168 repo.clear();
1169 assert_eq!(repo.len(), 0);
1170 }
1171
1172 #[test]
1175 fn test_repo_save_and_find_by_id() {
1176 let repo = InMemoryRepository::<User>::new();
1177 let user = User::new(1, "Alice", 30, "alice@example.com");
1178 let saved = repo.save(user.clone()).unwrap();
1179 assert_eq!(saved, user);
1180
1181 let found = repo.find_by_id(&Value::I64(1)).unwrap();
1182 assert_eq!(found, Some(user));
1183 }
1184
1185 #[test]
1186 fn test_repo_find_by_id_missing() {
1187 let repo = InMemoryRepository::<User>::new();
1188 let found = repo.find_by_id(&Value::I64(999)).unwrap();
1189 assert_eq!(found, None);
1190 }
1191
1192 #[test]
1193 fn test_repo_save_many() {
1194 let repo = InMemoryRepository::<User>::new();
1195 let users = vec![
1196 User::new(1, "Alice", 30, "alice@example.com"),
1197 User::new(2, "Bob", 25, "bob@example.com"),
1198 User::new(3, "Carol", 28, "carol@example.com"),
1199 ];
1200 let saved = repo.save_many(users.clone()).unwrap();
1201 assert_eq!(saved.len(), 3);
1202 assert_eq!(repo.len(), 3);
1203 }
1204
1205 #[test]
1208 fn test_batch_update_result_new() {
1209 let result = BatchUpdateResult::new(vec![1, 2, 3], 2);
1210 assert_eq!(result.updated_count(), 3);
1211 assert_eq!(result.skipped, 2);
1212 assert_eq!(result.total(), 5);
1213 assert!(result.has_skipped());
1214 assert!(!result.all_updated());
1215 }
1216
1217 #[test]
1218 fn test_batch_update_result_all_updated() {
1219 let result: BatchUpdateResult<i32> = BatchUpdateResult::new(vec![1, 2], 0);
1220 assert!(!result.has_skipped());
1221 assert!(result.all_updated());
1222 assert_eq!(result.total(), 2);
1223 }
1224
1225 #[test]
1226 fn test_batch_update_result_default() {
1227 let result: BatchUpdateResult<i32> = BatchUpdateResult::default();
1228 assert_eq!(result.updated_count(), 0);
1229 assert_eq!(result.skipped, 0);
1230 assert_eq!(result.total(), 0);
1231 }
1232
1233 #[test]
1234 fn test_batch_update_result_map() {
1235 let result = BatchUpdateResult::new(vec![1, 2, 3], 1);
1236 let mapped = result.map(|x| x * 10);
1237 assert_eq!(mapped.updated, vec![10, 20, 30]);
1238 assert_eq!(mapped.skipped, 1);
1239 }
1240
1241 #[test]
1242 fn test_repo_batch_update_all_existing() {
1243 let repo = InMemoryRepository::from_vec(vec![
1245 User::new(1, "Alice", 30, "a@b.com"),
1246 User::new(2, "Bob", 25, "b@b.com"),
1247 ]);
1248 let updates = vec![
1249 User::new(1, "Alice Updated", 31, "a2@b.com"),
1250 User::new(2, "Bob Updated", 26, "b2@b.com"),
1251 ];
1252 let result = repo.batch_update(updates).unwrap();
1253 assert_eq!(result.updated_count(), 2);
1254 assert_eq!(result.skipped, 0);
1255 assert!(result.all_updated());
1256
1257 let alice = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1259 assert_eq!(alice.name, "Alice Updated");
1260 assert_eq!(alice.age, 31);
1261 let bob = repo.find_by_id(&Value::I64(2)).unwrap().unwrap();
1262 assert_eq!(bob.name, "Bob Updated");
1263 assert_eq!(bob.age, 26);
1264 assert_eq!(repo.len(), 2);
1266 }
1267
1268 #[test]
1269 fn test_repo_batch_update_partial_missing() {
1270 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1272 let updates = vec![
1273 User::new(1, "Alice Updated", 31, "a2@b.com"),
1274 User::new(999, "Ghost", 1, "ghost@b.com"), ];
1276 let result = repo.batch_update(updates).unwrap();
1277 assert_eq!(result.updated_count(), 1);
1278 assert_eq!(result.skipped, 1);
1279 assert!(result.has_skipped());
1280
1281 let alice = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1283 assert_eq!(alice.name, "Alice Updated");
1284 assert!(repo.find_by_id(&Value::I64(999)).unwrap().is_none());
1286 assert_eq!(repo.len(), 1);
1287 }
1288
1289 #[test]
1290 fn test_repo_batch_update_all_missing() {
1291 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1293 let updates = vec![
1294 User::new(100, "Ghost1", 1, "g1@b.com"),
1295 User::new(200, "Ghost2", 2, "g2@b.com"),
1296 ];
1297 let result = repo.batch_update(updates).unwrap();
1298 assert_eq!(result.updated_count(), 0);
1299 assert_eq!(result.skipped, 2);
1300 assert_eq!(repo.len(), 1); }
1302
1303 #[test]
1304 fn test_repo_batch_update_empty() {
1305 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1306 let result = repo.batch_update(vec![]).unwrap();
1307 assert_eq!(result.updated_count(), 0);
1308 assert_eq!(result.skipped, 0);
1309 assert_eq!(result.total(), 0);
1310 }
1311
1312 #[test]
1313 fn test_repo_batch_update_distinct_from_save_many() {
1314 let repo1 = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1318 let repo2 = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1319
1320 let updates = vec![
1321 User::new(1, "Alice Updated", 31, "a2@b.com"),
1322 User::new(999, "New User", 1, "new@b.com"),
1323 ];
1324
1325 let saved = repo1.save_many(updates.clone()).unwrap();
1327 assert_eq!(saved.len(), 2);
1328 assert_eq!(repo1.len(), 2); let result = repo2.batch_update(updates).unwrap();
1332 assert_eq!(result.updated_count(), 1);
1333 assert_eq!(result.skipped, 1);
1334 assert_eq!(repo2.len(), 1); }
1336
1337 #[test]
1338 fn test_repo_save_update_existing() {
1339 let repo = InMemoryRepository::<User>::new();
1340 repo.save(User::new(1, "Alice", 30, "alice@example.com"))
1341 .unwrap();
1342
1343 repo.save(User::new(1, "Alice Updated", 31, "alice2@example.com"))
1345 .unwrap();
1346
1347 assert_eq!(repo.len(), 1);
1348 let found = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1349 assert_eq!(found.name, "Alice Updated");
1350 assert_eq!(found.age, 31);
1351 }
1352
1353 #[test]
1354 fn test_repo_find_all() {
1355 let repo = InMemoryRepository::from_vec(vec![
1356 User::new(1, "Alice", 30, "a@b.com"),
1357 User::new(2, "Bob", 25, "b@b.com"),
1358 ]);
1359 let all = repo.find_all().unwrap();
1360 assert_eq!(all.len(), 2);
1361 }
1362
1363 #[test]
1364 fn test_repo_find_all_empty() {
1365 let repo = InMemoryRepository::<User>::new();
1366 let all = repo.find_all().unwrap();
1367 assert!(all.is_empty());
1368 }
1369
1370 #[test]
1371 fn test_repo_delete() {
1372 let repo = InMemoryRepository::from_vec(vec![
1373 User::new(1, "Alice", 30, "a@b.com"),
1374 User::new(2, "Bob", 25, "b@b.com"),
1375 ]);
1376 let deleted = repo.delete(&Value::I64(1)).unwrap();
1377 assert_eq!(deleted, 1);
1378 assert_eq!(repo.len(), 1);
1379 }
1380
1381 #[test]
1382 fn test_repo_delete_missing() {
1383 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1384 let deleted = repo.delete(&Value::I64(999)).unwrap();
1385 assert_eq!(deleted, 0);
1386 assert_eq!(repo.len(), 1);
1387 }
1388
1389 #[test]
1390 fn test_repo_count() {
1391 let repo = InMemoryRepository::from_vec(vec![
1392 User::new(1, "Alice", 30, "a@b.com"),
1393 User::new(2, "Bob", 25, "b@b.com"),
1394 User::new(3, "Carol", 28, "c@b.com"),
1395 ]);
1396 assert_eq!(repo.count().unwrap(), 3);
1397 }
1398
1399 #[test]
1400 fn test_repo_count_empty() {
1401 let repo = InMemoryRepository::<User>::new();
1402 assert_eq!(repo.count().unwrap(), 0);
1403 }
1404
1405 #[test]
1406 fn test_repo_exists() {
1407 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1408 assert!(repo.exists(&Value::I64(1)).unwrap());
1409 assert!(!repo.exists(&Value::I64(999)).unwrap());
1410 }
1411
1412 #[test]
1415 fn test_repo_find_by_eq() {
1416 let repo = InMemoryRepository::from_vec(vec![
1417 User::new(1, "Alice", 30, "a@b.com"),
1418 User::new(2, "Bob", 30, "b@b.com"),
1419 User::new(3, "Carol", 25, "c@b.com"),
1420 ]);
1421
1422 let result = repo
1423 .find_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1424 .unwrap();
1425 assert_eq!(result.len(), 2);
1426 }
1427
1428 #[test]
1429 fn test_repo_find_by_gt() {
1430 let repo = InMemoryRepository::from_vec(vec![
1431 User::new(1, "Alice", 30, "a@b.com"),
1432 User::new(2, "Bob", 25, "b@b.com"),
1433 User::new(3, "Carol", 35, "c@b.com"),
1434 ]);
1435
1436 let result = repo
1437 .find_by(&[WhereCondition::new("age", WhereOp::Gt, Value::I64(28))])
1438 .unwrap();
1439 assert_eq!(result.len(), 2);
1440 }
1441
1442 #[test]
1443 fn test_repo_find_by_like() {
1444 let repo = InMemoryRepository::from_vec(vec![
1445 User::new(1, "Alice", 30, "alice@example.com"),
1446 User::new(2, "Bob", 25, "bob@example.com"),
1447 User::new(3, "Alicia", 28, "alicia@test.com"),
1448 ]);
1449
1450 let result = repo
1451 .find_by(&[WhereCondition::new(
1452 "name",
1453 WhereOp::Like,
1454 Value::String("Ali%".to_string()),
1455 )])
1456 .unwrap();
1457 assert_eq!(result.len(), 2);
1458 }
1459
1460 #[test]
1461 fn test_repo_find_by_like_case_insensitive() {
1462 let repo = InMemoryRepository::from_vec(vec![
1464 User::new(1, "Alice", 30, "alice@example.com"),
1465 User::new(2, "bob", 25, "bob@example.com"),
1466 User::new(3, "ALICIA", 28, "alicia@test.com"),
1467 ]);
1468
1469 let result = repo
1471 .find_by(&[WhereCondition::new(
1472 "name",
1473 WhereOp::Like,
1474 Value::String("ali%".to_string()),
1475 )])
1476 .unwrap();
1477 assert_eq!(result.len(), 2, "LIKE should be case-insensitive");
1478
1479 let result = repo
1481 .find_by(&[WhereCondition::new(
1482 "name",
1483 WhereOp::Like,
1484 Value::String("BOB".to_string()),
1485 )])
1486 .unwrap();
1487 assert_eq!(
1488 result.len(),
1489 1,
1490 "LIKE exact match should be case-insensitive"
1491 );
1492 }
1493
1494 #[test]
1495 fn test_repo_find_by_with_or_filter_multi_field_keyword() {
1496 let repo = InMemoryRepository::from_vec(vec![
1498 User::new(1, "Alice", 30, "alice@example.com"),
1499 User::new(2, "Bob", 25, "bob@example.com"),
1500 User::new(3, "Carol", 28, "carol@kw.com"), User::new(4, "Dave", 32, "dave@example.com"),
1502 ]);
1503
1504 let and = vec![]; let or = vec![
1506 WhereCondition::new("name", WhereOp::Like, Value::String("%kw%".to_string())),
1507 WhereCondition::new("email", WhereOp::Like, Value::String("%kw%".to_string())),
1508 ];
1509
1510 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1511 assert_eq!(result.len(), 1);
1513 assert_eq!(result[0].key(), 3);
1514 }
1515
1516 #[test]
1517 fn test_repo_find_by_with_or_filter_combined_with_and() {
1518 let repo = InMemoryRepository::from_vec(vec![
1520 User::new(1, "Alice_kw", 30, "alice@example.com"), User::new(2, "Bob", 25, "bob@kw.com"), User::new(3, "Carol_kw", 28, "carol@example.com"), User::new(4, "Dave", 32, "dave@example.com"), ]);
1525
1526 let and = vec![WhereCondition::new("age", WhereOp::Ge, Value::I64(28))];
1528 let or = vec![
1529 WhereCondition::new("name", WhereOp::Like, Value::String("%kw%".to_string())),
1530 WhereCondition::new("email", WhereOp::Like, Value::String("%kw%".to_string())),
1531 ];
1532
1533 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1534 assert_eq!(result.len(), 2);
1538 let ids: Vec<i64> = result.iter().map(|u| u.key()).collect();
1539 assert!(ids.contains(&1));
1540 assert!(ids.contains(&3));
1541 }
1542
1543 #[test]
1544 fn test_repo_find_by_with_or_filter_empty_or() {
1545 let repo = InMemoryRepository::from_vec(vec![
1547 User::new(1, "Alice", 30, "alice@example.com"),
1548 User::new(2, "Bob", 25, "bob@example.com"),
1549 ]);
1550
1551 let and = vec![WhereCondition::new("age", WhereOp::Ge, Value::I64(28))];
1552 let or: Vec<WhereCondition> = vec![];
1553
1554 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1555 assert_eq!(result.len(), 1);
1556 assert_eq!(result[0].key(), 1);
1557 }
1558
1559 #[test]
1560 fn test_repo_find_by_in() {
1561 let repo = InMemoryRepository::from_vec(vec![
1562 User::new(1, "Alice", 30, "a@b.com"),
1563 User::new(2, "Bob", 25, "b@b.com"),
1564 User::new(3, "Carol", 28, "c@b.com"),
1565 User::new(4, "Dave", 32, "d@b.com"),
1566 ]);
1567
1568 let result = repo
1569 .find_by(&[WhereCondition::in_op(
1570 "id",
1571 WhereOp::In,
1572 vec![Value::I64(1), Value::I64(3)],
1573 )])
1574 .unwrap();
1575 assert_eq!(result.len(), 2);
1576 }
1577
1578 #[test]
1579 fn test_repo_find_by_not_in() {
1580 let repo = InMemoryRepository::from_vec(vec![
1581 User::new(1, "Alice", 30, "a@b.com"),
1582 User::new(2, "Bob", 25, "b@b.com"),
1583 User::new(3, "Carol", 28, "c@b.com"),
1584 ]);
1585
1586 let result = repo
1587 .find_by(&[WhereCondition::in_op(
1588 "id",
1589 WhereOp::NotIn,
1590 vec![Value::I64(1)],
1591 )])
1592 .unwrap();
1593 assert_eq!(result.len(), 2);
1594 assert!(result.iter().all(|u| u.id != 1));
1595 }
1596
1597 #[test]
1598 fn test_repo_find_by_between() {
1599 let repo = InMemoryRepository::from_vec(vec![
1600 User::new(1, "Alice", 30, "a@b.com"),
1601 User::new(2, "Bob", 25, "b@b.com"),
1602 User::new(3, "Carol", 35, "c@b.com"),
1603 User::new(4, "Dave", 22, "d@b.com"),
1604 ]);
1605
1606 let result = repo
1607 .find_by(&[WhereCondition::between(
1608 "age",
1609 Value::I64(25),
1610 Value::I64(35),
1611 )])
1612 .unwrap();
1613 assert_eq!(result.len(), 3);
1614 }
1615
1616 #[test]
1617 fn test_repo_find_by_multiple_conditions() {
1618 let repo = InMemoryRepository::from_vec(vec![
1619 User::new(1, "Alice", 30, "a@b.com"),
1620 User::new(2, "Bob", 30, "b@b.com"),
1621 User::new(3, "Alice", 25, "c@b.com"),
1622 ]);
1623
1624 let result = repo
1625 .find_by(&[
1626 WhereCondition::new("name", WhereOp::Eq, Value::String("Alice".to_string())),
1627 WhereCondition::new("age", WhereOp::Ge, Value::I64(30)),
1628 ])
1629 .unwrap();
1630 assert_eq!(result.len(), 1);
1631 assert_eq!(result[0].id, 1);
1632 }
1633
1634 #[test]
1635 fn test_repo_find_one_by() {
1636 let repo = InMemoryRepository::from_vec(vec![
1637 User::new(1, "Alice", 30, "a@b.com"),
1638 User::new(2, "Bob", 25, "b@b.com"),
1639 ]);
1640
1641 let result = repo
1642 .find_one_by(&[WhereCondition::new(
1643 "name",
1644 WhereOp::Eq,
1645 Value::String("Bob".to_string()),
1646 )])
1647 .unwrap();
1648 assert!(result.is_some());
1649 assert_eq!(result.unwrap().id, 2);
1650 }
1651
1652 #[test]
1653 fn test_repo_find_one_by_missing() {
1654 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1655
1656 let result = repo
1657 .find_one_by(&[WhereCondition::new(
1658 "name",
1659 WhereOp::Eq,
1660 Value::String("Missing".to_string()),
1661 )])
1662 .unwrap();
1663 assert!(result.is_none());
1664 }
1665
1666 #[test]
1667 fn test_repo_count_by() {
1668 let repo = InMemoryRepository::from_vec(vec![
1669 User::new(1, "Alice", 30, "a@b.com"),
1670 User::new(2, "Bob", 30, "b@b.com"),
1671 User::new(3, "Carol", 25, "c@b.com"),
1672 ]);
1673
1674 let count = repo
1675 .count_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1676 .unwrap();
1677 assert_eq!(count, 2);
1678 }
1679
1680 #[test]
1681 fn test_repo_delete_by() {
1682 let repo = InMemoryRepository::from_vec(vec![
1683 User::new(1, "Alice", 30, "a@b.com"),
1684 User::new(2, "Bob", 30, "b@b.com"),
1685 User::new(3, "Carol", 25, "c@b.com"),
1686 ]);
1687
1688 let deleted = repo
1689 .delete_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1690 .unwrap();
1691 assert_eq!(deleted, 2);
1692 assert_eq!(repo.len(), 1);
1693 }
1694
1695 #[test]
1698 fn test_repo_paginate() {
1699 let users: Vec<User> = (1..=25)
1700 .map(|i| User::new(i, &format!("User{}", i), 20 + (i % 30), "u@b.com"))
1701 .collect();
1702 let repo = InMemoryRepository::from_vec(users);
1703
1704 let page = repo.paginate(1, 10).unwrap();
1705 assert_eq!(page.page, 1);
1706 assert_eq!(page.page_size, 10);
1707 assert_eq!(page.total, 25);
1708 assert_eq!(page.total_pages(), 3);
1709 assert_eq!(page.items.len(), 10);
1710 assert!(page.has_next());
1711 assert!(!page.has_prev());
1712 }
1713
1714 #[test]
1715 fn test_repo_paginate_last_page() {
1716 let users: Vec<User> = (1..=25)
1717 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1718 .collect();
1719 let repo = InMemoryRepository::from_vec(users);
1720
1721 let page = repo.paginate(3, 10).unwrap();
1722 assert_eq!(page.items.len(), 5);
1723 assert!(page.has_prev());
1724 assert!(!page.has_next());
1725 }
1726
1727 #[test]
1728 fn test_repo_paginate_out_of_range() {
1729 let users: Vec<User> = (1..=5)
1730 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1731 .collect();
1732 let repo = InMemoryRepository::from_vec(users);
1733
1734 let page = repo.paginate(10, 10).unwrap();
1735 assert_eq!(page.items.len(), 0);
1736 assert_eq!(page.total, 5);
1737 }
1738
1739 #[test]
1740 fn test_repo_paginate_by() {
1741 let users: Vec<User> = (1..=20)
1742 .map(|i| User::new(i, &format!("User{}", i), 20 + (i % 5), "u@b.com"))
1743 .collect();
1744 let repo = InMemoryRepository::from_vec(users);
1745
1746 let page = repo
1748 .paginate_by(
1749 &[WhereCondition::new("age", WhereOp::Eq, Value::I64(22))],
1750 1,
1751 2,
1752 )
1753 .unwrap();
1754 assert_eq!(page.total, 4);
1755 assert_eq!(page.items.len(), 2);
1756 assert_eq!(page.total_pages(), 2);
1757 }
1758
1759 #[test]
1762 fn test_generic_key_repo_basic() {
1763 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::new();
1764 assert!(repo.is_empty());
1765
1766 let user = User::new(1, "Alice", 30, "a@b.com");
1767 repo.save(user.clone()).unwrap();
1768 assert_eq!(repo.len(), 1);
1769
1770 let found = repo.find_by_id(&1).unwrap();
1771 assert_eq!(found, Some(user));
1772 }
1773
1774 #[test]
1775 fn test_generic_key_repo_delete() {
1776 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1777 User::new(1, "Alice", 30, "a@b.com"),
1778 User::new(2, "Bob", 25, "b@b.com"),
1779 ]);
1780
1781 let deleted = repo.delete(&1).unwrap();
1782 assert_eq!(deleted, 1);
1783 assert_eq!(repo.len(), 1);
1784
1785 let remaining = repo.find_all().unwrap();
1786 assert_eq!(remaining[0].id, 2);
1787 }
1788
1789 #[test]
1790 fn test_generic_key_repo_find_by() {
1791 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1792 User::new(1, "Alice", 30, "a@b.com"),
1793 User::new(2, "Bob", 30, "b@b.com"),
1794 User::new(3, "Carol", 25, "c@b.com"),
1795 ]);
1796
1797 let result = repo
1798 .find_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1799 .unwrap();
1800 assert_eq!(result.len(), 2);
1801 }
1802
1803 #[test]
1804 fn test_generic_key_repo_paginate() {
1805 let users: Vec<User> = (1..=15)
1806 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1807 .collect();
1808 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(users);
1809
1810 let page = repo.paginate(2, 10).unwrap();
1811 assert_eq!(page.items.len(), 5);
1812 assert_eq!(page.total, 15);
1813 assert_eq!(page.page, 2);
1814 }
1815
1816 #[test]
1817 fn test_generic_key_repo_count() {
1818 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1819 User::new(1, "Alice", 30, "a@b.com"),
1820 User::new(2, "Bob", 25, "b@b.com"),
1821 ]);
1822 assert_eq!(repo.count().unwrap(), 2);
1823 }
1824
1825 #[test]
1826 fn test_generic_key_repo_exists() {
1827 let repo: GenericKeyRepository<User, i64> =
1828 GenericKeyRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1829 assert!(repo.exists(&1).unwrap());
1830 assert!(!repo.exists(&999).unwrap());
1831 }
1832
1833 #[test]
1836 fn test_simple_regex_literal() {
1837 let re = simple_regex::compile("^abc$").unwrap();
1838 assert!(re.is_match("abc"));
1839 assert!(!re.is_match("abcd"));
1840 }
1841
1842 #[test]
1843 fn test_simple_regex_wildcard() {
1844 let re = simple_regex::compile("^Ali.*$").unwrap();
1845 assert!(re.is_match("Alice"));
1846 assert!(re.is_match("Alicia"));
1847 assert!(!re.is_match("Bob"));
1848 }
1849
1850 #[test]
1851 fn test_simple_regex_no_anchors() {
1852 let re = simple_regex::compile("ab").unwrap();
1853 assert!(re.is_match("ab"));
1854 }
1855
1856 #[test]
1859 fn test_e2e_repository_workflow() {
1860 let repo = InMemoryRepository::<User>::new();
1861
1862 let users = vec![
1864 User::new(1, "Alice", 30, "alice@example.com"),
1865 User::new(2, "Bob", 25, "bob@example.com"),
1866 User::new(3, "Carol", 35, "carol@example.com"),
1867 User::new(4, "Dave", 28, "dave@example.com"),
1868 User::new(5, "Eve", 32, "eve@example.com"),
1869 ];
1870 repo.save_many(users).unwrap();
1871 assert_eq!(repo.count().unwrap(), 5);
1872
1873 let adults = repo
1875 .find_by(&[WhereCondition::new("age", WhereOp::Ge, Value::I64(30))])
1876 .unwrap();
1877 assert_eq!(adults.len(), 3);
1878
1879 let page1 = repo.paginate(1, 2).unwrap();
1881 assert_eq!(page1.items.len(), 2);
1882 assert_eq!(page1.total_pages(), 3);
1883
1884 let page2 = repo.paginate(2, 2).unwrap();
1885 assert_eq!(page2.items.len(), 2);
1886
1887 let page3 = repo.paginate(3, 2).unwrap();
1888 assert_eq!(page3.items.len(), 1);
1889
1890 let adult_page = repo
1892 .paginate_by(
1893 &[WhereCondition::new("age", WhereOp::Ge, Value::I64(30))],
1894 1,
1895 2,
1896 )
1897 .unwrap();
1898 assert_eq!(adult_page.total, 3);
1899 assert_eq!(adult_page.items.len(), 2);
1900
1901 repo.save(User::new(1, "Alice Smith", 31, "alice.smith@example.com"))
1903 .unwrap();
1904 let updated = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1905 assert_eq!(updated.name, "Alice Smith");
1906 assert_eq!(updated.age, 31);
1907
1908 let deleted = repo.delete(&Value::I64(2)).unwrap();
1910 assert_eq!(deleted, 1);
1911 assert_eq!(repo.count().unwrap(), 4);
1912 assert!(!repo.exists(&Value::I64(2)).unwrap());
1913
1914 let deleted_by = repo
1916 .delete_by(&[WhereCondition::new("age", WhereOp::Ge, Value::I64(31))])
1917 .unwrap();
1918 assert_eq!(deleted_by, 3);
1919 assert_eq!(repo.count().unwrap(), 1); }
1921
1922 #[test]
1923 fn test_e2e_pagination_navigation() {
1924 let users: Vec<User> = (1..=100)
1925 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1926 .collect();
1927 let repo = InMemoryRepository::from_vec(users);
1928
1929 let mut current_page = 1u64;
1930 let mut visited: Vec<u64> = Vec::new();
1931 loop {
1932 let page = repo.paginate(current_page, 10).unwrap();
1933 visited.push(current_page);
1934 if !page.has_next() {
1935 break;
1936 }
1937 current_page += 1;
1938 }
1939 assert_eq!(visited.len(), 10);
1940 assert_eq!(visited, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1941 }
1942}