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,
112 pub op: WhereOp,
114 pub value: Value,
116 pub extra_values: Vec<Value>,
118}
119
120impl WhereCondition {
121 pub fn new(field: impl Into<String>, op: WhereOp, value: Value) -> Self {
123 Self {
124 field: field.into(),
125 op,
126 value,
127 extra_values: Vec::new(),
128 }
129 }
130
131 pub fn null_check(field: impl Into<String>, op: WhereOp) -> Self {
133 Self {
134 field: field.into(),
135 op,
136 value: Value::Null,
137 extra_values: Vec::new(),
138 }
139 }
140
141 pub fn in_op(field: impl Into<String>, op: WhereOp, values: Vec<Value>) -> Self {
143 Self {
144 field: field.into(),
145 op,
146 value: Value::Null,
147 extra_values: values,
148 }
149 }
150
151 pub fn between(field: impl Into<String>, low: Value, high: Value) -> Self {
153 Self {
154 field: field.into(),
155 op: WhereOp::Between,
156 value: low,
157 extra_values: vec![high],
158 }
159 }
160}
161
162#[derive(Debug, Clone)]
168pub struct PageResult<T> {
169 pub items: Vec<T>,
171 pub total: u64,
173 pub page: u64,
175 pub page_size: u64,
177}
178
179impl<T> PageResult<T> {
180 pub fn new(items: Vec<T>, total: u64, page: u64, page_size: u64) -> Self {
182 Self {
183 items,
184 total,
185 page,
186 page_size,
187 }
188 }
189
190 pub fn total_pages(&self) -> u64 {
192 if self.page_size == 0 {
193 return 0;
194 }
195 self.total.div_ceil(self.page_size)
196 }
197
198 pub fn has_next(&self) -> bool {
200 self.page < self.total_pages()
201 }
202
203 pub fn has_prev(&self) -> bool {
205 self.page > 1
206 }
207
208 pub fn is_empty(&self) -> bool {
210 self.items.is_empty()
211 }
212
213 pub fn len(&self) -> usize {
215 self.items.len()
216 }
217
218 pub fn map<U, F: Fn(T) -> U>(self, f: F) -> PageResult<U> {
220 PageResult {
221 items: self.items.into_iter().map(f).collect(),
222 total: self.total,
223 page: self.page,
224 page_size: self.page_size,
225 }
226 }
227}
228
229#[derive(Debug, Clone)]
249pub struct BatchUpdateResult<E> {
250 pub updated: Vec<E>,
252 pub skipped: usize,
254}
255
256impl<E> BatchUpdateResult<E> {
257 pub fn new(updated: Vec<E>, skipped: usize) -> Self {
259 Self { updated, skipped }
260 }
261
262 pub fn updated_count(&self) -> usize {
264 self.updated.len()
265 }
266
267 pub fn has_skipped(&self) -> bool {
269 self.skipped > 0
270 }
271
272 pub fn all_updated(&self) -> bool {
274 self.skipped == 0
275 }
276
277 pub fn total(&self) -> usize {
279 self.updated.len() + self.skipped
280 }
281
282 pub fn map<U, F: Fn(E) -> U>(self, f: F) -> BatchUpdateResult<U> {
284 BatchUpdateResult {
285 updated: self.updated.into_iter().map(f).collect(),
286 skipped: self.skipped,
287 }
288 }
289}
290
291impl<E> Default for BatchUpdateResult<E> {
292 fn default() -> Self {
293 Self {
294 updated: Vec::new(),
295 skipped: 0,
296 }
297 }
298}
299
300#[derive(Debug, Clone, PartialEq)]
306pub enum RepositoryError {
307 NotFound,
309 DatabaseError(String),
311 InvalidEntity(String),
313 Other(String),
315}
316
317impl std::fmt::Display for RepositoryError {
318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 match self {
320 RepositoryError::NotFound => write!(f, "entity not found"),
321 RepositoryError::DatabaseError(msg) => write!(f, "database error: {}", msg),
322 RepositoryError::InvalidEntity(msg) => write!(f, "invalid entity: {}", msg),
323 RepositoryError::Other(msg) => write!(f, "repository error: {}", msg),
324 }
325 }
326}
327
328impl std::error::Error for RepositoryError {}
329
330pub type RepositoryResult<T> = Result<T, RepositoryError>;
332
333pub trait Repository<E>: Send + Sync {
342 type Key: Clone + Debug + PartialEq + Send + Sync;
344
345 fn key_of(&self, entity: &E) -> Self::Key;
347
348 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>>;
350
351 fn find_all(&self) -> RepositoryResult<Vec<E>>;
353
354 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>>;
356
357 fn find_one_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Option<E>> {
359 let mut items = self.find_by(conditions)?;
360 if items.is_empty() {
361 Ok(None)
362 } else {
363 Ok(Some(items.remove(0)))
364 }
365 }
366
367 fn save(&self, entity: E) -> RepositoryResult<E>;
371
372 fn save_many(&self, entities: Vec<E>) -> RepositoryResult<Vec<E>> {
374 let mut saved = Vec::with_capacity(entities.len());
375 for e in entities {
376 saved.push(self.save(e)?);
377 }
378 Ok(saved)
379 }
380
381 fn batch_update(&self, entities: Vec<E>) -> RepositoryResult<BatchUpdateResult<E>> {
399 let mut updated = Vec::with_capacity(entities.len());
400 let mut skipped = 0usize;
401 for e in entities {
402 let key = self.key_of(&e);
403 if self.find_by_id(&key)?.is_some() {
404 updated.push(self.save(e)?);
405 } else {
406 skipped += 1;
407 }
408 }
409 Ok(BatchUpdateResult { updated, skipped })
410 }
411
412 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize>;
414
415 fn delete_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<usize> {
417 let items = self.find_by(conditions)?;
418 let mut count = 0;
419 for item in items {
420 let key = self.key_of(&item);
421 count += self.delete(&key)?;
422 }
423 Ok(count)
424 }
425
426 fn count(&self) -> RepositoryResult<u64>;
428
429 fn count_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<u64> {
431 let items = self.find_by(conditions)?;
432 Ok(items.len() as u64)
433 }
434
435 fn exists(&self, key: &Self::Key) -> RepositoryResult<bool> {
437 Ok(self.find_by_id(key)?.is_some())
438 }
439
440 fn paginate(&self, page: u64, page_size: u64) -> RepositoryResult<PageResult<E>>
442 where
443 E: Clone,
444 {
445 let all = self.find_all()?;
446 let total = all.len() as u64;
447 let start = ((page.saturating_sub(1)) * page_size) as usize;
448 let end = (start + page_size as usize).min(all.len());
449
450 let items = if start < all.len() {
451 all[start..end].to_vec()
452 } else {
453 Vec::new()
454 };
455
456 Ok(PageResult::new(items, total, page, page_size))
457 }
458
459 fn paginate_by(
461 &self,
462 conditions: &[WhereCondition],
463 page: u64,
464 page_size: u64,
465 ) -> RepositoryResult<PageResult<E>>
466 where
467 E: Clone,
468 {
469 let all = self.find_by(conditions)?;
470 let total = all.len() as u64;
471 let start = ((page.saturating_sub(1)) * page_size) as usize;
472 let end = (start + page_size as usize).min(all.len());
473
474 let items = if start < all.len() {
475 all[start..end].to_vec()
476 } else {
477 Vec::new()
478 };
479
480 Ok(PageResult::new(items, total, page, page_size))
481 }
482
483 fn find_by_with_or_filter(
519 &self,
520 and: &[WhereCondition],
521 or_filter: &[WhereCondition],
522 ) -> RepositoryResult<Vec<E>>
523 where
524 E: Clone + EntityAttributes,
525 {
526 let items = self.find_by(and)?;
527 if or_filter.is_empty() {
528 return Ok(items);
529 }
530 Ok(items
531 .into_iter()
532 .filter(|e| {
533 or_filter.iter().any(|c| {
534 let attr = e.get_attribute(&c.field);
535 match (attr, c.op) {
536 (None, WhereOp::IsNull) => true,
537 (None, _) => false,
538 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
539 }
540 })
541 })
542 .collect())
543 }
544}
545
546pub struct InMemoryRepository<E: Clone + Send + Sync + 'static> {
555 storage: RwLock<Vec<E>>,
556}
557
558impl<E: Clone + Send + Sync + 'static> InMemoryRepository<E> {
559 pub fn new() -> Self {
561 Self {
562 storage: RwLock::new(Vec::new()),
563 }
564 }
565
566 pub fn from_vec(items: Vec<E>) -> Self {
568 Self {
569 storage: RwLock::new(items),
570 }
571 }
572
573 pub fn len(&self) -> usize {
575 match self.storage.read() {
576 Ok(storage) => storage.len(),
577 Err(_) => 0,
578 }
579 }
580
581 pub fn is_empty(&self) -> bool {
583 self.len() == 0
584 }
585
586 pub fn clear(&self) {
588 if let Ok(mut storage) = self.storage.write() {
589 storage.clear();
590 }
591 }
592}
593
594impl<E: Clone + Send + Sync + 'static> Default for InMemoryRepository<E> {
595 fn default() -> Self {
596 Self::new()
597 }
598}
599
600fn value_matches(value: &Value, op: WhereOp, target: &Value, extras: &[Value]) -> bool {
602 use Value::*;
603 match op {
604 WhereOp::Eq => value == target,
605 WhereOp::Ne => value != target,
606 WhereOp::Gt => match (value, target) {
607 (I64(a), I64(b)) => a > b,
608 (F64(a), F64(b)) => a > b,
609 (F64(a), I64(b)) => a > &(*b as f64),
610 (I64(a), F64(b)) => a > &(*b as i64),
611 (String(a), String(b)) => a > b,
612 _ => false,
613 },
614 WhereOp::Ge => match (value, target) {
615 (I64(a), I64(b)) => a >= b,
616 (F64(a), F64(b)) => a >= b,
617 (String(a), String(b)) => a >= b,
618 _ => false,
619 },
620 WhereOp::Lt => match (value, target) {
621 (I64(a), I64(b)) => a < b,
622 (F64(a), F64(b)) => a < b,
623 (String(a), String(b)) => a < b,
624 _ => false,
625 },
626 WhereOp::Le => match (value, target) {
627 (I64(a), I64(b)) => a <= b,
628 (F64(a), F64(b)) => a <= b,
629 (String(a), String(b)) => a <= b,
630 _ => false,
631 },
632 WhereOp::Like => match (value, target) {
633 (String(a), String(b)) => {
634 let pattern = b.to_lowercase().replace('%', ".*").replace('_', ".");
637 let full_pattern = format!("^{}$", pattern);
638 if let Ok(re) = simple_regex::compile(&full_pattern) {
639 re.is_match(&a.to_lowercase())
640 } else {
641 false
642 }
643 }
644 _ => false,
645 },
646 WhereOp::In => extras.iter().any(|v| v == value),
647 WhereOp::NotIn => !extras.iter().any(|v| v == value),
648 WhereOp::IsNull => matches!(value, Null),
649 WhereOp::IsNotNull => !matches!(value, Null),
650 WhereOp::Between => {
651 if extras.is_empty() {
652 return false;
653 }
654 let low = target;
655 let high = &extras[0];
656 value_matches(value, WhereOp::Ge, low, &[])
658 && value_matches(value, WhereOp::Le, high, &[])
659 }
660 }
661}
662
663pub trait EntityAttributes: Send + Sync {
665 fn get_attribute(&self, field: &str) -> Option<Value>;
667}
668
669impl<E: Clone + Send + Sync + 'static + EntityAttributes> Repository<E> for InMemoryRepository<E> {
671 type Key = Value;
672
673 fn key_of(&self, entity: &E) -> Self::Key {
674 entity.get_attribute("id").unwrap_or(Value::Null)
675 }
676
677 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>> {
678 let storage = match self.storage.read() {
679 Ok(g) => g,
680 Err(_) => return Ok(None),
681 };
682 Ok(storage.iter().find(|e| self.key_of(e) == *key).cloned())
683 }
684
685 fn find_all(&self) -> RepositoryResult<Vec<E>> {
686 let storage = match self.storage.read() {
687 Ok(g) => g,
688 Err(_) => return Ok(Vec::new()),
689 };
690 Ok(storage.clone())
691 }
692
693 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>> {
694 let storage = match self.storage.read() {
695 Ok(g) => g,
696 Err(_) => return Ok(Vec::new()),
697 };
698 let result: Vec<E> = storage
699 .iter()
700 .filter(|e| {
701 conditions.iter().all(|c| {
702 let attr = e.get_attribute(&c.field);
703 match (attr, c.op) {
704 (None, WhereOp::IsNull) => true,
705 (None, _) => false,
706 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
707 }
708 })
709 })
710 .cloned()
711 .collect();
712 Ok(result)
713 }
714
715 fn save(&self, mut entity: E) -> RepositoryResult<E> {
716 let mut storage = match self.storage.write() {
717 Ok(g) => g,
718 Err(_) => return Ok(entity),
719 };
720 let key = self.key_of(&entity);
721
722 let existing_idx = storage.iter().position(|e| self.key_of(e) == key);
724
725 match existing_idx {
726 Some(idx) => {
727 storage[idx] = entity.clone();
728 }
729 None => {
730 storage.push(entity.clone());
731 }
732 }
733 let _ = &mut entity; Ok(entity)
736 }
737
738 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize> {
739 let mut storage = match self.storage.write() {
740 Ok(g) => g,
741 Err(_) => return Ok(0),
742 };
743 let before = storage.len();
744 storage.retain(|e| self.key_of(e) != *key);
745 Ok(before - storage.len())
746 }
747
748 fn count(&self) -> RepositoryResult<u64> {
749 Ok(self.len() as u64)
750 }
751}
752
753mod simple_regex {
758 pub struct Regex {
760 patterns: Vec<Pattern>,
761 }
762
763 enum Pattern {
764 AnyChars, Literal(char), Start, End, }
769
770 pub fn compile(pattern: &str) -> Result<Regex, String> {
771 let mut patterns = Vec::new();
772 let chars: Vec<char> = pattern.chars().collect();
773 let mut i = 0;
774 while i < chars.len() {
775 match chars[i] {
776 '^' => {
777 patterns.push(Pattern::Start);
778 i += 1;
779 }
780 '$' => {
781 patterns.push(Pattern::End);
782 i += 1;
783 }
784 '.' if i + 1 < chars.len() && chars[i + 1] == '*' => {
785 patterns.push(Pattern::AnyChars);
786 i += 2;
787 }
788 c => {
789 patterns.push(Pattern::Literal(c));
790 i += 1;
791 }
792 }
793 }
794 Ok(Regex { patterns })
795 }
796
797 impl Regex {
798 pub fn is_match(&self, text: &str) -> bool {
799 self.match_from(text, 0, 0)
800 }
801
802 fn match_from(&self, text: &str, text_idx: usize, pat_idx: usize) -> bool {
803 let chars: Vec<char> = text.chars().collect();
804 if pat_idx >= self.patterns.len() {
805 return text_idx == chars.len();
806 }
807 match &self.patterns[pat_idx] {
808 Pattern::Start => self.match_from(text, 0, pat_idx + 1),
809 Pattern::End => text_idx == chars.len(),
810 Pattern::Literal(c) => {
811 if text_idx < chars.len() && chars[text_idx] == *c {
812 self.match_from(text, text_idx + 1, pat_idx + 1)
813 } else {
814 false
815 }
816 }
817 Pattern::AnyChars => {
818 for skip in 0..=(chars.len() - text_idx) {
820 if self.match_from(text, text_idx + skip, pat_idx + 1) {
821 return true;
822 }
823 }
824 false
825 }
826 }
827 }
828 }
829}
830
831pub trait EntityKey<K>: Send + Sync {
837 fn key(&self) -> K;
839}
840
841pub struct GenericKeyRepository<E, K>
843where
844 E: Clone + Send + Sync + 'static,
845 K: Clone + Debug + PartialEq + Send + Sync + 'static,
846{
847 storage: RwLock<Vec<E>>,
848 _phantom: std::marker::PhantomData<K>,
849}
850
851impl<E, K> GenericKeyRepository<E, K>
852where
853 E: Clone + Send + Sync + 'static,
854 K: Clone + Debug + PartialEq + Send + Sync + 'static,
855{
856 pub fn new() -> Self {
858 Self {
859 storage: RwLock::new(Vec::new()),
860 _phantom: std::marker::PhantomData,
861 }
862 }
863
864 pub fn from_vec(items: Vec<E>) -> Self {
866 Self {
867 storage: RwLock::new(items),
868 _phantom: std::marker::PhantomData,
869 }
870 }
871
872 pub fn len(&self) -> usize {
874 match self.storage.read() {
875 Ok(g) => g.len(),
876 Err(_) => 0,
877 }
878 }
879
880 pub fn is_empty(&self) -> bool {
882 self.len() == 0
883 }
884
885 pub fn clear(&self) {
887 if let Ok(mut storage) = self.storage.write() {
888 storage.clear();
889 }
890 }
891}
892
893impl<E, K> Default for GenericKeyRepository<E, K>
894where
895 E: Clone + Send + Sync + 'static,
896 K: Clone + Debug + PartialEq + Send + Sync + 'static,
897{
898 fn default() -> Self {
899 Self::new()
900 }
901}
902
903impl<E, K> Repository<E> for GenericKeyRepository<E, K>
904where
905 E: Clone + Send + Sync + 'static + EntityKey<K> + EntityAttributes,
906 K: Clone + Debug + PartialEq + Send + Sync + 'static,
907{
908 type Key = K;
909
910 fn key_of(&self, entity: &E) -> Self::Key {
911 entity.key()
912 }
913
914 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>> {
915 let storage = match self.storage.read() {
916 Ok(g) => g,
917 Err(_) => return Ok(None),
918 };
919 Ok(storage.iter().find(|e| &e.key() == key).cloned())
920 }
921
922 fn find_all(&self) -> RepositoryResult<Vec<E>> {
923 let storage = match self.storage.read() {
924 Ok(g) => g,
925 Err(_) => return Ok(Vec::new()),
926 };
927 Ok(storage.clone())
928 }
929
930 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>> {
931 let storage = match self.storage.read() {
932 Ok(g) => g,
933 Err(_) => return Ok(Vec::new()),
934 };
935 let result: Vec<E> = storage
936 .iter()
937 .filter(|e| {
938 conditions.iter().all(|c| {
939 let attr = e.get_attribute(&c.field);
940 match (attr, c.op) {
941 (None, WhereOp::IsNull) => true,
942 (None, _) => false,
943 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
944 }
945 })
946 })
947 .cloned()
948 .collect();
949 Ok(result)
950 }
951
952 fn save(&self, entity: E) -> RepositoryResult<E> {
953 let mut storage = match self.storage.write() {
954 Ok(g) => g,
955 Err(_) => return Ok(entity),
956 };
957 let key = entity.key();
958 let existing_idx = storage.iter().position(|e| e.key() == key);
959 match existing_idx {
960 Some(idx) => {
961 storage[idx] = entity.clone();
962 }
963 None => {
964 storage.push(entity.clone());
965 }
966 }
967 Ok(entity)
968 }
969
970 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize> {
971 let mut storage = match self.storage.write() {
972 Ok(g) => g,
973 Err(_) => return Ok(0),
974 };
975 let before = storage.len();
976 storage.retain(|e| &e.key() != key);
977 Ok(before - storage.len())
978 }
979
980 fn count(&self) -> RepositoryResult<u64> {
981 Ok(self.len() as u64)
982 }
983}
984
985#[cfg(test)]
990mod tests {
991 use super::*;
992
993 #[derive(Debug, Clone, PartialEq)]
996 struct User {
997 id: i64,
998 name: String,
999 age: i64,
1000 email: String,
1001 }
1002
1003 impl User {
1004 fn new(id: i64, name: &str, age: i64, email: &str) -> Self {
1005 Self {
1006 id,
1007 name: name.to_string(),
1008 age,
1009 email: email.to_string(),
1010 }
1011 }
1012 }
1013
1014 impl EntityAttributes for User {
1015 fn get_attribute(&self, field: &str) -> Option<Value> {
1016 match field {
1017 "id" => Some(Value::I64(self.id)),
1018 "name" => Some(Value::String(self.name.clone())),
1019 "age" => Some(Value::I64(self.age)),
1020 "email" => Some(Value::String(self.email.clone())),
1021 _ => None,
1022 }
1023 }
1024 }
1025
1026 impl EntityKey<i64> for User {
1027 fn key(&self) -> i64 {
1028 self.id
1029 }
1030 }
1031
1032 #[test]
1035 fn test_where_op_name() {
1036 assert_eq!(WhereOp::Eq.name(), "eq");
1037 assert_eq!(WhereOp::Ne.name(), "ne");
1038 assert_eq!(WhereOp::Gt.name(), "gt");
1039 assert_eq!(WhereOp::Like.name(), "like");
1040 assert_eq!(WhereOp::In.name(), "in");
1041 assert_eq!(WhereOp::IsNull.name(), "is_null");
1042 assert_eq!(WhereOp::Between.name(), "between");
1043 }
1044
1045 #[test]
1046 fn test_where_condition_new() {
1047 let c = WhereCondition::new("age", WhereOp::Ge, Value::I64(18));
1048 assert_eq!(c.field, "age");
1049 assert_eq!(c.op, WhereOp::Ge);
1050 assert_eq!(c.value, Value::I64(18));
1051 assert!(c.extra_values.is_empty());
1052 }
1053
1054 #[test]
1055 fn test_where_condition_null_check() {
1056 let c = WhereCondition::null_check("deleted_at", WhereOp::IsNull);
1057 assert_eq!(c.field, "deleted_at");
1058 assert_eq!(c.op, WhereOp::IsNull);
1059 assert_eq!(c.value, Value::Null);
1060 }
1061
1062 #[test]
1063 fn test_where_condition_in() {
1064 let c = WhereCondition::in_op(
1065 "id",
1066 WhereOp::In,
1067 vec![Value::I64(1), Value::I64(2), Value::I64(3)],
1068 );
1069 assert_eq!(c.field, "id");
1070 assert_eq!(c.op, WhereOp::In);
1071 assert_eq!(c.extra_values.len(), 3);
1072 }
1073
1074 #[test]
1075 fn test_where_condition_between() {
1076 let c = WhereCondition::between("age", Value::I64(18), Value::I64(30));
1077 assert_eq!(c.field, "age");
1078 assert_eq!(c.op, WhereOp::Between);
1079 assert_eq!(c.value, Value::I64(18));
1080 assert_eq!(c.extra_values, vec![Value::I64(30)]);
1081 }
1082
1083 #[test]
1086 fn test_page_result_total_pages() {
1087 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1088 assert_eq!(pr.total_pages(), 10);
1089 }
1090
1091 #[test]
1092 fn test_page_result_total_pages_with_remainder() {
1093 let pr: PageResult<i32> = PageResult::new(vec![], 105, 1, 10);
1094 assert_eq!(pr.total_pages(), 11);
1095 }
1096
1097 #[test]
1098 fn test_page_result_total_pages_zero_size() {
1099 let pr: PageResult<i32> = PageResult::new(vec![], 100, 1, 0);
1100 assert_eq!(pr.total_pages(), 0);
1101 }
1102
1103 #[test]
1104 fn test_page_result_has_next() {
1105 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1106 assert!(pr.has_next());
1107 assert!(!pr.has_prev());
1108 }
1109
1110 #[test]
1111 fn test_page_result_has_prev() {
1112 let pr = PageResult::new(vec![1, 2, 3], 100, 5, 10);
1113 assert!(pr.has_prev());
1114 assert!(pr.has_next()); }
1116
1117 #[test]
1118 fn test_page_result_is_empty() {
1119 let pr: PageResult<i32> = PageResult::new(vec![], 0, 1, 10);
1120 assert!(pr.is_empty());
1121 assert_eq!(pr.len(), 0);
1122 }
1123
1124 #[test]
1125 fn test_page_result_map() {
1126 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1127 let mapped = pr.map(|x| x * 2);
1128 assert_eq!(mapped.items, vec![2, 4, 6]);
1129 assert_eq!(mapped.total, 100);
1130 }
1131
1132 #[test]
1135 fn test_repository_error_display() {
1136 let e = RepositoryError::NotFound;
1137 assert_eq!(e.to_string(), "entity not found");
1138
1139 let e = RepositoryError::DatabaseError("conn refused".to_string());
1140 assert_eq!(e.to_string(), "database error: conn refused");
1141
1142 let e = RepositoryError::InvalidEntity("missing id".to_string());
1143 assert_eq!(e.to_string(), "invalid entity: missing id");
1144
1145 let e = RepositoryError::Other("custom".to_string());
1146 assert_eq!(e.to_string(), "repository error: custom");
1147 }
1148
1149 #[test]
1150 fn test_repository_error_eq() {
1151 assert_eq!(RepositoryError::NotFound, RepositoryError::NotFound);
1152 assert_ne!(
1153 RepositoryError::NotFound,
1154 RepositoryError::Other("x".to_string())
1155 );
1156 }
1157
1158 #[test]
1161 fn test_inmemory_create_empty() {
1162 let repo = InMemoryRepository::<User>::new();
1163 assert!(repo.is_empty());
1164 assert_eq!(repo.len(), 0);
1165 }
1166
1167 #[test]
1168 fn test_inmemory_from_vec() {
1169 let repo = InMemoryRepository::from_vec(vec![
1170 User::new(1, "Alice", 30, "alice@example.com"),
1171 User::new(2, "Bob", 25, "bob@example.com"),
1172 ]);
1173 assert_eq!(repo.len(), 2);
1174 }
1175
1176 #[test]
1177 fn test_inmemory_clear() {
1178 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1179 assert_eq!(repo.len(), 1);
1180 repo.clear();
1181 assert_eq!(repo.len(), 0);
1182 }
1183
1184 #[test]
1187 fn test_repo_save_and_find_by_id() {
1188 let repo = InMemoryRepository::<User>::new();
1189 let user = User::new(1, "Alice", 30, "alice@example.com");
1190 let saved = repo.save(user.clone()).unwrap();
1191 assert_eq!(saved, user);
1192
1193 let found = repo.find_by_id(&Value::I64(1)).unwrap();
1194 assert_eq!(found, Some(user));
1195 }
1196
1197 #[test]
1198 fn test_repo_find_by_id_missing() {
1199 let repo = InMemoryRepository::<User>::new();
1200 let found = repo.find_by_id(&Value::I64(999)).unwrap();
1201 assert_eq!(found, None);
1202 }
1203
1204 #[test]
1205 fn test_repo_save_many() {
1206 let repo = InMemoryRepository::<User>::new();
1207 let users = vec![
1208 User::new(1, "Alice", 30, "alice@example.com"),
1209 User::new(2, "Bob", 25, "bob@example.com"),
1210 User::new(3, "Carol", 28, "carol@example.com"),
1211 ];
1212 let saved = repo.save_many(users.clone()).unwrap();
1213 assert_eq!(saved.len(), 3);
1214 assert_eq!(repo.len(), 3);
1215 }
1216
1217 #[test]
1220 fn test_batch_update_result_new() {
1221 let result = BatchUpdateResult::new(vec![1, 2, 3], 2);
1222 assert_eq!(result.updated_count(), 3);
1223 assert_eq!(result.skipped, 2);
1224 assert_eq!(result.total(), 5);
1225 assert!(result.has_skipped());
1226 assert!(!result.all_updated());
1227 }
1228
1229 #[test]
1230 fn test_batch_update_result_all_updated() {
1231 let result: BatchUpdateResult<i32> = BatchUpdateResult::new(vec![1, 2], 0);
1232 assert!(!result.has_skipped());
1233 assert!(result.all_updated());
1234 assert_eq!(result.total(), 2);
1235 }
1236
1237 #[test]
1238 fn test_batch_update_result_default() {
1239 let result: BatchUpdateResult<i32> = BatchUpdateResult::default();
1240 assert_eq!(result.updated_count(), 0);
1241 assert_eq!(result.skipped, 0);
1242 assert_eq!(result.total(), 0);
1243 }
1244
1245 #[test]
1246 fn test_batch_update_result_map() {
1247 let result = BatchUpdateResult::new(vec![1, 2, 3], 1);
1248 let mapped = result.map(|x| x * 10);
1249 assert_eq!(mapped.updated, vec![10, 20, 30]);
1250 assert_eq!(mapped.skipped, 1);
1251 }
1252
1253 #[test]
1254 fn test_repo_batch_update_all_existing() {
1255 let repo = InMemoryRepository::from_vec(vec![
1257 User::new(1, "Alice", 30, "a@b.com"),
1258 User::new(2, "Bob", 25, "b@b.com"),
1259 ]);
1260 let updates = vec![
1261 User::new(1, "Alice Updated", 31, "a2@b.com"),
1262 User::new(2, "Bob Updated", 26, "b2@b.com"),
1263 ];
1264 let result = repo.batch_update(updates).unwrap();
1265 assert_eq!(result.updated_count(), 2);
1266 assert_eq!(result.skipped, 0);
1267 assert!(result.all_updated());
1268
1269 let alice = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1271 assert_eq!(alice.name, "Alice Updated");
1272 assert_eq!(alice.age, 31);
1273 let bob = repo.find_by_id(&Value::I64(2)).unwrap().unwrap();
1274 assert_eq!(bob.name, "Bob Updated");
1275 assert_eq!(bob.age, 26);
1276 assert_eq!(repo.len(), 2);
1278 }
1279
1280 #[test]
1281 fn test_repo_batch_update_partial_missing() {
1282 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1284 let updates = vec![
1285 User::new(1, "Alice Updated", 31, "a2@b.com"),
1286 User::new(999, "Ghost", 1, "ghost@b.com"), ];
1288 let result = repo.batch_update(updates).unwrap();
1289 assert_eq!(result.updated_count(), 1);
1290 assert_eq!(result.skipped, 1);
1291 assert!(result.has_skipped());
1292
1293 let alice = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1295 assert_eq!(alice.name, "Alice Updated");
1296 assert!(repo.find_by_id(&Value::I64(999)).unwrap().is_none());
1298 assert_eq!(repo.len(), 1);
1299 }
1300
1301 #[test]
1302 fn test_repo_batch_update_all_missing() {
1303 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1305 let updates = vec![
1306 User::new(100, "Ghost1", 1, "g1@b.com"),
1307 User::new(200, "Ghost2", 2, "g2@b.com"),
1308 ];
1309 let result = repo.batch_update(updates).unwrap();
1310 assert_eq!(result.updated_count(), 0);
1311 assert_eq!(result.skipped, 2);
1312 assert_eq!(repo.len(), 1); }
1314
1315 #[test]
1316 fn test_repo_batch_update_empty() {
1317 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1318 let result = repo.batch_update(vec![]).unwrap();
1319 assert_eq!(result.updated_count(), 0);
1320 assert_eq!(result.skipped, 0);
1321 assert_eq!(result.total(), 0);
1322 }
1323
1324 #[test]
1325 fn test_repo_batch_update_distinct_from_save_many() {
1326 let repo1 = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1330 let repo2 = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1331
1332 let updates = vec![
1333 User::new(1, "Alice Updated", 31, "a2@b.com"),
1334 User::new(999, "New User", 1, "new@b.com"),
1335 ];
1336
1337 let saved = repo1.save_many(updates.clone()).unwrap();
1339 assert_eq!(saved.len(), 2);
1340 assert_eq!(repo1.len(), 2); let result = repo2.batch_update(updates).unwrap();
1344 assert_eq!(result.updated_count(), 1);
1345 assert_eq!(result.skipped, 1);
1346 assert_eq!(repo2.len(), 1); }
1348
1349 #[test]
1350 fn test_repo_save_update_existing() {
1351 let repo = InMemoryRepository::<User>::new();
1352 repo.save(User::new(1, "Alice", 30, "alice@example.com"))
1353 .unwrap();
1354
1355 repo.save(User::new(1, "Alice Updated", 31, "alice2@example.com"))
1357 .unwrap();
1358
1359 assert_eq!(repo.len(), 1);
1360 let found = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1361 assert_eq!(found.name, "Alice Updated");
1362 assert_eq!(found.age, 31);
1363 }
1364
1365 #[test]
1366 fn test_repo_find_all() {
1367 let repo = InMemoryRepository::from_vec(vec![
1368 User::new(1, "Alice", 30, "a@b.com"),
1369 User::new(2, "Bob", 25, "b@b.com"),
1370 ]);
1371 let all = repo.find_all().unwrap();
1372 assert_eq!(all.len(), 2);
1373 }
1374
1375 #[test]
1376 fn test_repo_find_all_empty() {
1377 let repo = InMemoryRepository::<User>::new();
1378 let all = repo.find_all().unwrap();
1379 assert!(all.is_empty());
1380 }
1381
1382 #[test]
1383 fn test_repo_delete() {
1384 let repo = InMemoryRepository::from_vec(vec![
1385 User::new(1, "Alice", 30, "a@b.com"),
1386 User::new(2, "Bob", 25, "b@b.com"),
1387 ]);
1388 let deleted = repo.delete(&Value::I64(1)).unwrap();
1389 assert_eq!(deleted, 1);
1390 assert_eq!(repo.len(), 1);
1391 }
1392
1393 #[test]
1394 fn test_repo_delete_missing() {
1395 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1396 let deleted = repo.delete(&Value::I64(999)).unwrap();
1397 assert_eq!(deleted, 0);
1398 assert_eq!(repo.len(), 1);
1399 }
1400
1401 #[test]
1402 fn test_repo_count() {
1403 let repo = InMemoryRepository::from_vec(vec![
1404 User::new(1, "Alice", 30, "a@b.com"),
1405 User::new(2, "Bob", 25, "b@b.com"),
1406 User::new(3, "Carol", 28, "c@b.com"),
1407 ]);
1408 assert_eq!(repo.count().unwrap(), 3);
1409 }
1410
1411 #[test]
1412 fn test_repo_count_empty() {
1413 let repo = InMemoryRepository::<User>::new();
1414 assert_eq!(repo.count().unwrap(), 0);
1415 }
1416
1417 #[test]
1418 fn test_repo_exists() {
1419 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1420 assert!(repo.exists(&Value::I64(1)).unwrap());
1421 assert!(!repo.exists(&Value::I64(999)).unwrap());
1422 }
1423
1424 #[test]
1427 fn test_repo_find_by_eq() {
1428 let repo = InMemoryRepository::from_vec(vec![
1429 User::new(1, "Alice", 30, "a@b.com"),
1430 User::new(2, "Bob", 30, "b@b.com"),
1431 User::new(3, "Carol", 25, "c@b.com"),
1432 ]);
1433
1434 let result = repo
1435 .find_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1436 .unwrap();
1437 assert_eq!(result.len(), 2);
1438 }
1439
1440 #[test]
1441 fn test_repo_find_by_gt() {
1442 let repo = InMemoryRepository::from_vec(vec![
1443 User::new(1, "Alice", 30, "a@b.com"),
1444 User::new(2, "Bob", 25, "b@b.com"),
1445 User::new(3, "Carol", 35, "c@b.com"),
1446 ]);
1447
1448 let result = repo
1449 .find_by(&[WhereCondition::new("age", WhereOp::Gt, Value::I64(28))])
1450 .unwrap();
1451 assert_eq!(result.len(), 2);
1452 }
1453
1454 #[test]
1455 fn test_repo_find_by_like() {
1456 let repo = InMemoryRepository::from_vec(vec![
1457 User::new(1, "Alice", 30, "alice@example.com"),
1458 User::new(2, "Bob", 25, "bob@example.com"),
1459 User::new(3, "Alicia", 28, "alicia@test.com"),
1460 ]);
1461
1462 let result = repo
1463 .find_by(&[WhereCondition::new(
1464 "name",
1465 WhereOp::Like,
1466 Value::String("Ali%".to_string()),
1467 )])
1468 .unwrap();
1469 assert_eq!(result.len(), 2);
1470 }
1471
1472 #[test]
1473 fn test_repo_find_by_like_case_insensitive() {
1474 let repo = InMemoryRepository::from_vec(vec![
1476 User::new(1, "Alice", 30, "alice@example.com"),
1477 User::new(2, "bob", 25, "bob@example.com"),
1478 User::new(3, "ALICIA", 28, "alicia@test.com"),
1479 ]);
1480
1481 let result = repo
1483 .find_by(&[WhereCondition::new(
1484 "name",
1485 WhereOp::Like,
1486 Value::String("ali%".to_string()),
1487 )])
1488 .unwrap();
1489 assert_eq!(result.len(), 2, "LIKE should be case-insensitive");
1490
1491 let result = repo
1493 .find_by(&[WhereCondition::new(
1494 "name",
1495 WhereOp::Like,
1496 Value::String("BOB".to_string()),
1497 )])
1498 .unwrap();
1499 assert_eq!(
1500 result.len(),
1501 1,
1502 "LIKE exact match should be case-insensitive"
1503 );
1504 }
1505
1506 #[test]
1507 fn test_repo_find_by_with_or_filter_multi_field_keyword() {
1508 let repo = InMemoryRepository::from_vec(vec![
1510 User::new(1, "Alice", 30, "alice@example.com"),
1511 User::new(2, "Bob", 25, "bob@example.com"),
1512 User::new(3, "Carol", 28, "carol@kw.com"), User::new(4, "Dave", 32, "dave@example.com"),
1514 ]);
1515
1516 let and = vec![]; let or = vec![
1518 WhereCondition::new("name", WhereOp::Like, Value::String("%kw%".to_string())),
1519 WhereCondition::new("email", WhereOp::Like, Value::String("%kw%".to_string())),
1520 ];
1521
1522 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1523 assert_eq!(result.len(), 1);
1525 assert_eq!(result[0].key(), 3);
1526 }
1527
1528 #[test]
1529 fn test_repo_find_by_with_or_filter_combined_with_and() {
1530 let repo = InMemoryRepository::from_vec(vec![
1532 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"), ]);
1537
1538 let and = vec![WhereCondition::new("age", WhereOp::Ge, Value::I64(28))];
1540 let or = vec![
1541 WhereCondition::new("name", WhereOp::Like, Value::String("%kw%".to_string())),
1542 WhereCondition::new("email", WhereOp::Like, Value::String("%kw%".to_string())),
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}