1use std::collections::HashMap;
28use std::collections::HashSet;
29use std::time::Duration;
30use std::time::Instant;
31
32use parking_lot::RwLock;
33
34use crate::compute_md5;
35use crate::CacheDriver;
36use crate::CacheError;
37
38const MEMCACHED_MAX_VALUE_SIZE: usize = 1024 * 1024;
40
41const MEMCACHED_MAX_KEY_LEN: usize = 250;
43
44const MEMCACHED_MAX_TTL_SECS: u64 = 30 * 24 * 60 * 60;
46
47#[derive(Debug, Clone)]
61pub struct MemcachedConfig {
62 pub host: String,
64 pub port: u16,
66 pub expire: Option<Duration>,
68 pub prefix: String,
70 pub tag_prefix: String,
72 pub timeout: Duration,
74 pub weight: u32,
76}
77
78impl Default for MemcachedConfig {
79 fn default() -> Self {
80 Self {
81 host: "127.0.0.1".to_string(),
82 port: 11211,
83 expire: None,
84 prefix: String::new(),
85 tag_prefix: "tag:".to_string(),
86 timeout: Duration::ZERO,
87 weight: 0,
88 }
89 }
90}
91
92impl MemcachedConfig {
93 pub fn with_prefix(prefix: impl Into<String>) -> Self {
95 Self {
96 prefix: prefix.into(),
97 ..Default::default()
98 }
99 }
100
101 pub fn with_expire(expire: Duration) -> Self {
103 Self {
104 expire: Some(expire),
105 ..Default::default()
106 }
107 }
108}
109
110pub trait MemcachedBackend: Send + Sync {
127 fn get(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError>;
129
130 fn set(&self, key: &str, value: Vec<u8>, ttl: Duration) -> Result<(), CacheError>;
135
136 fn delete(&self, key: &str) -> Result<bool, CacheError>;
140
141 fn increment(&self, key: &str, step: u64) -> Result<i64, CacheError>;
146
147 fn decrement(&self, key: &str, step: u64) -> Result<i64, CacheError>;
151
152 fn flush(&self) -> Result<(), CacheError>;
154
155 fn touch(&self, key: &str, ttl: Duration) -> Result<bool, CacheError>;
157}
158
159type MemcachedKvEntry = (Vec<u8>, Option<Instant>);
170
171type MemcachedKvMap = HashMap<String, MemcachedKvEntry>;
173
174pub struct MockMemcachedBackend {
178 kv: RwLock<MemcachedKvMap>,
180}
181
182impl Default for MockMemcachedBackend {
183 fn default() -> Self {
184 Self::new()
185 }
186}
187
188impl MockMemcachedBackend {
189 pub fn new() -> Self {
191 Self {
192 kv: RwLock::new(HashMap::new()),
193 }
194 }
195
196 fn is_expired(expires_at: Option<Instant>) -> bool {
198 match expires_at {
199 Some(exp) => Instant::now() >= exp,
200 None => false,
201 }
202 }
203
204 fn cleanup_expired(kv: &mut HashMap<String, (Vec<u8>, Option<Instant>)>, key: &str) {
206 if let Some((_, Some(exp))) = kv.get(key) {
207 if Self::is_expired(Some(*exp)) {
208 kv.remove(key);
209 }
210 }
211 }
212}
213
214impl MemcachedBackend for MockMemcachedBackend {
215 fn get(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
216 let mut kv = self.kv.write();
217 Self::cleanup_expired(&mut kv, key);
218 Ok(kv.get(key).map(|(v, _)| v.clone()))
219 }
220
221 fn set(&self, key: &str, value: Vec<u8>, ttl: Duration) -> Result<(), CacheError> {
222 if value.len() > MEMCACHED_MAX_VALUE_SIZE {
223 return Err(CacheError::SerializationError(format!(
224 "Memcached value size {} exceeds max {} bytes",
225 value.len(),
226 MEMCACHED_MAX_VALUE_SIZE
227 )));
228 }
229
230 let expires_at = if ttl == Duration::ZERO {
231 None
232 } else {
233 Some(Instant::now() + ttl)
234 };
235
236 let mut kv = self.kv.write();
237 kv.insert(key.to_string(), (value, expires_at));
238 Ok(())
239 }
240
241 fn delete(&self, key: &str) -> Result<bool, CacheError> {
242 let mut kv = self.kv.write();
243 Ok(kv.remove(key).is_some())
244 }
245
246 fn increment(&self, key: &str, step: u64) -> Result<i64, CacheError> {
247 let mut kv = self.kv.write();
248 Self::cleanup_expired(&mut kv, key);
249
250 let current = match kv.get(key) {
251 Some((bytes, exp)) => {
252 if Self::is_expired(*exp) {
253 kv.remove(key);
254 0
255 } else {
256 let s = std::str::from_utf8(bytes).map_err(|e| {
257 CacheError::DeserializationError(format!(
258 "increment: value is not valid UTF-8: {}",
259 e
260 ))
261 })?;
262 s.parse::<i64>().map_err(|e| {
263 CacheError::DeserializationError(format!(
264 "increment: value '{}' is not numeric: {}",
265 s, e
266 ))
267 })?
268 }
269 }
270 None => 0,
271 };
272
273 let new_value = current + step as i64;
274 let expires_at = kv
275 .get(key)
276 .and_then(|(_, exp)| *exp)
277 .or_else(|| Some(Instant::now() + Duration::from_secs(MEMCACHED_MAX_TTL_SECS)));
278
279 kv.insert(
280 key.to_string(),
281 (new_value.to_string().into_bytes(), expires_at),
282 );
283 Ok(new_value)
284 }
285
286 fn decrement(&self, key: &str, step: u64) -> Result<i64, CacheError> {
287 let mut kv = self.kv.write();
288 Self::cleanup_expired(&mut kv, key);
289
290 let current = match kv.get(key) {
291 Some((bytes, exp)) => {
292 if Self::is_expired(*exp) {
293 kv.remove(key);
294 0
295 } else {
296 let s = std::str::from_utf8(bytes).map_err(|e| {
297 CacheError::DeserializationError(format!(
298 "decrement: value is not valid UTF-8: {}",
299 e
300 ))
301 })?;
302 s.parse::<i64>().map_err(|e| {
303 CacheError::DeserializationError(format!(
304 "decrement: value '{}' is not numeric: {}",
305 s, e
306 ))
307 })?
308 }
309 }
310 None => 0,
311 };
312
313 let new_value = (current - step as i64).max(0);
315 let expires_at = kv
316 .get(key)
317 .and_then(|(_, exp)| *exp)
318 .or_else(|| Some(Instant::now() + Duration::from_secs(MEMCACHED_MAX_TTL_SECS)));
319
320 kv.insert(
321 key.to_string(),
322 (new_value.to_string().into_bytes(), expires_at),
323 );
324 Ok(new_value)
325 }
326
327 fn flush(&self) -> Result<(), CacheError> {
328 let mut kv = self.kv.write();
329 kv.clear();
330 Ok(())
331 }
332
333 fn touch(&self, key: &str, ttl: Duration) -> Result<bool, CacheError> {
334 let mut kv = self.kv.write();
335 Self::cleanup_expired(&mut kv, key);
336
337 if let Some((_, ref mut exp)) = kv.get_mut(key) {
338 *exp = if ttl == Duration::ZERO {
339 None
340 } else {
341 Some(Instant::now() + ttl)
342 };
343 Ok(true)
344 } else {
345 Ok(false)
346 }
347 }
348}
349
350pub struct MemcachedCacheDriver {
379 backend: Box<dyn MemcachedBackend>,
380 config: MemcachedConfig,
381}
382
383impl MemcachedCacheDriver {
384 pub fn new(config: MemcachedConfig) -> Self {
388 Self::with_backend(config, Box::new(MockMemcachedBackend::new()))
389 }
390
391 pub fn with_backend(config: MemcachedConfig, backend: Box<dyn MemcachedBackend>) -> Self {
395 Self { backend, config }
396 }
397
398 pub fn config(&self) -> &MemcachedConfig {
400 &self.config
401 }
402
403 fn validate_key(key: &str) -> Result<(), CacheError> {
410 if key.is_empty() {
411 return Err(CacheError::Internal(
412 "Memcached key cannot be empty".to_string(),
413 ));
414 }
415 if key.len() > MEMCACHED_MAX_KEY_LEN {
416 return Err(CacheError::Internal(format!(
417 "Memcached key length {} exceeds max {} bytes",
418 key.len(),
419 MEMCACHED_MAX_KEY_LEN
420 )));
421 }
422 if key.chars().any(|c| c.is_control() || c == ' ') {
423 return Err(CacheError::Internal(format!(
424 "Memcached key contains invalid characters (space or control): {}",
425 key
426 )));
427 }
428 Ok(())
429 }
430
431 fn normalize_ttl(ttl: Duration) -> Duration {
440 let max_ttl = Duration::from_secs(MEMCACHED_MAX_TTL_SECS);
441 if ttl > max_ttl {
442 max_ttl
443 } else {
444 ttl
445 }
446 }
447
448 pub fn append(&self, name: &str, value: &str) -> Result<(), CacheError> {
455 let tag_key = self.get_tag_key(name);
456 let cache_key = self.get_cache_key(&tag_key);
457
458 let mut items: HashSet<String> = self.read_tag_set(&cache_key)?;
459 items.insert(value.to_string());
460
461 self.write_tag_set(&cache_key, &items)
462 }
463
464 pub fn get_tag_items(&self, tag: &str) -> Result<Vec<String>, CacheError> {
466 let name = self.get_tag_key(tag);
467 let cache_key = self.get_cache_key(&name);
468 let items = self.read_tag_set(&cache_key)?;
469 Ok(items.into_iter().collect())
470 }
471
472 pub fn clear_tag(&self, keys: &[&str]) -> Result<(), CacheError> {
476 for key in keys {
477 self.backend.delete(key)?;
478 }
479 Ok(())
480 }
481
482 fn read_tag_set(&self, cache_key: &str) -> Result<HashSet<String>, CacheError> {
484 match self.backend.get(cache_key)? {
485 Some(bytes) => {
486 if bytes.is_empty() {
487 return Ok(HashSet::new());
488 }
489 let json_str = std::str::from_utf8(&bytes).map_err(|e| {
490 CacheError::DeserializationError(format!("tag set is not valid UTF-8: {}", e))
491 })?;
492 let items: Vec<String> = serde_json::from_str(json_str).map_err(|e| {
493 CacheError::DeserializationError(format!(
494 "tag set JSON deserialization failed: {}",
495 e
496 ))
497 })?;
498 Ok(items.into_iter().collect())
499 }
500 None => Ok(HashSet::new()),
501 }
502 }
503
504 fn write_tag_set(&self, cache_key: &str, items: &HashSet<String>) -> Result<(), CacheError> {
506 let mut vec: Vec<String> = items.iter().cloned().collect();
507 vec.sort(); let json = serde_json::to_string(&vec).map_err(|e| {
509 CacheError::SerializationError(format!("tag set JSON serialization failed: {}", e))
510 })?;
511 let ttl = Duration::from_secs(MEMCACHED_MAX_TTL_SECS);
512 self.backend.set(cache_key, json.into_bytes(), ttl)?;
513 Ok(())
514 }
515}
516
517impl CacheDriver for MemcachedCacheDriver {
518 fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
519 Self::validate_key(key)?;
520 let cache_key = self.get_cache_key(key);
521 self.backend.get(&cache_key)
522 }
523
524 fn set_raw(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<(), CacheError> {
525 Self::validate_key(key)?;
526 let cache_key = self.get_cache_key(key);
527
528 let effective_ttl = ttl.or(self.config.expire);
530 let normalized_ttl = effective_ttl
532 .map(Self::normalize_ttl)
533 .unwrap_or_else(|| Duration::from_secs(MEMCACHED_MAX_TTL_SECS));
534
535 self.backend.set(&cache_key, value, normalized_ttl)
536 }
537
538 fn delete(&self, key: &str) -> Result<(), CacheError> {
539 Self::validate_key(key)?;
540 let cache_key = self.get_cache_key(key);
541 self.backend.delete(&cache_key)?;
542 Ok(())
543 }
544
545 fn has(&self, key: &str) -> Result<bool, CacheError> {
546 Self::validate_key(key)?;
547 let cache_key = self.get_cache_key(key);
548 Ok(self.backend.get(&cache_key)?.is_some())
549 }
550
551 fn inc(&self, key: &str, step: i64) -> Result<i64, CacheError> {
552 Self::validate_key(key)?;
553 let cache_key = self.get_cache_key(key);
554
555 if step >= 0 {
556 self.backend.increment(&cache_key, step as u64)
557 } else {
558 self.backend.decrement(&cache_key, (-step) as u64)
560 }
561 }
562
563 fn dec(&self, key: &str, step: i64) -> Result<i64, CacheError> {
564 self.inc(key, -step)
565 }
566
567 fn clear(&self) -> Result<(), CacheError> {
568 self.backend.flush()
569 }
570
571 fn get_cache_key(&self, name: &str) -> String {
572 if self.config.prefix.is_empty() {
573 name.to_string()
574 } else {
575 format!("{}{}", self.config.prefix, name)
576 }
577 }
578
579 fn get_tag_key(&self, tag: &str) -> String {
580 format!("{}{}", self.config.tag_prefix, compute_md5(tag))
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 #[test]
593 fn test_memcached_config_default() {
594 let config = MemcachedConfig::default();
595 assert_eq!(config.host, "127.0.0.1");
596 assert_eq!(config.port, 11211);
597 assert!(config.expire.is_none());
598 assert!(config.prefix.is_empty());
599 assert_eq!(config.tag_prefix, "tag:");
600 }
601
602 #[test]
603 fn test_memcached_config_with_prefix() {
604 let config = MemcachedConfig::with_prefix("myapp:");
605 assert_eq!(config.prefix, "myapp:");
606 assert_eq!(config.port, 11211);
607 }
608
609 #[test]
610 fn test_memcached_config_with_expire() {
611 let config = MemcachedConfig::with_expire(Duration::from_secs(3600));
612 assert_eq!(config.expire, Some(Duration::from_secs(3600)));
613 }
614
615 #[test]
620 fn test_mock_backend_set_get() {
621 let backend = MockMemcachedBackend::new();
622 backend
623 .set("key1", b"value1".to_vec(), Duration::from_secs(60))
624 .unwrap();
625 let result = backend.get("key1").unwrap();
626 assert_eq!(result, Some(b"value1".to_vec()));
627 }
628
629 #[test]
630 fn test_mock_backend_get_nonexistent() {
631 let backend = MockMemcachedBackend::new();
632 let result = backend.get("nonexistent").unwrap();
633 assert_eq!(result, None);
634 }
635
636 #[test]
637 fn test_mock_backend_delete() {
638 let backend = MockMemcachedBackend::new();
639 backend
640 .set("key1", b"value1".to_vec(), Duration::from_secs(60))
641 .unwrap();
642 assert!(backend.delete("key1").unwrap());
643 assert!(backend.get("key1").unwrap().is_none());
644 }
645
646 #[test]
647 fn test_mock_backend_delete_nonexistent() {
648 let backend = MockMemcachedBackend::new();
649 assert!(!backend.delete("nonexistent").unwrap());
650 }
651
652 #[test]
653 fn test_mock_backend_flush() {
654 let backend = MockMemcachedBackend::new();
655 backend
656 .set("key1", b"value1".to_vec(), Duration::from_secs(60))
657 .unwrap();
658 backend
659 .set("key2", b"value2".to_vec(), Duration::from_secs(60))
660 .unwrap();
661 backend.flush().unwrap();
662 assert!(backend.get("key1").unwrap().is_none());
663 assert!(backend.get("key2").unwrap().is_none());
664 }
665
666 #[test]
671 fn test_mock_backend_ttl_expiration() {
672 let backend = MockMemcachedBackend::new();
673 backend
674 .set("key1", b"value1".to_vec(), Duration::from_millis(10))
675 .unwrap();
676 std::thread::sleep(Duration::from_millis(20));
677 assert!(backend.get("key1").unwrap().is_none());
678 }
679
680 #[test]
681 fn test_mock_backend_no_ttl_never_expires() {
682 let backend = MockMemcachedBackend::new();
683 backend
684 .set("key1", b"value1".to_vec(), Duration::ZERO)
685 .unwrap();
686 assert!(backend.get("key1").unwrap().is_some());
687 }
688
689 #[test]
694 fn test_mock_backend_increment_new_key() {
695 let backend = MockMemcachedBackend::new();
696 let result = backend.increment("counter", 5).unwrap();
697 assert_eq!(result, 5);
698 }
699
700 #[test]
701 fn test_mock_backend_increment_existing_key() {
702 let backend = MockMemcachedBackend::new();
703 backend.increment("counter", 5).unwrap();
704 let result = backend.increment("counter", 3).unwrap();
705 assert_eq!(result, 8);
706 }
707
708 #[test]
709 fn test_mock_backend_decrement() {
710 let backend = MockMemcachedBackend::new();
711 backend.increment("counter", 10).unwrap();
712 let result = backend.decrement("counter", 3).unwrap();
713 assert_eq!(result, 7);
714 }
715
716 #[test]
717 fn test_mock_backend_decrement_not_below_zero() {
718 let backend = MockMemcachedBackend::new();
719 backend.increment("counter", 5).unwrap();
720 let result = backend.decrement("counter", 10).unwrap();
721 assert_eq!(result, 0);
722 }
723
724 #[test]
725 fn test_mock_backend_increment_non_numeric_value() {
726 let backend = MockMemcachedBackend::new();
727 backend
728 .set("text", b"hello".to_vec(), Duration::from_secs(60))
729 .unwrap();
730 let result = backend.increment("text", 1);
731 assert!(result.is_err());
732 }
733
734 #[test]
739 fn test_mock_backend_touch_existing_key() {
740 let backend = MockMemcachedBackend::new();
741 backend
742 .set("key1", b"value1".to_vec(), Duration::from_secs(60))
743 .unwrap();
744 assert!(backend.touch("key1", Duration::from_millis(10)).unwrap());
745 std::thread::sleep(Duration::from_millis(20));
746 assert!(backend.get("key1").unwrap().is_none());
747 }
748
749 #[test]
750 fn test_mock_backend_touch_nonexistent_key() {
751 let backend = MockMemcachedBackend::new();
752 assert!(!backend
753 .touch("nonexistent", Duration::from_secs(60))
754 .unwrap());
755 }
756
757 #[test]
762 fn test_mock_backend_set_oversized_value() {
763 let backend = MockMemcachedBackend::new();
764 let large_value = vec![0u8; MEMCACHED_MAX_VALUE_SIZE + 1];
765 let result = backend.set("key1", large_value, Duration::from_secs(60));
766 assert!(result.is_err());
767 }
768
769 #[test]
774 fn test_driver_set_get_raw() {
775 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
776 driver
777 .set_raw("key1", b"value1".to_vec(), Some(Duration::from_secs(60)))
778 .unwrap();
779 let result = driver.get_raw("key1").unwrap();
780 assert_eq!(result, Some(b"value1".to_vec()));
781 }
782
783 #[test]
784 fn test_driver_get_nonexistent() {
785 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
786 let result = driver.get_raw("nonexistent").unwrap();
787 assert_eq!(result, None);
788 }
789
790 #[test]
791 fn test_driver_delete() {
792 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
793 driver
794 .set_raw("key1", b"value1".to_vec(), Some(Duration::from_secs(60)))
795 .unwrap();
796 driver.delete("key1").unwrap();
797 assert!(driver.get_raw("key1").unwrap().is_none());
798 }
799
800 #[test]
801 fn test_driver_has() {
802 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
803 assert!(!driver.has("key1").unwrap());
804 driver
805 .set_raw("key1", b"value1".to_vec(), Some(Duration::from_secs(60)))
806 .unwrap();
807 assert!(driver.has("key1").unwrap());
808 }
809
810 #[test]
811 fn test_driver_clear() {
812 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
813 driver
814 .set_raw("key1", b"value1".to_vec(), Some(Duration::from_secs(60)))
815 .unwrap();
816 driver
817 .set_raw("key2", b"value2".to_vec(), Some(Duration::from_secs(60)))
818 .unwrap();
819 driver.clear().unwrap();
820 assert!(driver.get_raw("key1").unwrap().is_none());
821 assert!(driver.get_raw("key2").unwrap().is_none());
822 }
823
824 #[test]
829 fn test_driver_inc_new_key() {
830 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
831 let result = driver.inc("counter", 5).unwrap();
832 assert_eq!(result, 5);
833 }
834
835 #[test]
836 fn test_driver_inc_existing_key() {
837 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
838 driver.inc("counter", 5).unwrap();
839 let result = driver.inc("counter", 3).unwrap();
840 assert_eq!(result, 8);
841 }
842
843 #[test]
844 fn test_driver_dec() {
845 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
846 driver.inc("counter", 10).unwrap();
847 let result = driver.dec("counter", 3).unwrap();
848 assert_eq!(result, 7);
849 }
850
851 #[test]
852 fn test_driver_dec_not_below_zero() {
853 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
854 driver.inc("counter", 5).unwrap();
855 let result = driver.dec("counter", 10).unwrap();
856 assert_eq!(result, 0);
857 }
858
859 #[test]
864 fn test_driver_get_cache_key_no_prefix() {
865 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
866 assert_eq!(driver.get_cache_key("mykey"), "mykey");
867 }
868
869 #[test]
870 fn test_driver_get_cache_key_with_prefix() {
871 let config = MemcachedConfig::with_prefix("myapp:");
872 let driver = MemcachedCacheDriver::new(config);
873 assert_eq!(driver.get_cache_key("mykey"), "myapp:mykey");
874 }
875
876 #[test]
877 fn test_driver_set_get_with_prefix() {
878 let config = MemcachedConfig::with_prefix("myapp:");
879 let driver = MemcachedCacheDriver::new(config);
880 driver
881 .set_raw("key1", b"value1".to_vec(), Some(Duration::from_secs(60)))
882 .unwrap();
883 let result = driver.get_raw("key1").unwrap();
884 assert_eq!(result, Some(b"value1".to_vec()));
885 }
886
887 #[test]
892 fn test_driver_validate_key_empty() {
893 assert!(MemcachedCacheDriver::validate_key("").is_err());
894 }
895
896 #[test]
897 fn test_driver_validate_key_with_space() {
898 assert!(MemcachedCacheDriver::validate_key("key with space").is_err());
899 }
900
901 #[test]
902 fn test_driver_validate_key_too_long() {
903 let long_key = "a".repeat(MEMCACHED_MAX_KEY_LEN + 1);
904 assert!(MemcachedCacheDriver::validate_key(&long_key).is_err());
905 }
906
907 #[test]
908 fn test_driver_validate_key_valid() {
909 assert!(MemcachedCacheDriver::validate_key("valid_key_123").is_ok());
910 assert!(MemcachedCacheDriver::validate_key("user:123:session").is_ok());
911 }
912
913 #[test]
918 fn test_driver_normalize_ttl_within_limit() {
919 let ttl = Duration::from_secs(3600);
920 assert_eq!(MemcachedCacheDriver::normalize_ttl(ttl), ttl);
921 }
922
923 #[test]
924 fn test_driver_normalize_ttl_exceeds_limit() {
925 let ttl = Duration::from_secs(MEMCACHED_MAX_TTL_SECS + 100);
926 let normalized = MemcachedCacheDriver::normalize_ttl(ttl);
927 assert_eq!(normalized, Duration::from_secs(MEMCACHED_MAX_TTL_SECS));
928 }
929
930 #[test]
931 fn test_driver_set_with_default_expire() {
932 let config = MemcachedConfig::with_expire(Duration::from_secs(60));
933 let driver = MemcachedCacheDriver::new(config);
934 driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
935 assert!(driver.get_raw("key1").unwrap().is_some());
936 }
937
938 #[test]
943 fn test_driver_tag_append_and_items() {
944 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
945
946 driver.append("mytag", "key1").unwrap();
947 driver.append("mytag", "key2").unwrap();
948 driver.append("mytag", "key3").unwrap();
949
950 let items = driver.get_tag_items("mytag").unwrap();
951 assert_eq!(items.len(), 3);
952 assert!(items.contains(&"key1".to_string()));
953 assert!(items.contains(&"key2".to_string()));
954 assert!(items.contains(&"key3".to_string()));
955 }
956
957 #[test]
958 fn test_driver_tag_append_deduplication() {
959 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
960
961 driver.append("mytag", "key1").unwrap();
962 driver.append("mytag", "key1").unwrap();
963 driver.append("mytag", "key1").unwrap();
964
965 let items = driver.get_tag_items("mytag").unwrap();
966 assert_eq!(items.len(), 1);
967 }
968
969 #[test]
970 fn test_driver_tag_items_empty() {
971 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
972 let items = driver.get_tag_items("nonexistent").unwrap();
973 assert!(items.is_empty());
974 }
975
976 #[test]
977 fn test_driver_tag_clear() {
978 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
979
980 driver
981 .set_raw("key1", b"value1".to_vec(), Some(Duration::from_secs(60)))
982 .unwrap();
983 driver
984 .set_raw("key2", b"value2".to_vec(), Some(Duration::from_secs(60)))
985 .unwrap();
986
987 driver.append("mytag", "key1").unwrap();
988 driver.append("mytag", "key2").unwrap();
989
990 let items = driver.get_tag_items("mytag").unwrap();
991 let item_refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
992 driver.clear_tag(&item_refs).unwrap();
993
994 assert!(driver.get_raw("key1").unwrap().is_none());
995 assert!(driver.get_raw("key2").unwrap().is_none());
996 }
997
998 #[test]
999 fn test_driver_tag_clear_empty() {
1000 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
1001 driver.clear_tag(&[]).unwrap();
1002 }
1003
1004 #[test]
1009 fn test_driver_get_tag_key_uses_md5() {
1010 let driver = MemcachedCacheDriver::new(MemcachedConfig::default());
1011 let tag_key = driver.get_tag_key("mytag");
1012 assert!(tag_key.starts_with("tag:"));
1013 let md5_part = &tag_key["tag:".len()..];
1014 assert_eq!(md5_part.len(), 32);
1015 }
1016
1017 #[test]
1018 fn test_driver_get_tag_key_with_custom_prefix() {
1019 let config = MemcachedConfig {
1020 tag_prefix: "tagset:".to_string(),
1021 ..MemcachedConfig::default()
1022 };
1023 let driver = MemcachedCacheDriver::new(config);
1024 let tag_key = driver.get_tag_key("mytag");
1025 assert!(tag_key.starts_with("tagset:"));
1026 }
1027}