1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Type-mapping example: composite key to simple key.
//!
//! L1 uses a rich composite key (`CacheKey`) for lookups.
//! L2 uses a simple `String` key derived from the composite.
//! The transform boundary converts between them.
use std::time::Duration;
use cachet::{Cache, CacheEntry, MockCache, TransformCodec, TransformEncoder, infallible, infallible_owned};
use tick::Clock;
/// A composite cache key with tenant and resource ID.
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct CacheKey {
tenant: String,
resource_id: u64,
}
impl CacheKey {
fn new(tenant: impl Into<String>, resource_id: u64) -> Self {
Self {
tenant: tenant.into(),
resource_id,
}
}
/// Flattens the composite key into a simple string for L2 storage.
fn to_flat_key(&self) -> String {
format!("{}:{}", self.tenant, self.resource_id)
}
}
#[tokio::main]
async fn main() {
let clock = Clock::new_tokio();
// L2: uses simple String keys and String values.
let l2 = Cache::builder::<String, String>(clock.clone())
.storage(MockCache::new())
.ttl(Duration::from_mins(5));
// Build the cache with a type-mapping boundary:
// L1 uses CacheKey → String
// L2 uses String → String (after transform)
let cache = Cache::builder::<CacheKey, String>(clock)
.memory()
.ttl(Duration::from_mins(1))
.transform(
// Key: CacheKey → String (flatten composite key)
TransformEncoder::infallible(|k: &CacheKey| k.to_flat_key()),
// Value: String ↔ String (identity — no value mapping needed)
TransformCodec::new(infallible(|v: &String| v.clone()), infallible_owned(|v: String| v)),
)
.fallback(l2)
.build();
let key = CacheKey::new("acme", 42);
// Insert with the composite key
cache
.insert(key.clone(), CacheEntry::new("widget-data".to_string()))
.await
.expect("insert failed");
// Retrieve — L1 uses CacheKey, L2 uses "acme:42"
let result = cache.get(&key).await.expect("get failed");
match result {
Some(entry) => println!("got: {}", entry.value()),
None => println!("not found"),
}
println!("done");
}