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