use std::time::Duration;
use amalgam::{Cache, EntryOptions, FactoryContext};
#[tokio::main]
async fn main() {
adaptive_caching().await;
println!();
conditional_refresh().await;
}
async fn adaptive_caching() {
println!("== adaptive caching ==");
let cache: Cache<String> = Cache::new();
let base = EntryOptions::new(Duration::from_secs(10));
let value = cache
.get_or_set_with(
"search:zzz",
|mut ctx: FactoryContext<String>| async move {
let result = String::new(); if result.is_empty() {
ctx.adapt(|o| o.with_duration(Duration::from_millis(100)));
println!(" [factory] empty result → adapting duration down to 100ms");
}
Ok(ctx.value(result))
},
base.clone(),
)
.await
.expect("factory runs");
println!("produced (empty) value, cached for only 100ms: {value:?}");
let cached = cache
.get_or_set_with(
"search:zzz",
|ctx| async move {
println!(" [factory] (should NOT run yet)");
Ok(ctx.value("late".to_owned()))
},
base.clone(),
)
.await
.expect("served from cache");
println!("immediately after => served from cache: {cached:?}");
tokio::time::sleep(Duration::from_millis(150)).await;
let refreshed = cache
.get_or_set_with(
"search:zzz",
|ctx| async move {
println!(" [factory] adapted window elapsed → re-running");
Ok(ctx.value("RESULTS NOW".to_owned()))
},
base,
)
.await
.expect("factory re-runs after the adapted window");
println!("after 150ms => factory re-ran: {refreshed:?}");
assert_eq!(refreshed, "RESULTS NOW");
}
async fn conditional_refresh() {
println!("== conditional refresh (ETag) ==");
let cache: Cache<String> = Cache::new();
let opts = EntryOptions::new(Duration::from_millis(150)).with_fail_safe(
true,
Some(Duration::from_secs(60 * 60)),
Some(Duration::from_secs(1)),
);
let primed = cache
.get_or_set_with(
"doc",
|ctx| async move {
Ok(ctx
.modified("document v1".to_owned())
.etag("etag-v1")
.done())
},
opts.clone(),
)
.await
.expect("priming succeeds");
println!("primed with etag-v1 => {primed:?}");
tokio::time::sleep(Duration::from_millis(200)).await;
let reused = cache
.get_or_set_with(
"doc",
|ctx: FactoryContext<String>| async move {
println!(
" [factory] revalidating with stale etag = {:?}",
ctx.stale_etag()
);
println!(" [factory] server replied 304 Not Modified");
ctx.not_modified() },
opts.clone(),
)
.await
.expect("not_modified reuses the stale value");
println!("after 304 NotModified => {reused:?} (stale value reused, expiration bumped)");
assert_eq!(reused, "document v1");
tokio::time::sleep(Duration::from_millis(200)).await;
let updated = cache
.get_or_set_with(
"doc",
|ctx: FactoryContext<String>| async move {
println!(
" [factory] revalidating with stale etag = {:?}",
ctx.stale_etag()
);
println!(" [factory] server replied 200 with a new body");
Ok(ctx
.modified("document v2".to_owned())
.etag("etag-v2")
.done())
},
opts,
)
.await
.expect("modified replaces the value");
println!("after 200 Modified => {updated:?} (new value + new etag cached)");
assert_eq!(updated, "document v2");
println!("OK: adaptive duration + conditional (304/200) refresh both demonstrated.");
}