1use std::time::Duration;
2
3use serde::{de::DeserializeOwned, Serialize};
4
5use crate::backend::Backend;
6use crate::error::CachekitError;
7use crate::serializer;
8
9#[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
17pub type SharedBackend = std::sync::Arc<dyn Backend>;
18
19#[cfg(any(target_arch = "wasm32", feature = "unsync"))]
21pub type SharedBackend = std::rc::Rc<dyn Backend>;
22
23#[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
28type SharedFlight = std::sync::Arc<crate::flight::FlightMap>;
29
30#[cfg(any(target_arch = "wasm32", feature = "unsync"))]
31type SharedFlight = std::rc::Rc<crate::flight::FlightMap>;
32
33#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
37type SharedMutations = std::sync::Arc<crate::flight::MutationMap>;
38
39#[cfg(all(
47 feature = "encryption",
48 not(any(target_arch = "wasm32", feature = "unsync"))
49))]
50type SharedEncryption = std::sync::Arc<crate::encryption::EncryptionLayer>;
51
52#[cfg(all(
53 feature = "encryption",
54 any(target_arch = "wasm32", feature = "unsync")
55))]
56type SharedEncryption = std::rc::Rc<crate::encryption::EncryptionLayer>;
57
58const MAX_KEY_BYTES: usize = 1024;
61
62const L1_BACKFILL_TTL_SECS: u64 = 30;
75
76fn validate_key(key: &str) -> Result<(), CachekitError> {
77 if key.is_empty() {
78 return Err(CachekitError::InvalidKey(
79 "key must not be empty".to_owned(),
80 ));
81 }
82 if key.len() > MAX_KEY_BYTES {
83 return Err(CachekitError::InvalidKey(format!(
84 "key is {} bytes (limit: {MAX_KEY_BYTES})",
85 key.len()
86 )));
87 }
88 for b in key.bytes() {
89 if b < 0x20 || b == 0x7F {
90 return Err(CachekitError::InvalidKey(format!(
91 "key contains illegal control character 0x{b:02X}"
92 )));
93 }
94 }
95 Ok(())
96}
97
98#[derive(Debug, Clone, PartialEq)]
107pub enum SwrRead<T> {
108 Fresh(T),
110 Stale(T, SwrToken),
115 Miss,
117}
118
119#[derive(Clone)]
125pub struct SwrToken {
126 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
127 state: std::sync::Arc<crate::flight::MutationState>,
128 version: u64,
129}
130
131impl std::fmt::Debug for SwrToken {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 f.debug_struct("SwrToken")
134 .field("version", &self.version)
135 .finish_non_exhaustive()
136 }
137}
138
139impl PartialEq for SwrToken {
140 fn eq(&self, other: &Self) -> bool {
141 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
142 {
143 self.version == other.version && std::sync::Arc::ptr_eq(&self.state, &other.state)
144 }
145 #[cfg(not(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32"))))]
146 {
147 self.version == other.version
148 }
149 }
150}
151
152impl Eq for SwrToken {}
153
154#[derive(Clone)]
163pub struct CacheKit {
164 backend: SharedBackend,
165 default_ttl: Duration,
166 namespace: Option<String>,
167 max_payload_bytes: usize,
168 flight: SharedFlight,
169
170 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
171 mutations: SharedMutations,
172
173 #[cfg(feature = "l1")]
174 l1: Option<crate::l1::L1Cache>,
175
176 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
177 swr_enabled: bool,
178
179 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
180 swr_threshold_ratio: f64,
181
182 #[cfg(feature = "encryption")]
183 encryption: Option<SharedEncryption>,
184}
185
186impl CacheKit {
187 pub fn builder() -> CacheKitBuilder {
189 CacheKitBuilder::default()
190 }
191
192 #[cfg(all(feature = "cachekitio", not(target_arch = "wasm32")))]
197 pub fn from_env() -> Result<CacheKitBuilder, CachekitError> {
198 use crate::backend::cachekitio::CachekitIO;
199 use crate::config::CachekitConfig;
200
201 let config = CachekitConfig::from_env()?;
202
203 let api_key_z = config
204 .api_key
205 .ok_or_else(|| CachekitError::Config("CACHEKIT_API_KEY is required".to_owned()))?;
206
207 let backend = CachekitIO::builder()
208 .api_key(api_key_z.as_str())
209 .api_url(config.api_url)
210 .build()
211 .map_err(|e| CachekitError::Config(e.to_string()))?;
212
213 #[cfg(not(feature = "unsync"))]
214 let shared: SharedBackend = std::sync::Arc::new(backend);
215 #[cfg(feature = "unsync")]
216 let shared: SharedBackend = std::rc::Rc::new(backend);
217
218 let mut builder = CacheKitBuilder::default()
219 .backend(shared)
220 .default_ttl(config.default_ttl)
221 .max_payload_bytes(config.max_payload_bytes)
222 .l1_capacity(config.l1_capacity);
223
224 if let Some(ns) = config.namespace.clone() {
225 builder = builder.namespace(ns);
226 }
227
228 #[cfg(feature = "encryption")]
230 if let Some(ref master_key) = config.master_key {
231 let namespace = config.namespace.as_deref().unwrap_or("default");
232 builder = builder.encryption_from_bytes(master_key, namespace)?;
233 }
234
235 Ok(builder)
236 }
237
238 fn namespaced_key(&self, key: &str) -> String {
241 match &self.namespace {
242 Some(ns) => format!("{ns}:{key}"),
243 None => key.to_owned(),
244 }
245 }
246
247 fn resolve_key(&self, key: &str) -> Result<String, CachekitError> {
249 validate_key(key)?;
250 Ok(self.namespaced_key(key))
251 }
252
253 #[cfg(feature = "l1")]
257 fn l1_get(&self, full_key: &str) -> Option<Vec<u8>> {
258 self.l1.as_ref().and_then(|l1| l1.get(full_key))
259 }
260
261 #[cfg(feature = "l1")]
263 fn l1_backfill(&self, full_key: &str, bytes: &[u8]) {
264 if let Some(ref l1) = self.l1 {
265 let l1_ttl = std::cmp::min(self.default_ttl, Duration::from_secs(L1_BACKFILL_TTL_SECS));
266 l1.set(full_key, bytes, l1_ttl);
267 }
268 }
269
270 #[cfg(feature = "l1")]
272 fn l1_set(&self, full_key: &str, bytes: &[u8], ttl: Duration) {
273 if let Some(ref l1) = self.l1 {
274 l1.set(full_key, bytes, ttl);
275 }
276 }
277
278 #[cfg(feature = "l1")]
280 fn l1_delete(&self, full_key: &str) {
281 if let Some(ref l1) = self.l1 {
282 l1.delete(full_key);
283 }
284 }
285
286 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
287 async fn lock_l1_mutation(&self, full_key: &str) -> Option<crate::flight::MutationGuard> {
288 if self.l1.is_some() {
289 Some(self.mutations.lock(full_key).await)
290 } else {
291 None
292 }
293 }
294
295 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
296 async fn complete_swr_bytes(
297 &self,
298 key: &str,
299 bytes: Vec<u8>,
300 ttl: Duration,
301 token: SwrToken,
302 ) -> Result<bool, CachekitError> {
303 Self::validate_ttl(ttl)?;
304 self.check_payload_size(bytes.len())?;
305 let full_key = self.resolve_key(key)?;
306 let Some(mutation) = self.lock_l1_mutation(&full_key).await else {
307 return Ok(false);
308 };
309
310 if !mutation.is_current(&token.state, token.version) {
314 return Ok(false);
315 }
316 let l1_bytes = bytes.clone();
317 self.backend.set(&full_key, bytes, Some(ttl)).await?;
318 self.l1_set(&full_key, &l1_bytes, ttl);
319 mutation.advance();
320 Ok(true)
321 }
322
323 #[cfg(not(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32"))))]
324 async fn complete_swr_bytes(
325 &self,
326 _key: &str,
327 _bytes: Vec<u8>,
328 _ttl: Duration,
329 _token: SwrToken,
330 ) -> Result<bool, CachekitError> {
331 Ok(false)
334 }
335
336 fn validate_ttl(ttl: Duration) -> Result<(), CachekitError> {
338 if ttl < Duration::from_secs(1) {
339 return Err(CachekitError::Config(format!(
340 "TTL must be at least 1 second; got {ttl:?}"
341 )));
342 }
343 Ok(())
344 }
345
346 pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CachekitError> {
353 match self.get_bytes(key).await? {
354 Some(bytes) => Ok(Some(serializer::deserialize(&bytes)?)),
355 None => Ok(None),
356 }
357 }
358
359 pub async fn interop_get<T: DeserializeOwned>(
381 &self,
382 key: &str,
383 ) -> Result<Option<T>, CachekitError> {
384 self.reject_namespaced_interop()?;
385 match self.get_bytes(key).await? {
386 Some(bytes) => Ok(Some(crate::interop::deserialize(&bytes)?)),
387 None => Ok(None),
388 }
389 }
390
391 fn reject_namespaced_interop(&self) -> Result<(), CachekitError> {
394 match self.namespace {
395 None => Ok(()),
396 Some(_) => Err(CachekitError::Config(
397 "interop reads require a client without a namespace prefix: .namespace() / \
398 CACHEKIT_NAMESPACE would store interop entries under {prefix}:{interop_key}, \
399 which other SDKs never compute (interop keys already carry a namespace \
400 segment) — use a dedicated non-namespaced client for interop entries"
401 .to_owned(),
402 )),
403 }
404 }
405
406 pub async fn interop_get_swr<T: DeserializeOwned>(
434 &self,
435 key: &str,
436 ) -> Result<SwrRead<T>, CachekitError> {
437 self.reject_namespaced_interop()?;
438 match self.get_bytes_swr(key).await? {
439 SwrRead::Fresh(b) => Ok(SwrRead::Fresh(crate::interop::deserialize(&b)?)),
440 SwrRead::Stale(b, token) => Ok(SwrRead::Stale(crate::interop::deserialize(&b)?, token)),
441 SwrRead::Miss => Ok(SwrRead::Miss),
442 }
443 }
444
445 async fn get_bytes_swr(&self, key: &str) -> Result<SwrRead<Vec<u8>>, CachekitError> {
450 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
451 if self.swr_enabled {
452 if let Some(ref l1) = self.l1 {
453 let full_key = self.resolve_key(key)?;
454 match l1.get_with_swr(&full_key, self.swr_threshold_ratio) {
455 crate::l1::L1SwrRead::Fresh(bytes) => {
456 self.check_payload_size(bytes.len())?;
457 return Ok(SwrRead::Fresh(bytes));
458 }
459 crate::l1::L1SwrRead::Stale(_) => {
460 let mutation = self.mutations.lock(&full_key).await;
465 match l1.get_with_swr(&full_key, self.swr_threshold_ratio) {
466 crate::l1::L1SwrRead::Fresh(bytes) => {
467 self.check_payload_size(bytes.len())?;
468 return Ok(SwrRead::Fresh(bytes));
469 }
470 crate::l1::L1SwrRead::Stale(bytes) => {
471 self.check_payload_size(bytes.len())?;
472 let (state, version) = mutation.snapshot();
473 return Ok(SwrRead::Stale(bytes, SwrToken { state, version }));
474 }
475 crate::l1::L1SwrRead::Miss => {}
476 }
477 }
478 crate::l1::L1SwrRead::Miss => {}
482 }
483 }
484 }
485
486 Ok(match self.get_bytes(key).await? {
487 Some(bytes) => SwrRead::Fresh(bytes),
488 None => SwrRead::Miss,
489 })
490 }
491
492 async fn get_bytes(&self, key: &str) -> Result<Option<Vec<u8>>, CachekitError> {
494 let full_key = self.resolve_key(key)?;
495
496 #[cfg(feature = "l1")]
498 if let Some(bytes) = self.l1_get(&full_key) {
499 self.check_payload_size(bytes.len())?;
500 return Ok(Some(bytes));
501 }
502
503 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
507 let _mutation = self.lock_l1_mutation(&full_key).await;
508
509 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
510 if let Some(bytes) = self.l1_get(&full_key) {
511 self.check_payload_size(bytes.len())?;
512 return Ok(Some(bytes));
513 }
514
515 let bytes = match self.backend.get(&full_key).await? {
517 Some(b) => b,
518 None => return Ok(None),
519 };
520
521 self.check_payload_size(bytes.len())?;
522
523 #[cfg(feature = "l1")]
525 self.l1_backfill(&full_key, &bytes);
526
527 Ok(Some(bytes))
528 }
529
530 pub async fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<(), CachekitError> {
532 self.set_with_ttl(key, value, self.default_ttl).await
533 }
534
535 pub async fn set_with_ttl<T: Serialize>(
539 &self,
540 key: &str,
541 value: &T,
542 ttl: Duration,
543 ) -> Result<(), CachekitError> {
544 Self::validate_ttl(ttl)?;
545
546 let bytes = serializer::serialize(value)?;
547 self.check_payload_size(bytes.len())?;
548
549 let full_key = self.resolve_key(key)?;
550
551 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
552 let mutation = self.lock_l1_mutation(&full_key).await;
553
554 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
558 if let Some(ref mutation) = mutation {
559 mutation.advance();
560 }
561
562 #[cfg(feature = "l1")]
564 {
565 let l1_bytes = bytes.clone();
566 self.backend.set(&full_key, bytes, Some(ttl)).await?;
567 self.l1_set(&full_key, &l1_bytes, ttl);
568 }
569 #[cfg(not(feature = "l1"))]
570 {
571 self.backend.set(&full_key, bytes, Some(ttl)).await?;
572 }
573
574 Ok(())
575 }
576
577 #[doc(hidden)]
580 pub async fn __complete_swr_refresh<T: Serialize>(
581 &self,
582 key: &str,
583 value: &T,
584 ttl: Duration,
585 token: SwrToken,
586 ) -> Result<bool, CachekitError> {
587 let bytes = serializer::serialize(value)?;
588 self.complete_swr_bytes(key, bytes, ttl, token).await
589 }
590
591 pub async fn delete(&self, key: &str) -> Result<bool, CachekitError> {
595 let full_key = self.resolve_key(key)?;
596
597 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
598 let mutation = self.lock_l1_mutation(&full_key).await;
599
600 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
603 if let Some(ref mutation) = mutation {
604 mutation.advance();
605 }
606
607 #[cfg(feature = "l1")]
610 self.l1_delete(&full_key);
611
612 Ok(self.backend.delete(&full_key).await?)
613 }
614
615 pub async fn exists(&self, key: &str) -> Result<bool, CachekitError> {
617 let full_key = self.resolve_key(key)?;
618
619 #[cfg(feature = "l1")]
621 if self.l1_get(&full_key).is_some() {
622 return Ok(true);
623 }
624
625 Ok(self.backend.exists(&full_key).await?)
626 }
627
628 pub async fn single_flight(&self, key: &str) -> crate::flight::SingleFlight {
642 let full_key = self.namespaced_key(key);
643 crate::flight::SingleFlight::acquire(&self.flight, &self.backend, &full_key).await
644 }
645
646 #[cfg(feature = "encryption")]
658 pub fn secure(&self) -> Result<SecureCache<'_>, CachekitError> {
659 let enc = self.encryption.as_ref().ok_or_else(|| {
660 CachekitError::Config(
661 "encryption requires CACHEKIT_MASTER_KEY or .encryption() on builder".to_owned(),
662 )
663 })?;
664 Ok(SecureCache {
665 client: self,
666 encryption: enc,
667 })
668 }
669
670 fn check_payload_size(&self, size: usize) -> Result<(), CachekitError> {
673 if size > self.max_payload_bytes {
674 return Err(CachekitError::PayloadTooLarge {
675 size,
676 limit: self.max_payload_bytes,
677 });
678 }
679 Ok(())
680 }
681}
682
683#[cfg(feature = "encryption")]
690pub struct SecureCache<'a> {
691 client: &'a CacheKit,
692 encryption: &'a crate::encryption::EncryptionLayer,
693}
694
695#[cfg(feature = "encryption")]
696impl std::fmt::Debug for SecureCache<'_> {
697 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
698 f.debug_struct("SecureCache")
699 .field("tenant_id", &self.encryption.tenant_id())
700 .finish()
701 }
702}
703
704#[cfg(feature = "encryption")]
705impl SecureCache<'_> {
706 pub async fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<(), CachekitError> {
708 self.set_with_ttl(key, value, self.client.default_ttl).await
709 }
710
711 pub async fn set_with_ttl<T: Serialize>(
713 &self,
714 key: &str,
715 value: &T,
716 ttl: Duration,
717 ) -> Result<(), CachekitError> {
718 CacheKit::validate_ttl(ttl)?;
719
720 let plaintext = serializer::serialize(value)?;
722 let ciphertext = self.encryption.encrypt(&plaintext, key)?;
723 self.client.check_payload_size(ciphertext.len())?;
728
729 let full_key = self.client.resolve_key(key)?;
730
731 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
732 let mutation = self.client.lock_l1_mutation(&full_key).await;
733
734 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
737 if let Some(ref mutation) = mutation {
738 mutation.advance();
739 }
740
741 #[cfg(feature = "l1")]
743 {
744 let l1_bytes = ciphertext.clone();
745 self.client
746 .backend
747 .set(&full_key, ciphertext, Some(ttl))
748 .await?;
749 self.client.l1_set(&full_key, &l1_bytes, ttl);
750 }
751 #[cfg(not(feature = "l1"))]
752 {
753 self.client
754 .backend
755 .set(&full_key, ciphertext, Some(ttl))
756 .await?;
757 }
758
759 Ok(())
760 }
761
762 #[doc(hidden)]
765 pub async fn __complete_swr_refresh<T: Serialize>(
766 &self,
767 key: &str,
768 value: &T,
769 ttl: Duration,
770 token: SwrToken,
771 ) -> Result<bool, CachekitError> {
772 let plaintext = serializer::serialize(value)?;
773 let ciphertext = self.encryption.encrypt(&plaintext, key)?;
774 self.client
775 .complete_swr_bytes(key, ciphertext, ttl, token)
776 .await
777 }
778
779 pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CachekitError> {
783 match self.get_plaintext(key).await? {
784 Some(plaintext) => Ok(Some(serializer::deserialize(&plaintext)?)),
785 None => Ok(None),
786 }
787 }
788
789 pub async fn interop_get<T: DeserializeOwned>(
803 &self,
804 key: &str,
805 ) -> Result<Option<T>, CachekitError> {
806 self.client.reject_namespaced_interop()?;
807 match self.get_plaintext(key).await? {
808 Some(plaintext) => Ok(Some(crate::interop::deserialize(&plaintext)?)),
809 None => Ok(None),
810 }
811 }
812
813 pub async fn interop_get_swr<T: DeserializeOwned>(
824 &self,
825 key: &str,
826 ) -> Result<SwrRead<T>, CachekitError> {
827 self.client.reject_namespaced_interop()?;
828 match self.client.get_bytes_swr(key).await? {
829 SwrRead::Fresh(ct) => Ok(SwrRead::Fresh(crate::interop::deserialize(
830 &self.encryption.decrypt(&ct, key)?,
831 )?)),
832 SwrRead::Stale(ct, token) => Ok(SwrRead::Stale(
833 crate::interop::deserialize(&self.encryption.decrypt(&ct, key)?)?,
834 token,
835 )),
836 SwrRead::Miss => Ok(SwrRead::Miss),
837 }
838 }
839
840 async fn get_plaintext(&self, key: &str) -> Result<Option<Vec<u8>>, CachekitError> {
846 match self.client.get_bytes(key).await? {
847 Some(ciphertext) => Ok(Some(self.encryption.decrypt(&ciphertext, key)?)),
848 None => Ok(None),
849 }
850 }
851
852 pub async fn delete(&self, key: &str) -> Result<bool, CachekitError> {
854 self.client.delete(key).await
855 }
856
857 pub async fn exists(&self, key: &str) -> Result<bool, CachekitError> {
859 self.client.exists(key).await
860 }
861}
862
863#[derive(Default)]
867#[must_use]
868pub struct CacheKitBuilder {
869 backend: Option<SharedBackend>,
870 default_ttl: Option<Duration>,
871 namespace: Option<String>,
872 max_payload_bytes: Option<usize>,
873
874 #[cfg(feature = "l1")]
875 l1_capacity: Option<usize>,
876
877 #[cfg(feature = "l1")]
878 no_l1: bool,
879
880 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
881 swr_enabled: Option<bool>,
882
883 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
884 swr_threshold_ratio: Option<f64>,
885
886 #[cfg(feature = "encryption")]
887 encryption: Option<SharedEncryption>,
888
889 #[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
890 reliability: Option<crate::reliability::ReliabilityConfig>,
891}
892
893impl CacheKitBuilder {
894 pub fn backend(mut self, backend: SharedBackend) -> Self {
896 self.backend = Some(backend);
897 self
898 }
899
900 pub fn default_ttl(mut self, ttl: Duration) -> Self {
902 self.default_ttl = Some(ttl);
903 self
904 }
905
906 pub fn namespace(mut self, ns: impl Into<String>) -> Self {
908 self.namespace = Some(ns.into());
909 self
910 }
911
912 pub fn max_payload_bytes(mut self, limit: usize) -> Self {
914 self.max_payload_bytes = Some(limit);
915 self
916 }
917
918 #[cfg(feature = "l1")]
920 pub fn l1_capacity(mut self, capacity: usize) -> Self {
921 self.l1_capacity = Some(capacity);
922 self
923 }
924
925 #[cfg(feature = "l1")]
927 pub fn no_l1(mut self) -> Self {
928 self.no_l1 = true;
929 self
930 }
931
932 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
946 pub fn swr_enabled(mut self, enabled: bool) -> Self {
947 self.swr_enabled = Some(enabled);
948 self
949 }
950
951 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
961 pub fn swr_threshold_ratio(mut self, ratio: f64) -> Self {
962 self.swr_threshold_ratio = Some(ratio);
963 self
964 }
965
966 #[cfg(not(feature = "l1"))]
968 pub fn l1_capacity(self, _capacity: usize) -> Self {
969 self
970 }
971
972 #[cfg(not(feature = "l1"))]
973 pub fn no_l1(self) -> Self {
974 self
975 }
976
977 #[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
987 pub fn reliability(mut self, config: crate::reliability::ReliabilityConfig) -> Self {
988 self.reliability = Some(config);
989 self
990 }
991
992 #[cfg(feature = "encryption")]
997 pub fn encryption_from_bytes(
998 mut self,
999 master_key: &[u8],
1000 tenant_id: &str,
1001 ) -> Result<Self, CachekitError> {
1002 let layer = crate::encryption::EncryptionLayer::new(master_key, tenant_id)?;
1003 self.encryption = Some(SharedEncryption::new(layer));
1004 Ok(self)
1005 }
1006
1007 #[cfg(feature = "encryption")]
1012 pub fn encryption(self, hex_key: &str, tenant_id: &str) -> Result<Self, CachekitError> {
1013 let bytes = hex::decode(hex_key)
1014 .map_err(|e| CachekitError::Config(format!("master key is not valid hex: {e}")))?;
1015 self.encryption_from_bytes(&bytes, tenant_id)
1016 }
1017
1018 #[cfg(not(feature = "encryption"))]
1020 pub fn encryption_from_bytes(
1021 self,
1022 _master_key: &[u8],
1023 _tenant_id: &str,
1024 ) -> Result<Self, CachekitError> {
1025 Ok(self)
1026 }
1027
1028 #[cfg(not(feature = "encryption"))]
1029 pub fn encryption(self, _hex_key: &str, _tenant_id: &str) -> Result<Self, CachekitError> {
1030 Ok(self)
1031 }
1032
1033 pub fn build(self) -> Result<CacheKit, CachekitError> {
1037 let backend = self.backend.ok_or_else(|| {
1038 CachekitError::Config("a backend must be provided via .backend()".to_owned())
1039 })?;
1040
1041 if let Some(ref ns) = self.namespace {
1043 if ns.is_empty() {
1044 return Err(CachekitError::Config("namespace cannot be empty".into()));
1045 }
1046 if ns.len() > 255 {
1047 return Err(CachekitError::Config("namespace exceeds 255 bytes".into()));
1048 }
1049 if !ns.bytes().all(|b| (0x20..=0x7E).contains(&b)) {
1050 return Err(CachekitError::Config(
1051 "namespace must be ASCII printable".into(),
1052 ));
1053 }
1054 }
1055
1056 #[cfg(feature = "l1")]
1057 let l1 = if self.no_l1 {
1058 None
1059 } else {
1060 let capacity = self.l1_capacity.unwrap_or(1000);
1061 Some(crate::l1::L1Cache::new(capacity))
1062 };
1063
1064 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
1067 let swr_threshold_ratio = {
1068 let ratio = self.swr_threshold_ratio.unwrap_or(0.5);
1069 if !(ratio > 0.0 && ratio <= 1.0) {
1070 return Err(CachekitError::Config(format!(
1071 "swr_threshold_ratio must be in (0.0, 1.0]; got {ratio}"
1072 )));
1073 }
1074 ratio
1075 };
1076
1077 #[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
1083 let backend = match self.reliability {
1084 Some(config) if !config.is_disabled() => {
1085 crate::reliability::wrap_reliable(backend, config)
1086 }
1087 _ => backend,
1088 };
1089
1090 Ok(CacheKit {
1091 backend,
1092 default_ttl: self.default_ttl.unwrap_or(Duration::from_secs(300)),
1093 namespace: self.namespace,
1094 max_payload_bytes: self.max_payload_bytes.unwrap_or(5 * 1024 * 1024),
1095 flight: SharedFlight::default(),
1096
1097 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
1098 mutations: SharedMutations::default(),
1099
1100 #[cfg(feature = "l1")]
1101 l1,
1102
1103 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
1104 swr_enabled: self.swr_enabled.unwrap_or(true),
1105
1106 #[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
1107 swr_threshold_ratio,
1108
1109 #[cfg(feature = "encryption")]
1110 encryption: self.encryption,
1111 })
1112 }
1113}