Skip to main content

cachekit/
client.rs

1use std::time::Duration;
2
3use serde::{de::DeserializeOwned, Serialize};
4
5use crate::backend::Backend;
6use crate::error::CachekitError;
7use crate::serializer;
8
9// ── SharedBackend type alias ──────────────────────────────────────────────────
10
11/// Reference-counted pointer to a heap-allocated backend.
12///
13/// On native targets (without `unsync`) we require `Send + Sync` via `Arc`.
14/// On `wasm32` or with the `unsync` feature, `Rc` is used instead — the runtime
15/// is single-threaded so `Send` bounds are unnecessary.
16#[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
17pub type SharedBackend = std::sync::Arc<dyn Backend>;
18
19/// Reference-counted pointer to a heap-allocated backend (`?Send` variant).
20#[cfg(any(target_arch = "wasm32", feature = "unsync"))]
21pub type SharedBackend = std::rc::Rc<dyn Backend>;
22
23// ── SharedEncryption type alias ──────────────────────────────────────────────
24
25/// Reference-counted pointer to the encryption layer.
26///
27/// On native targets (without `unsync`) `Arc` is used (requires `Sync`).
28/// On `wasm32` or with `unsync`, `Rc` is used — avoids the `!Sync` problem
29/// caused by `Cell<u64>` inside cachekit-core's nonce counter.
30#[cfg(all(
31    feature = "encryption",
32    not(any(target_arch = "wasm32", feature = "unsync"))
33))]
34type SharedEncryption = std::sync::Arc<crate::encryption::EncryptionLayer>;
35
36#[cfg(all(
37    feature = "encryption",
38    any(target_arch = "wasm32", feature = "unsync")
39))]
40type SharedEncryption = std::rc::Rc<crate::encryption::EncryptionLayer>;
41
42// ── Key validation ────────────────────────────────────────────────────────────
43
44const MAX_KEY_BYTES: usize = 1024;
45
46/// Maximum TTL for L1 entries populated from L2 cache hits.
47/// Uses a short ceiling to limit staleness when the original TTL is unknown.
48const L1_BACKFILL_TTL_SECS: u64 = 30;
49
50fn validate_key(key: &str) -> Result<(), CachekitError> {
51    if key.is_empty() {
52        return Err(CachekitError::InvalidKey(
53            "key must not be empty".to_owned(),
54        ));
55    }
56    if key.len() > MAX_KEY_BYTES {
57        return Err(CachekitError::InvalidKey(format!(
58            "key is {} bytes (limit: {MAX_KEY_BYTES})",
59            key.len()
60        )));
61    }
62    for b in key.bytes() {
63        if b < 0x20 || b == 0x7F {
64            return Err(CachekitError::InvalidKey(format!(
65                "key contains illegal control character 0x{b:02X}"
66            )));
67        }
68    }
69    Ok(())
70}
71
72// ── CacheKit ─────────────────────────────────────────────────────────────────
73
74/// Production-ready cache client with optional L1 in-process cache layer.
75pub struct CacheKit {
76    backend: SharedBackend,
77    default_ttl: Duration,
78    namespace: Option<String>,
79    max_payload_bytes: usize,
80
81    #[cfg(feature = "l1")]
82    l1: Option<crate::l1::L1Cache>,
83
84    #[cfg(feature = "encryption")]
85    encryption: Option<SharedEncryption>,
86}
87
88impl CacheKit {
89    /// Create a new builder.
90    pub fn builder() -> CacheKitBuilder {
91        CacheKitBuilder::default()
92    }
93
94    /// Build from environment variables via [`crate::config::CachekitConfig::from_env`].
95    ///
96    /// Creates a [`crate::backend::cachekitio::CachekitIO`] backend from the
97    /// config. Requires the `cachekitio` feature.
98    #[cfg(all(feature = "cachekitio", not(target_arch = "wasm32")))]
99    pub fn from_env() -> Result<CacheKitBuilder, CachekitError> {
100        use crate::backend::cachekitio::CachekitIO;
101        use crate::config::CachekitConfig;
102
103        let config = CachekitConfig::from_env()?;
104
105        let api_key_z = config
106            .api_key
107            .ok_or_else(|| CachekitError::Config("CACHEKIT_API_KEY is required".to_owned()))?;
108
109        let backend = CachekitIO::builder()
110            .api_key(api_key_z.as_str())
111            .api_url(config.api_url)
112            .build()
113            .map_err(|e| CachekitError::Config(e.to_string()))?;
114
115        #[cfg(not(feature = "unsync"))]
116        let shared: SharedBackend = std::sync::Arc::new(backend);
117        #[cfg(feature = "unsync")]
118        let shared: SharedBackend = std::rc::Rc::new(backend);
119
120        let mut builder = CacheKitBuilder::default()
121            .backend(shared)
122            .default_ttl(config.default_ttl)
123            .max_payload_bytes(config.max_payload_bytes)
124            .l1_capacity(config.l1_capacity);
125
126        if let Some(ns) = config.namespace.clone() {
127            builder = builder.namespace(ns);
128        }
129
130        // Wire up encryption if master key is configured
131        #[cfg(feature = "encryption")]
132        if let Some(ref master_key) = config.master_key {
133            let namespace = config.namespace.as_deref().unwrap_or("default");
134            builder = builder.encryption_from_bytes(master_key, namespace)?;
135        }
136
137        Ok(builder)
138    }
139
140    // ── Namespacing ───────────────────────────────────────────────────────────
141
142    fn namespaced_key(&self, key: &str) -> String {
143        match &self.namespace {
144            Some(ns) => format!("{ns}:{key}"),
145            None => key.to_owned(),
146        }
147    }
148
149    /// Validate key and return the namespaced version.
150    fn resolve_key(&self, key: &str) -> Result<String, CachekitError> {
151        validate_key(key)?;
152        Ok(self.namespaced_key(key))
153    }
154
155    // ── L1 helpers ───────────────────────────────────────────────────────────
156
157    /// Try L1 cache first. Returns Some(bytes) on hit.
158    #[cfg(feature = "l1")]
159    fn l1_get(&self, full_key: &str) -> Option<Vec<u8>> {
160        self.l1.as_ref().and_then(|l1| l1.get(full_key))
161    }
162
163    /// Populate L1 from an L2 hit with capped TTL to limit staleness.
164    #[cfg(feature = "l1")]
165    fn l1_backfill(&self, full_key: &str, bytes: &[u8]) {
166        if let Some(ref l1) = self.l1 {
167            let l1_ttl = std::cmp::min(self.default_ttl, Duration::from_secs(L1_BACKFILL_TTL_SECS));
168            l1.set(full_key, bytes, l1_ttl);
169        }
170    }
171
172    /// Write-through to L1.
173    #[cfg(feature = "l1")]
174    fn l1_set(&self, full_key: &str, bytes: &[u8], ttl: Duration) {
175        if let Some(ref l1) = self.l1 {
176            l1.set(full_key, bytes, ttl);
177        }
178    }
179
180    /// Invalidate L1 entry.
181    #[cfg(feature = "l1")]
182    fn l1_delete(&self, full_key: &str) {
183        if let Some(ref l1) = self.l1 {
184            l1.delete(full_key);
185        }
186    }
187
188    /// Validate TTL is at least 1 second.
189    fn validate_ttl(ttl: Duration) -> Result<(), CachekitError> {
190        if ttl < Duration::from_secs(1) {
191            return Err(CachekitError::Config(format!(
192                "TTL must be at least 1 second; got {ttl:?}"
193            )));
194        }
195        Ok(())
196    }
197
198    // ── Public operations ─────────────────────────────────────────────────────
199
200    /// Retrieve and deserialize a value stored under `key`.
201    ///
202    /// Returns `None` if the key does not exist.
203    /// Checks L1 cache before hitting the backend.
204    pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CachekitError> {
205        match self.get_bytes(key).await? {
206            Some(bytes) => Ok(Some(serializer::deserialize(&bytes)?)),
207            None => Ok(None),
208        }
209    }
210
211    /// Retrieve and deserialize an interop-mode value stored under `key`.
212    ///
213    /// Identical to [`Self::get`] except the payload is decoded with
214    /// [`crate::interop::deserialize`], which consumes exactly one MessagePack
215    /// document and rejects trailing bytes (interop/v1 spec MUST). A
216    /// Python-SDK-internal CK frame is rejected with a specific diagnostic
217    /// instead of silently decoding as the integer 67.
218    ///
219    /// Use with keys from [`crate::interop::interop_key`] on a client
220    /// **without** a namespace prefix. There is no interop-specific write
221    /// method: [`Self::set`] already writes plain MessagePack (no ByteStorage
222    /// envelope), which is the interop value format.
223    ///
224    /// # Errors
225    ///
226    /// Returns [`CachekitError::Config`] if the client was built with
227    /// [`CacheKitBuilder::namespace`] (or `CACHEKIT_NAMESPACE`): the prefix
228    /// would rewrite the storage key to `{prefix}:{interop_key}`, which no
229    /// other SDK computes — every cross-SDK entry would silently miss. Interop
230    /// keys carry their own namespace segment; failing loudly here beats a
231    /// 100% miss rate that looks like a cold cache.
232    pub async fn interop_get<T: DeserializeOwned>(
233        &self,
234        key: &str,
235    ) -> Result<Option<T>, CachekitError> {
236        self.reject_namespaced_interop()?;
237        match self.get_bytes(key).await? {
238            Some(bytes) => Ok(Some(crate::interop::deserialize(&bytes)?)),
239            None => Ok(None),
240        }
241    }
242
243    /// Interop keys must reach the backend verbatim; a client namespace prefix
244    /// would silently produce storage keys no other SDK computes.
245    fn reject_namespaced_interop(&self) -> Result<(), CachekitError> {
246        match self.namespace {
247            None => Ok(()),
248            Some(_) => Err(CachekitError::Config(
249                "interop reads require a client without a namespace prefix: .namespace() / \
250                 CACHEKIT_NAMESPACE would store interop entries under {prefix}:{interop_key}, \
251                 which other SDKs never compute (interop keys already carry a namespace \
252                 segment) — use a dedicated non-namespaced client for interop entries"
253                    .to_owned(),
254            )),
255        }
256    }
257
258    /// Fetch raw payload bytes for `key` (L1, then L2 with L1 backfill).
259    async fn get_bytes(&self, key: &str) -> Result<Option<Vec<u8>>, CachekitError> {
260        let full_key = self.resolve_key(key)?;
261
262        // L1 hit
263        #[cfg(feature = "l1")]
264        if let Some(bytes) = self.l1_get(&full_key) {
265            self.check_payload_size(bytes.len())?;
266            return Ok(Some(bytes));
267        }
268
269        // L2 backend
270        let bytes = match self.backend.get(&full_key).await? {
271            Some(b) => b,
272            None => return Ok(None),
273        };
274
275        self.check_payload_size(bytes.len())?;
276
277        // Populate L1 on L2 hit (capped TTL to limit staleness)
278        #[cfg(feature = "l1")]
279        self.l1_backfill(&full_key, &bytes);
280
281        Ok(Some(bytes))
282    }
283
284    /// Serialize and store `value` under `key` using the client's default TTL.
285    pub async fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<(), CachekitError> {
286        self.set_with_ttl(key, value, self.default_ttl).await
287    }
288
289    /// Serialize and store `value` under `key` with an explicit `ttl`.
290    ///
291    /// Returns [`CachekitError::Config`] if `ttl` is less than 1 second.
292    pub async fn set_with_ttl<T: Serialize>(
293        &self,
294        key: &str,
295        value: &T,
296        ttl: Duration,
297    ) -> Result<(), CachekitError> {
298        Self::validate_ttl(ttl)?;
299
300        let bytes = serializer::serialize(value)?;
301        self.check_payload_size(bytes.len())?;
302
303        let full_key = self.resolve_key(key)?;
304
305        // Only clone bytes when L1 needs a copy after the backend consumes them.
306        #[cfg(feature = "l1")]
307        {
308            let l1_bytes = bytes.clone();
309            self.backend.set(&full_key, bytes, Some(ttl)).await?;
310            self.l1_set(&full_key, &l1_bytes, ttl);
311        }
312        #[cfg(not(feature = "l1"))]
313        {
314            self.backend.set(&full_key, bytes, Some(ttl)).await?;
315        }
316
317        Ok(())
318    }
319
320    /// Delete `key` and return `true` if it existed.
321    ///
322    /// Invalidates the L1 entry regardless of the backend result.
323    pub async fn delete(&self, key: &str) -> Result<bool, CachekitError> {
324        let full_key = self.resolve_key(key)?;
325
326        // Invalidate L1 first so callers never read a stale value even if the
327        // backend delete fails partway through.
328        #[cfg(feature = "l1")]
329        self.l1_delete(&full_key);
330
331        Ok(self.backend.delete(&full_key).await?)
332    }
333
334    /// Return `true` if `key` exists without fetching the value.
335    pub async fn exists(&self, key: &str) -> Result<bool, CachekitError> {
336        let full_key = self.resolve_key(key)?;
337
338        // Check L1 first — avoids a network round-trip for warm entries.
339        #[cfg(feature = "l1")]
340        if self.l1_get(&full_key).is_some() {
341            return Ok(true);
342        }
343
344        Ok(self.backend.exists(&full_key).await?)
345    }
346
347    // ── Secure cache ─────────────────────────────────────────────────────────
348
349    /// Return a [`SecureCache`] handle that encrypts all values before storage.
350    ///
351    /// L1 stores **ciphertext** (not plaintext) to preserve the zero-knowledge
352    /// property across all cache layers.
353    ///
354    /// # Errors
355    /// Returns `CachekitError::Config` if no encryption layer is configured.
356    /// Configure encryption via [`CacheKitBuilder::encryption`] or
357    /// [`CacheKitBuilder::encryption_from_bytes`].
358    #[cfg(feature = "encryption")]
359    pub fn secure(&self) -> Result<SecureCache<'_>, CachekitError> {
360        let enc = self.encryption.as_ref().ok_or_else(|| {
361            CachekitError::Config(
362                "encryption requires CACHEKIT_MASTER_KEY or .encryption() on builder".to_owned(),
363            )
364        })?;
365        Ok(SecureCache {
366            client: self,
367            encryption: enc,
368        })
369    }
370
371    // ── Private helpers ───────────────────────────────────────────────────────
372
373    fn check_payload_size(&self, size: usize) -> Result<(), CachekitError> {
374        if size > self.max_payload_bytes {
375            return Err(CachekitError::PayloadTooLarge {
376                size,
377                limit: self.max_payload_bytes,
378            });
379        }
380        Ok(())
381    }
382}
383
384// ── SecureCache ──────────────────────────────────────────────────────────────
385
386/// Encrypted cache handle returned by [`CacheKit::secure()`].
387///
388/// All values are serialized, then encrypted with AES-256-GCM before storage.
389/// L1 stores ciphertext to maintain zero-knowledge guarantees.
390#[cfg(feature = "encryption")]
391pub struct SecureCache<'a> {
392    client: &'a CacheKit,
393    encryption: &'a crate::encryption::EncryptionLayer,
394}
395
396#[cfg(feature = "encryption")]
397impl std::fmt::Debug for SecureCache<'_> {
398    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399        f.debug_struct("SecureCache")
400            .field("tenant_id", &self.encryption.tenant_id())
401            .finish()
402    }
403}
404
405#[cfg(feature = "encryption")]
406impl SecureCache<'_> {
407    /// Encrypt and store `value` under `key` using the client's default TTL.
408    pub async fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<(), CachekitError> {
409        self.set_with_ttl(key, value, self.client.default_ttl).await
410    }
411
412    /// Encrypt and store `value` under `key` with an explicit `ttl`.
413    pub async fn set_with_ttl<T: Serialize>(
414        &self,
415        key: &str,
416        value: &T,
417        ttl: Duration,
418    ) -> Result<(), CachekitError> {
419        CacheKit::validate_ttl(ttl)?;
420
421        // Serialize then encrypt
422        let plaintext = serializer::serialize(value)?;
423        let ciphertext = self.encryption.encrypt(&plaintext, key)?;
424        // Size-check what is actually persisted (nonce + ciphertext + tag).
425        // The get paths check the stored ciphertext length, so checking the
426        // plaintext here would let a value within 28 bytes of the limit write
427        // successfully and then fail EVERY subsequent read with PayloadTooLarge.
428        self.client.check_payload_size(ciphertext.len())?;
429
430        let full_key = self.client.resolve_key(key)?;
431
432        // Only clone when L1 needs a copy after the backend consumes the data.
433        #[cfg(feature = "l1")]
434        {
435            let l1_bytes = ciphertext.clone();
436            self.client
437                .backend
438                .set(&full_key, ciphertext, Some(ttl))
439                .await?;
440            self.client.l1_set(&full_key, &l1_bytes, ttl);
441        }
442        #[cfg(not(feature = "l1"))]
443        {
444            self.client
445                .backend
446                .set(&full_key, ciphertext, Some(ttl))
447                .await?;
448        }
449
450        Ok(())
451    }
452
453    /// Retrieve, decrypt, and deserialize a value stored under `key`.
454    ///
455    /// Checks L1 (which holds ciphertext) before the backend.
456    pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CachekitError> {
457        match self.get_plaintext(key).await? {
458            Some(plaintext) => Ok(Some(serializer::deserialize(&plaintext)?)),
459            None => Ok(None),
460        }
461    }
462
463    /// Retrieve, decrypt, and deserialize an interop-mode value stored under `key`.
464    ///
465    /// Identical to [`Self::get`] except the decrypted plaintext is decoded
466    /// with [`crate::interop::deserialize`] — exactly one MessagePack document,
467    /// trailing bytes rejected (interop/v1 spec MUST). In interop mode the
468    /// AES-GCM plaintext is the plain MessagePack value bytes, so the AAD
469    /// (v0x03, `format="msgpack"`, `compressed="False"`) verifies cross-SDK
470    /// unchanged.
471    ///
472    /// # Errors
473    ///
474    /// Returns [`CachekitError::Config`] on a namespace-prefixed client — see
475    /// [`CacheKit::interop_get`].
476    pub async fn interop_get<T: DeserializeOwned>(
477        &self,
478        key: &str,
479    ) -> Result<Option<T>, CachekitError> {
480        self.client.reject_namespaced_interop()?;
481        match self.get_plaintext(key).await? {
482            Some(plaintext) => Ok(Some(crate::interop::deserialize(&plaintext)?)),
483            None => Ok(None),
484        }
485    }
486
487    /// Fetch ciphertext (L1, then L2 with L1 backfill) and decrypt it.
488    ///
489    /// Ciphertext retrieval delegates to [`CacheKit::get_bytes`], which returns
490    /// the stored bytes untransformed — for a secure cache exactly the AES-GCM
491    /// ciphertext, so decrypt receives the same bytes the backend holds.
492    async fn get_plaintext(&self, key: &str) -> Result<Option<Vec<u8>>, CachekitError> {
493        match self.client.get_bytes(key).await? {
494            Some(ciphertext) => Ok(Some(self.encryption.decrypt(&ciphertext, key)?)),
495            None => Ok(None),
496        }
497    }
498
499    /// Delete an encrypted key. Behaves identically to [`CacheKit::delete`].
500    pub async fn delete(&self, key: &str) -> Result<bool, CachekitError> {
501        self.client.delete(key).await
502    }
503
504    /// Check if an encrypted key exists. Behaves identically to [`CacheKit::exists`].
505    pub async fn exists(&self, key: &str) -> Result<bool, CachekitError> {
506        self.client.exists(key).await
507    }
508}
509
510// ── CacheKitBuilder ───────────────────────────────────────────────────────────
511
512/// Fluent builder for [`CacheKit`].
513#[derive(Default)]
514#[must_use]
515pub struct CacheKitBuilder {
516    backend: Option<SharedBackend>,
517    default_ttl: Option<Duration>,
518    namespace: Option<String>,
519    max_payload_bytes: Option<usize>,
520
521    #[cfg(feature = "l1")]
522    l1_capacity: Option<usize>,
523
524    #[cfg(feature = "l1")]
525    no_l1: bool,
526
527    #[cfg(feature = "encryption")]
528    encryption: Option<SharedEncryption>,
529}
530
531impl CacheKitBuilder {
532    /// Set the storage backend.
533    pub fn backend(mut self, backend: SharedBackend) -> Self {
534        self.backend = Some(backend);
535        self
536    }
537
538    /// Override the default TTL (used when no per-call TTL is specified).
539    pub fn default_ttl(mut self, ttl: Duration) -> Self {
540        self.default_ttl = Some(ttl);
541        self
542    }
543
544    /// Set a namespace prefix. All keys will be stored as `{namespace}:{key}`.
545    pub fn namespace(mut self, ns: impl Into<String>) -> Self {
546        self.namespace = Some(ns.into());
547        self
548    }
549
550    /// Set the maximum accepted payload size in bytes.
551    pub fn max_payload_bytes(mut self, limit: usize) -> Self {
552        self.max_payload_bytes = Some(limit);
553        self
554    }
555
556    /// Set the L1 cache capacity (max entries).
557    #[cfg(feature = "l1")]
558    pub fn l1_capacity(mut self, capacity: usize) -> Self {
559        self.l1_capacity = Some(capacity);
560        self
561    }
562
563    /// Disable the L1 cache entirely.
564    #[cfg(feature = "l1")]
565    pub fn no_l1(mut self) -> Self {
566        self.no_l1 = true;
567        self
568    }
569
570    // Stubs for when the l1 feature is disabled — still compile cleanly.
571    #[cfg(not(feature = "l1"))]
572    pub fn l1_capacity(self, _capacity: usize) -> Self {
573        self
574    }
575
576    #[cfg(not(feature = "l1"))]
577    pub fn no_l1(self) -> Self {
578        self
579    }
580
581    /// Configure encryption from raw master key bytes and tenant ID.
582    ///
583    /// The master key must be at least 16 bytes (32 recommended).
584    /// Keys are derived per-tenant via HKDF-SHA256.
585    #[cfg(feature = "encryption")]
586    pub fn encryption_from_bytes(
587        mut self,
588        master_key: &[u8],
589        tenant_id: &str,
590    ) -> Result<Self, CachekitError> {
591        let layer = crate::encryption::EncryptionLayer::new(master_key, tenant_id)?;
592        self.encryption = Some(SharedEncryption::new(layer));
593        Ok(self)
594    }
595
596    /// Configure encryption from a hex-encoded master key string.
597    ///
598    /// Convenience wrapper that hex-decodes then delegates to
599    /// [`Self::encryption_from_bytes`].
600    #[cfg(feature = "encryption")]
601    pub fn encryption(self, hex_key: &str, tenant_id: &str) -> Result<Self, CachekitError> {
602        let bytes = hex::decode(hex_key)
603            .map_err(|e| CachekitError::Config(format!("master key is not valid hex: {e}")))?;
604        self.encryption_from_bytes(&bytes, tenant_id)
605    }
606
607    // Stub for when encryption feature is disabled.
608    #[cfg(not(feature = "encryption"))]
609    pub fn encryption_from_bytes(
610        self,
611        _master_key: &[u8],
612        _tenant_id: &str,
613    ) -> Result<Self, CachekitError> {
614        Ok(self)
615    }
616
617    #[cfg(not(feature = "encryption"))]
618    pub fn encryption(self, _hex_key: &str, _tenant_id: &str) -> Result<Self, CachekitError> {
619        Ok(self)
620    }
621
622    /// Finalise and build the [`CacheKit`] client.
623    ///
624    /// Returns an error if no backend was provided.
625    pub fn build(self) -> Result<CacheKit, CachekitError> {
626        let backend = self.backend.ok_or_else(|| {
627            CachekitError::Config("a backend must be provided via .backend()".to_owned())
628        })?;
629
630        // Validate namespace if provided
631        if let Some(ref ns) = self.namespace {
632            if ns.is_empty() {
633                return Err(CachekitError::Config("namespace cannot be empty".into()));
634            }
635            if ns.len() > 255 {
636                return Err(CachekitError::Config("namespace exceeds 255 bytes".into()));
637            }
638            if !ns.bytes().all(|b| (0x20..=0x7E).contains(&b)) {
639                return Err(CachekitError::Config(
640                    "namespace must be ASCII printable".into(),
641                ));
642            }
643        }
644
645        #[cfg(feature = "l1")]
646        let l1 = if self.no_l1 {
647            None
648        } else {
649            let capacity = self.l1_capacity.unwrap_or(1000);
650            Some(crate::l1::L1Cache::new(capacity))
651        };
652
653        Ok(CacheKit {
654            backend,
655            default_ttl: self.default_ttl.unwrap_or(Duration::from_secs(300)),
656            namespace: self.namespace,
657            max_payload_bytes: self.max_payload_bytes.unwrap_or(5 * 1024 * 1024),
658
659            #[cfg(feature = "l1")]
660            l1,
661
662            #[cfg(feature = "encryption")]
663            encryption: self.encryption,
664        })
665    }
666}