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!(result.len(), 1, "LIKE exact match should be case-insensitive");
1488 }
1489
1490 #[test]
1491 fn test_repo_find_by_with_or_filter_multi_field_keyword() {
1492 let repo = InMemoryRepository::from_vec(vec![
1494 User::new(1, "Alice", 30, "alice@example.com"),
1495 User::new(2, "Bob", 25, "bob@example.com"),
1496 User::new(3, "Carol", 28, "carol@kw.com"), User::new(4, "Dave", 32, "dave@example.com"),
1498 ]);
1499
1500 let and = vec![]; let or = vec![
1502 WhereCondition::new(
1503 "name",
1504 WhereOp::Like,
1505 Value::String("%kw%".to_string()),
1506 ),
1507 WhereCondition::new(
1508 "email",
1509 WhereOp::Like,
1510 Value::String("%kw%".to_string()),
1511 ),
1512 ];
1513
1514 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1515 assert_eq!(result.len(), 1);
1517 assert_eq!(result[0].key(), 3);
1518 }
1519
1520 #[test]
1521 fn test_repo_find_by_with_or_filter_combined_with_and() {
1522 let repo = InMemoryRepository::from_vec(vec![
1524 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"), ]);
1529
1530 let and = vec![WhereCondition::new("age", WhereOp::Ge, Value::I64(28))];
1532 let or = vec![
1533 WhereCondition::new(
1534 "name",
1535 WhereOp::Like,
1536 Value::String("%kw%".to_string()),
1537 ),
1538 WhereCondition::new(
1539 "email",
1540 WhereOp::Like,
1541 Value::String("%kw%".to_string()),
1542 ),
1543 ];
1544
1545 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1546 assert_eq!(result.len(), 2);
1550 let ids: Vec<i64> = result.iter().map(|u| u.key()).collect();
1551 assert!(ids.contains(&1));
1552 assert!(ids.contains(&3));
1553 }
1554
1555 #[test]
1556 fn test_repo_find_by_with_or_filter_empty_or() {
1557 let repo = InMemoryRepository::from_vec(vec![
1559 User::new(1, "Alice", 30, "alice@example.com"),
1560 User::new(2, "Bob", 25, "bob@example.com"),
1561 ]);
1562
1563 let and = vec![WhereCondition::new("age", WhereOp::Ge, Value::I64(28))];
1564 let or: Vec<WhereCondition> = vec![];
1565
1566 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1567 assert_eq!(result.len(), 1);
1568 assert_eq!(result[0].key(), 1);
1569 }
1570
1571 #[test]
1572 fn test_repo_find_by_in() {
1573 let repo = InMemoryRepository::from_vec(vec![
1574 User::new(1, "Alice", 30, "a@b.com"),
1575 User::new(2, "Bob", 25, "b@b.com"),
1576 User::new(3, "Carol", 28, "c@b.com"),
1577 User::new(4, "Dave", 32, "d@b.com"),
1578 ]);
1579
1580 let result = repo
1581 .find_by(&[WhereCondition::in_op(
1582 "id",
1583 WhereOp::In,
1584 vec![Value::I64(1), Value::I64(3)],
1585 )])
1586 .unwrap();
1587 assert_eq!(result.len(), 2);
1588 }
1589
1590 #[test]
1591 fn test_repo_find_by_not_in() {
1592 let repo = InMemoryRepository::from_vec(vec![
1593 User::new(1, "Alice", 30, "a@b.com"),
1594 User::new(2, "Bob", 25, "b@b.com"),
1595 User::new(3, "Carol", 28, "c@b.com"),
1596 ]);
1597
1598 let result = repo
1599 .find_by(&[WhereCondition::in_op(
1600 "id",
1601 WhereOp::NotIn,
1602 vec![Value::I64(1)],
1603 )])
1604 .unwrap();
1605 assert_eq!(result.len(), 2);
1606 assert!(result.iter().all(|u| u.id != 1));
1607 }
1608
1609 #[test]
1610 fn test_repo_find_by_between() {
1611 let repo = InMemoryRepository::from_vec(vec![
1612 User::new(1, "Alice", 30, "a@b.com"),
1613 User::new(2, "Bob", 25, "b@b.com"),
1614 User::new(3, "Carol", 35, "c@b.com"),
1615 User::new(4, "Dave", 22, "d@b.com"),
1616 ]);
1617
1618 let result = repo
1619 .find_by(&[WhereCondition::between(
1620 "age",
1621 Value::I64(25),
1622 Value::I64(35),
1623 )])
1624 .unwrap();
1625 assert_eq!(result.len(), 3);
1626 }
1627
1628 #[test]
1629 fn test_repo_find_by_multiple_conditions() {
1630 let repo = InMemoryRepository::from_vec(vec![
1631 User::new(1, "Alice", 30, "a@b.com"),
1632 User::new(2, "Bob", 30, "b@b.com"),
1633 User::new(3, "Alice", 25, "c@b.com"),
1634 ]);
1635
1636 let result = repo
1637 .find_by(&[
1638 WhereCondition::new("name", WhereOp::Eq, Value::String("Alice".to_string())),
1639 WhereCondition::new("age", WhereOp::Ge, Value::I64(30)),
1640 ])
1641 .unwrap();
1642 assert_eq!(result.len(), 1);
1643 assert_eq!(result[0].id, 1);
1644 }
1645
1646 #[test]
1647 fn test_repo_find_one_by() {
1648 let repo = InMemoryRepository::from_vec(vec![
1649 User::new(1, "Alice", 30, "a@b.com"),
1650 User::new(2, "Bob", 25, "b@b.com"),
1651 ]);
1652
1653 let result = repo
1654 .find_one_by(&[WhereCondition::new(
1655 "name",
1656 WhereOp::Eq,
1657 Value::String("Bob".to_string()),
1658 )])
1659 .unwrap();
1660 assert!(result.is_some());
1661 assert_eq!(result.unwrap().id, 2);
1662 }
1663
1664 #[test]
1665 fn test_repo_find_one_by_missing() {
1666 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1667
1668 let result = repo
1669 .find_one_by(&[WhereCondition::new(
1670 "name",
1671 WhereOp::Eq,
1672 Value::String("Missing".to_string()),
1673 )])
1674 .unwrap();
1675 assert!(result.is_none());
1676 }
1677
1678 #[test]
1679 fn test_repo_count_by() {
1680 let repo = InMemoryRepository::from_vec(vec![
1681 User::new(1, "Alice", 30, "a@b.com"),
1682 User::new(2, "Bob", 30, "b@b.com"),
1683 User::new(3, "Carol", 25, "c@b.com"),
1684 ]);
1685
1686 let count = repo
1687 .count_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1688 .unwrap();
1689 assert_eq!(count, 2);
1690 }
1691
1692 #[test]
1693 fn test_repo_delete_by() {
1694 let repo = InMemoryRepository::from_vec(vec![
1695 User::new(1, "Alice", 30, "a@b.com"),
1696 User::new(2, "Bob", 30, "b@b.com"),
1697 User::new(3, "Carol", 25, "c@b.com"),
1698 ]);
1699
1700 let deleted = repo
1701 .delete_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1702 .unwrap();
1703 assert_eq!(deleted, 2);
1704 assert_eq!(repo.len(), 1);
1705 }
1706
1707 #[test]
1710 fn test_repo_paginate() {
1711 let users: Vec<User> = (1..=25)
1712 .map(|i| User::new(i, &format!("User{}", i), 20 + (i % 30), "u@b.com"))
1713 .collect();
1714 let repo = InMemoryRepository::from_vec(users);
1715
1716 let page = repo.paginate(1, 10).unwrap();
1717 assert_eq!(page.page, 1);
1718 assert_eq!(page.page_size, 10);
1719 assert_eq!(page.total, 25);
1720 assert_eq!(page.total_pages(), 3);
1721 assert_eq!(page.items.len(), 10);
1722 assert!(page.has_next());
1723 assert!(!page.has_prev());
1724 }
1725
1726 #[test]
1727 fn test_repo_paginate_last_page() {
1728 let users: Vec<User> = (1..=25)
1729 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1730 .collect();
1731 let repo = InMemoryRepository::from_vec(users);
1732
1733 let page = repo.paginate(3, 10).unwrap();
1734 assert_eq!(page.items.len(), 5);
1735 assert!(page.has_prev());
1736 assert!(!page.has_next());
1737 }
1738
1739 #[test]
1740 fn test_repo_paginate_out_of_range() {
1741 let users: Vec<User> = (1..=5)
1742 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1743 .collect();
1744 let repo = InMemoryRepository::from_vec(users);
1745
1746 let page = repo.paginate(10, 10).unwrap();
1747 assert_eq!(page.items.len(), 0);
1748 assert_eq!(page.total, 5);
1749 }
1750
1751 #[test]
1752 fn test_repo_paginate_by() {
1753 let users: Vec<User> = (1..=20)
1754 .map(|i| User::new(i, &format!("User{}", i), 20 + (i % 5), "u@b.com"))
1755 .collect();
1756 let repo = InMemoryRepository::from_vec(users);
1757
1758 let page = repo
1760 .paginate_by(
1761 &[WhereCondition::new("age", WhereOp::Eq, Value::I64(22))],
1762 1,
1763 2,
1764 )
1765 .unwrap();
1766 assert_eq!(page.total, 4);
1767 assert_eq!(page.items.len(), 2);
1768 assert_eq!(page.total_pages(), 2);
1769 }
1770
1771 #[test]
1774 fn test_generic_key_repo_basic() {
1775 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::new();
1776 assert!(repo.is_empty());
1777
1778 let user = User::new(1, "Alice", 30, "a@b.com");
1779 repo.save(user.clone()).unwrap();
1780 assert_eq!(repo.len(), 1);
1781
1782 let found = repo.find_by_id(&1).unwrap();
1783 assert_eq!(found, Some(user));
1784 }
1785
1786 #[test]
1787 fn test_generic_key_repo_delete() {
1788 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1789 User::new(1, "Alice", 30, "a@b.com"),
1790 User::new(2, "Bob", 25, "b@b.com"),
1791 ]);
1792
1793 let deleted = repo.delete(&1).unwrap();
1794 assert_eq!(deleted, 1);
1795 assert_eq!(repo.len(), 1);
1796
1797 let remaining = repo.find_all().unwrap();
1798 assert_eq!(remaining[0].id, 2);
1799 }
1800
1801 #[test]
1802 fn test_generic_key_repo_find_by() {
1803 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1804 User::new(1, "Alice", 30, "a@b.com"),
1805 User::new(2, "Bob", 30, "b@b.com"),
1806 User::new(3, "Carol", 25, "c@b.com"),
1807 ]);
1808
1809 let result = repo
1810 .find_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1811 .unwrap();
1812 assert_eq!(result.len(), 2);
1813 }
1814
1815 #[test]
1816 fn test_generic_key_repo_paginate() {
1817 let users: Vec<User> = (1..=15)
1818 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1819 .collect();
1820 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(users);
1821
1822 let page = repo.paginate(2, 10).unwrap();
1823 assert_eq!(page.items.len(), 5);
1824 assert_eq!(page.total, 15);
1825 assert_eq!(page.page, 2);
1826 }
1827
1828 #[test]
1829 fn test_generic_key_repo_count() {
1830 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1831 User::new(1, "Alice", 30, "a@b.com"),
1832 User::new(2, "Bob", 25, "b@b.com"),
1833 ]);
1834 assert_eq!(repo.count().unwrap(), 2);
1835 }
1836
1837 #[test]
1838 fn test_generic_key_repo_exists() {
1839 let repo: GenericKeyRepository<User, i64> =
1840 GenericKeyRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1841 assert!(repo.exists(&1).unwrap());
1842 assert!(!repo.exists(&999).unwrap());
1843 }
1844
1845 #[test]
1848 fn test_simple_regex_literal() {
1849 let re = simple_regex::compile("^abc$").unwrap();
1850 assert!(re.is_match("abc"));
1851 assert!(!re.is_match("abcd"));
1852 }
1853
1854 #[test]
1855 fn test_simple_regex_wildcard() {
1856 let re = simple_regex::compile("^Ali.*$").unwrap();
1857 assert!(re.is_match("Alice"));
1858 assert!(re.is_match("Alicia"));
1859 assert!(!re.is_match("Bob"));
1860 }
1861
1862 #[test]
1863 fn test_simple_regex_no_anchors() {
1864 let re = simple_regex::compile("ab").unwrap();
1865 assert!(re.is_match("ab"));
1866 }
1867
1868 #[test]
1871 fn test_e2e_repository_workflow() {
1872 let repo = InMemoryRepository::<User>::new();
1873
1874 let users = vec![
1876 User::new(1, "Alice", 30, "alice@example.com"),
1877 User::new(2, "Bob", 25, "bob@example.com"),
1878 User::new(3, "Carol", 35, "carol@example.com"),
1879 User::new(4, "Dave", 28, "dave@example.com"),
1880 User::new(5, "Eve", 32, "eve@example.com"),
1881 ];
1882 repo.save_many(users).unwrap();
1883 assert_eq!(repo.count().unwrap(), 5);
1884
1885 let adults = repo
1887 .find_by(&[WhereCondition::new("age", WhereOp::Ge, Value::I64(30))])
1888 .unwrap();
1889 assert_eq!(adults.len(), 3);
1890
1891 let page1 = repo.paginate(1, 2).unwrap();
1893 assert_eq!(page1.items.len(), 2);
1894 assert_eq!(page1.total_pages(), 3);
1895
1896 let page2 = repo.paginate(2, 2).unwrap();
1897 assert_eq!(page2.items.len(), 2);
1898
1899 let page3 = repo.paginate(3, 2).unwrap();
1900 assert_eq!(page3.items.len(), 1);
1901
1902 let adult_page = repo
1904 .paginate_by(
1905 &[WhereCondition::new("age", WhereOp::Ge, Value::I64(30))],
1906 1,
1907 2,
1908 )
1909 .unwrap();
1910 assert_eq!(adult_page.total, 3);
1911 assert_eq!(adult_page.items.len(), 2);
1912
1913 repo.save(User::new(1, "Alice Smith", 31, "alice.smith@example.com"))
1915 .unwrap();
1916 let updated = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1917 assert_eq!(updated.name, "Alice Smith");
1918 assert_eq!(updated.age, 31);
1919
1920 let deleted = repo.delete(&Value::I64(2)).unwrap();
1922 assert_eq!(deleted, 1);
1923 assert_eq!(repo.count().unwrap(), 4);
1924 assert!(!repo.exists(&Value::I64(2)).unwrap());
1925
1926 let deleted_by = repo
1928 .delete_by(&[WhereCondition::new("age", WhereOp::Ge, Value::I64(31))])
1929 .unwrap();
1930 assert_eq!(deleted_by, 3);
1931 assert_eq!(repo.count().unwrap(), 1); }
1933
1934 #[test]
1935 fn test_e2e_pagination_navigation() {
1936 let users: Vec<User> = (1..=100)
1937 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1938 .collect();
1939 let repo = InMemoryRepository::from_vec(users);
1940
1941 let mut current_page = 1u64;
1942 let mut visited: Vec<u64> = Vec::new();
1943 loop {
1944 let page = repo.paginate(current_page, 10).unwrap();
1945 visited.push(current_page);
1946 if !page.has_next() {
1947 break;
1948 }
1949 current_page += 1;
1950 }
1951 assert_eq!(visited.len(), 10);
1952 assert_eq!(visited, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1953 }
1954}