cache_kit/
key.rs

1//! Cache key management utilities.
2
3use crate::entity::CacheEntity;
4
5/// Type alias for key generator function.
6type KeyGeneratorFn = dyn Fn(&dyn std::fmt::Display) -> String + Send + Sync;
7
8/// Builder for cache keys.
9pub struct CacheKeyBuilder;
10
11impl CacheKeyBuilder {
12    /// Build full cache key from entity type and ID.
13    pub fn build<T: CacheEntity>(id: &T::Key) -> String {
14        format!("{}:{}", T::cache_prefix(), id)
15    }
16
17    /// Build cache key with custom prefix.
18    pub fn build_with_prefix(prefix: &str, id: &dyn std::fmt::Display) -> String {
19        format!("{}:{}", prefix, id)
20    }
21
22    /// Build composite key from multiple parts.
23    pub fn build_composite(parts: &[&str]) -> String {
24        parts.join(":")
25    }
26
27    /// Parse a composite key into parts.
28    pub fn parse(key: &str) -> Vec<&str> {
29        key.split(':').collect()
30    }
31}
32
33/// Registry for custom cache key generators.
34pub struct KeyRegistry {
35    generators: std::collections::HashMap<String, Box<KeyGeneratorFn>>,
36}
37
38impl KeyRegistry {
39    pub fn new() -> Self {
40        KeyRegistry {
41            generators: std::collections::HashMap::new(),
42        }
43    }
44
45    /// Register a custom key generator for a type.
46    pub fn register<F>(&mut self, type_name: String, generator: F)
47    where
48        F: Fn(&dyn std::fmt::Display) -> String + Send + Sync + 'static,
49    {
50        self.generators.insert(type_name, Box::new(generator));
51    }
52
53    /// Get a key generator for a type.
54    pub fn get(&self, type_name: &str) -> Option<&KeyGeneratorFn> {
55        self.generators.get(type_name).map(|b| b.as_ref())
56    }
57
58    /// Generate key using registered generator.
59    pub fn generate(&self, type_name: &str, id: &dyn std::fmt::Display) -> Option<String> {
60        self.get(type_name).map(|gen| gen(id))
61    }
62}
63
64impl Default for KeyRegistry {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use serde::{Deserialize, Serialize};
74
75    #[derive(Clone, Serialize, Deserialize)]
76    struct TestEntity {
77        id: String,
78    }
79
80    impl CacheEntity for TestEntity {
81        type Key = String;
82
83        fn cache_key(&self) -> Self::Key {
84            self.id.clone()
85        }
86
87        fn cache_prefix() -> &'static str {
88            "test"
89        }
90    }
91
92    #[test]
93    fn test_cache_key_builder() {
94        let key = CacheKeyBuilder::build::<TestEntity>(&"entity_123".to_string());
95        assert_eq!(key, "test:entity_123");
96    }
97
98    #[test]
99    fn test_cache_key_builder_custom_prefix() {
100        let key = CacheKeyBuilder::build_with_prefix("custom", &"123");
101        assert_eq!(key, "custom:123");
102    }
103
104    #[test]
105    fn test_composite_key_builder() {
106        let key = CacheKeyBuilder::build_composite(&["user", "123", "profile"]);
107        assert_eq!(key, "user:123:profile");
108    }
109
110    #[test]
111    fn test_composite_key_parser() {
112        let key = "user:123:profile";
113        let parts = CacheKeyBuilder::parse(key);
114        assert_eq!(parts, vec!["user", "123", "profile"]);
115    }
116
117    #[test]
118    fn test_key_registry() {
119        let mut registry = KeyRegistry::new();
120
121        registry.register("custom".to_string(), |id| format!("CUSTOM_{}", id));
122
123        let generated = registry.generate("custom", &"123").unwrap();
124        assert_eq!(generated, "CUSTOM_123");
125
126        assert!(registry.generate("unknown", &"123").is_none());
127    }
128}