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}