cachekit/intents.rs
1//! Intent-based cache presets.
2//!
3//! Pre-configured factory methods that build a [`CacheKit`] client from a
4//! single declarative call. Each intent sets sensible defaults for a specific
5//! use case and returns a [`CacheKitBuilder`] so callers can override any
6//! setting before building.
7//!
8//! | Intent | Backend | L1 | Encryption | Auto-reconnect | Reliability¹ | Default TTL |
9//! |------------|-----------|------|------------|----------------|--------------|-------------|
10//! | `minimal` | Redis | Off | No | No | Off | 300 s |
11//! | `production` | Redis | On | No | Yes | On | 600 s |
12//! | `encrypted` | Redis | On | AES-256-GCM | Yes | On | 600 s |
13//! | `io` | cachekit.io | On | No | n/a (HTTP) | On | 3 600 s |
14//!
15//! ¹ Retry with backoff + jitter, a circuit breaker, and backpressure
16//! (bounded backend concurrency) around backend ops (requires the
17//! `reliability` feature, on by default — see [`crate::reliability`]).
18//! Override via [`CacheKitBuilder::reliability`];
19//! [`ReliabilityConfig::disabled()`](crate::reliability::ReliabilityConfig::disabled)
20//! turns the stack off entirely.
21
22use std::time::Duration;
23
24use crate::client::{CacheKit, CacheKitBuilder, SharedBackend};
25use crate::error::CachekitError;
26
27// ── SharedBackend wrapping ───────────────────────────────────────────────────
28
29#[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
30fn wrap(b: impl crate::backend::Backend + 'static) -> SharedBackend {
31 std::sync::Arc::new(b)
32}
33
34#[cfg(any(target_arch = "wasm32", feature = "unsync"))]
35fn wrap(b: impl crate::backend::Backend + 'static) -> SharedBackend {
36 std::rc::Rc::new(b)
37}
38
39// ── Intent presets ───────────────────────────────────────────────────────────
40
41impl CacheKit {
42 /// **Minimal** — speed-first Redis cache, no extras.
43 ///
44 /// * Backend: Redis (connects eagerly; **fails fast** — a dropped
45 /// connection is not re-established)
46 /// * L1 cache: **off**
47 /// * Encryption: **no**
48 /// * Reliability: **off** — no retry, no circuit breaker, no
49 /// backpressure; every backend error propagates on first failure and
50 /// backend concurrency is unbounded
51 /// * Default TTL: **300 s**
52 ///
53 /// Good for: product catalogs, public data, development.
54 ///
55 /// # Errors
56 ///
57 /// Returns [`CachekitError`] if the URL is invalid or Redis is unreachable.
58 ///
59 /// # Example
60 ///
61 /// ```no_run
62 /// # async fn example() -> Result<(), cachekit::CachekitError> {
63 /// let cache = cachekit::CacheKit::minimal("redis://localhost:6379").await?
64 /// .namespace("myapp")
65 /// .build()?;
66 /// # Ok(())
67 /// # }
68 /// ```
69 #[cfg(feature = "redis")]
70 pub async fn minimal(redis_url: &str) -> Result<CacheKitBuilder, CachekitError> {
71 let backend = crate::backend::redis::RedisBackend::builder()
72 .url(redis_url)
73 .build()?;
74 drop(backend.connect().await?);
75
76 Ok(CacheKitBuilder::default()
77 .backend(wrap(backend))
78 .default_ttl(Duration::from_secs(300))
79 .no_l1())
80 }
81
82 /// **Production** — reliability-first Redis cache with L1.
83 ///
84 /// * Backend: Redis (connects eagerly; **auto-reconnects** with
85 /// exponential backoff after a dropped connection)
86 /// * L1 cache: **on** (1 000 entries)
87 /// * Encryption: **no**
88 /// * Reliability: **on** — retry with backoff + jitter, circuit
89 /// breaker, backpressure (max 100 concurrent backend ops)
90 /// * Default TTL: **600 s**
91 ///
92 /// Good for: user sessions, API responses, production services.
93 ///
94 /// # Errors
95 ///
96 /// Returns [`CachekitError`] if the URL is invalid or Redis is unreachable.
97 ///
98 /// # Example
99 ///
100 /// ```no_run
101 /// # async fn example() -> Result<(), cachekit::CachekitError> {
102 /// let cache = cachekit::CacheKit::production("redis://localhost:6379").await?
103 /// .namespace("api")
104 /// .build()?;
105 /// # Ok(())
106 /// # }
107 /// ```
108 #[cfg(feature = "redis")]
109 pub async fn production(redis_url: &str) -> Result<CacheKitBuilder, CachekitError> {
110 let backend = crate::backend::redis::RedisBackend::builder()
111 .url(redis_url)
112 .auto_reconnect()
113 .build()?;
114 drop(backend.connect().await?);
115
116 let builder = CacheKitBuilder::default()
117 .backend(wrap(backend))
118 .default_ttl(Duration::from_secs(600))
119 .l1_capacity(1000);
120 #[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
121 let builder = builder.reliability(crate::reliability::ReliabilityConfig::default());
122 Ok(builder)
123 }
124
125 /// **Encrypted** — zero-knowledge encrypted Redis cache.
126 ///
127 /// * Backend: Redis (connects eagerly; **auto-reconnects** with
128 /// exponential backoff after a dropped connection)
129 /// * L1 cache: **on** (1 000 entries, stores ciphertext)
130 /// * Encryption: **AES-256-GCM** with HKDF-SHA256
131 /// * Reliability: **on** — retry with backoff + jitter, circuit
132 /// breaker, backpressure (max 100 concurrent backend ops)
133 /// * Default TTL: **600 s**
134 /// * Tenant ID: `"default"` (override via
135 /// [`.encryption_from_bytes()`](CacheKitBuilder::encryption_from_bytes))
136 ///
137 /// Good for: PII, payments, GDPR/HIPAA-sensitive data.
138 ///
139 /// `master_key` must be at least 32 raw bytes.
140 ///
141 /// # Errors
142 ///
143 /// Returns [`CachekitError`] if the URL is invalid, Redis is unreachable,
144 /// or the master key is too short.
145 ///
146 /// # Example
147 ///
148 /// ```no_run
149 /// # async fn example() -> Result<(), cachekit::CachekitError> {
150 /// let key = b"my_32_byte_production_key_here!!";
151 /// let cache = cachekit::CacheKit::encrypted("redis://localhost:6379", key).await?
152 /// .build()?;
153 /// let encrypted = cache.secure()?;
154 /// # Ok(())
155 /// # }
156 /// ```
157 #[cfg(all(feature = "redis", feature = "encryption"))]
158 pub async fn encrypted(
159 redis_url: &str,
160 master_key: &[u8],
161 ) -> Result<CacheKitBuilder, CachekitError> {
162 // Validate the master key first: a bad key is a deterministic local
163 // error and must not be masked by (or pay for) Redis I/O.
164 let builder = CacheKitBuilder::default()
165 .default_ttl(Duration::from_secs(600))
166 .l1_capacity(1000)
167 .encryption_from_bytes(master_key, "default")?;
168 #[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
169 let builder = builder.reliability(crate::reliability::ReliabilityConfig::default());
170
171 let backend = crate::backend::redis::RedisBackend::builder()
172 .url(redis_url)
173 .auto_reconnect()
174 .build()?;
175 drop(backend.connect().await?);
176
177 Ok(builder.backend(wrap(backend)))
178 }
179
180 /// **CachekitIO** — managed SaaS cache, zero infrastructure.
181 ///
182 /// * Backend: [cachekit.io](https://cachekit.io) HTTP API
183 /// * L1 cache: **on** (1 000 entries)
184 /// * Encryption: **no** (add via
185 /// [`.encryption()`](CacheKitBuilder::encryption))
186 /// * Reliability: **on** — retry with backoff + jitter, circuit
187 /// breaker, backpressure (max 100 concurrent backend ops)
188 /// * Default TTL: **3 600 s**
189 ///
190 /// Good for: serverless, edge compute, managed caching without Redis.
191 ///
192 /// # Errors
193 ///
194 /// Returns [`CachekitError`] if `api_key` is empty.
195 ///
196 /// # Example
197 ///
198 /// ```no_run
199 /// # fn example() -> Result<(), cachekit::CachekitError> {
200 /// let cache = cachekit::CacheKit::io("ck_live_abc123")?
201 /// .namespace("edge")
202 /// .build()?;
203 /// # Ok(())
204 /// # }
205 /// ```
206 #[cfg(all(feature = "cachekitio", not(target_arch = "wasm32")))]
207 pub fn io(api_key: &str) -> Result<CacheKitBuilder, CachekitError> {
208 let backend = crate::backend::cachekitio::CachekitIO::builder()
209 .api_key(api_key)
210 .build()?;
211
212 let builder = CacheKitBuilder::default()
213 .backend(wrap(backend))
214 .default_ttl(Duration::from_secs(3600))
215 .l1_capacity(1000);
216 #[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
217 let builder = builder.reliability(crate::reliability::ReliabilityConfig::default());
218 Ok(builder)
219 }
220}