Skip to main content

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}