Skip to main content

multi_tier_cache/
builder.rs

1//! Cache System Builder
2//!
3//! Provides a flexible builder pattern for constructing `CacheSystem` with custom backends.
4//!
5//! # Example: Using Default Backends
6//!
7//! ```rust,no_run
8//! use multi_tier_cache::CacheSystemBuilder;
9//!
10//! #[tokio::main]
11//! async fn main() -> anyhow::Result<()> {
12//!     let cache = CacheSystemBuilder::new()
13//!         .build()
14//!         .await?;
15//!     Ok(())
16//! }
17//! ```
18//!
19//! # Example: Custom L1 Backend
20//!
21//! ```rust,no_run
22//! # use std::sync::Arc;
23//! # use futures_util::future::BoxFuture;
24//! # use bytes::Bytes;
25//! # use std::time::Duration;
26//! # use multi_tier_cache::error::CacheResult;
27//! # struct MyCustomL1Cache;
28//! # impl multi_tier_cache::CacheBackend for MyCustomL1Cache {
29//! #     fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> { Box::pin(async { None }) }
30//! #     fn set_with_ttl<'a>(&'a self, _key: &'a str, _value: Bytes, _ttl: Duration) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
31//! #     fn remove<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
32//! #     fn health_check(&self) -> BoxFuture<'_, bool> { Box::pin(async { true }) }
33//! #     fn name(&self) -> &'static str { "MyCustomL1" }
34//! # }
35//! # #[tokio::main]
36//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
37//! use multi_tier_cache::{CacheSystemBuilder, CacheBackend};
38//! use std::sync::Arc;
39//!
40//! let custom_l1 = Arc::new(MyCustomL1Cache);
41//!
42//! let cache = CacheSystemBuilder::new()
43//!     .with_l1(custom_l1)
44//!     .build()
45//!     .await?;
46//! # Ok(())
47//! # }
48//! ```
49
50#[cfg(feature = "moka")]
51#[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
52use crate::backends::MokaCacheConfig;
53use crate::traits::{CacheBackend, L2CacheBackend, StreamingBackend};
54use crate::{CacheManager, CacheSystem, CacheTier, TierConfig};
55
56#[cfg(feature = "moka")]
57#[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
58use crate::L1Cache;
59#[cfg(feature = "redis")]
60#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
61use crate::L2Cache;
62use crate::error::CacheResult;
63use std::sync::Arc;
64use tracing::info;
65
66/// Builder for constructing `CacheSystem` with custom backends
67///
68/// This builder allows you to configure custom L1 (in-memory) and L2 (distributed)
69/// cache backends, enabling you to swap Moka and Redis with alternative implementations.
70///
71/// # Multi-Tier Support (v0.5.0+)
72///
73/// The builder now supports dynamic multi-tier architectures (L1+L2+L3+L4+...).
74/// Use `.with_tier()` to add custom tiers, or `.with_l3()` / `.with_l4()` for convenience.
75///
76/// # Default Behavior
77///
78/// If no custom backends are provided, the builder uses:
79/// - **L1**: Moka in-memory cache
80/// - **L2**: Redis distributed cache
81///
82/// # Type Safety
83///
84/// The builder accepts any type that implements the required traits:
85/// - L1 backends must implement `CacheBackend`
86/// - L2 backends must implement `L2CacheBackend` (extends `CacheBackend`)
87/// - All tier backends must implement `L2CacheBackend` (for TTL support)
88/// - Streaming backends must implement `StreamingBackend`
89///
90/// # Example - Default 2-Tier
91///
92/// ```rust,no_run
93/// use multi_tier_cache::CacheSystemBuilder;
94///
95/// #[tokio::main]
96/// async fn main() -> anyhow::Result<()> {
97///     // Use default backends (Moka + Redis)
98///     let cache = CacheSystemBuilder::new()
99///         .build()
100///         .await?;
101///
102///     Ok(())
103/// }
104/// ```
105///
106/// # Example - Custom 3-Tier (v0.5.0+)
107///
108/// ```rust,no_run
109/// # use std::sync::Arc;
110/// # use futures_util::future::BoxFuture;
111/// # use bytes::Bytes;
112/// # use std::time::Duration;
113/// # use multi_tier_cache::error::CacheResult;
114/// # use multi_tier_cache::{CacheBackend, L2CacheBackend};
115/// # struct RocksDBCache;
116/// # impl CacheBackend for RocksDBCache {
117/// #     fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> { Box::pin(async { None }) }
118/// #     fn set_with_ttl<'a>(&'a self, _key: &'a str, _value: Bytes, _ttl: Duration) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
119/// #     fn remove<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
120/// #     fn health_check(&self) -> BoxFuture<'_, bool> { Box::pin(async { true }) }
121/// #     fn name(&self) -> &'static str { "RocksDB" }
122/// # }
123/// # impl L2CacheBackend for RocksDBCache {
124/// #     fn get_with_ttl<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>> { Box::pin(async { None }) }
125/// # }
126/// # #[tokio::main]
127/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
128/// use multi_tier_cache::{CacheSystemBuilder, TierConfig, L1Cache, L2Cache, MokaCacheConfig};
129/// use std::sync::Arc;
130///
131/// let l1 = Arc::new(L1Cache::new(MokaCacheConfig::default())?);
132/// let l2 = Arc::new(L2Cache::new().await?);
133/// let l3 = Arc::new(RocksDBCache);
134///
135/// let cache = CacheSystemBuilder::new()
136///     .with_tier(l1, TierConfig::as_l1())
137///     .with_tier(l2, TierConfig::as_l2())
138///     .with_l3(l3)  // Convenience method
139///     .build()
140///     .await?;
141/// # Ok(())
142/// # }
143/// ```
144pub struct CacheSystemBuilder {
145    // Legacy 2-tier configuration (v0.1.0 - v0.4.x)
146    l1_backend: Option<Arc<dyn CacheBackend>>,
147    l2_backend: Option<Arc<dyn L2CacheBackend>>,
148
149    streaming_backend: Option<Arc<dyn StreamingBackend>>,
150    #[cfg(feature = "moka")]
151    #[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
152    moka_config: Option<MokaCacheConfig>,
153
154    // Multi-tier configuration (v0.5.0+)
155    tiers: Vec<(Arc<dyn L2CacheBackend>, TierConfig)>,
156}
157
158impl CacheSystemBuilder {
159    /// Create a new builder with no custom backends configured
160    ///
161    /// By default, calling `.build()` will use Moka (L1) and Redis (L2).
162    /// Use `.with_tier()` to configure multi-tier architecture (v0.5.0+).
163    #[must_use]
164    pub fn new() -> Self {
165        Self {
166            l1_backend: None,
167            l2_backend: None,
168
169            streaming_backend: None,
170            #[cfg(feature = "moka")]
171            #[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
172            moka_config: None,
173            tiers: Vec::new(),
174        }
175    }
176
177    /// Configure a custom L1 (in-memory) cache backend
178    ///
179    /// # Arguments
180    ///
181    /// * `backend` - Any type implementing `CacheBackend` trait
182    ///
183    /// # Example
184    ///
185    /// ```rust,no_run
186    /// # use std::sync::Arc;
187    /// # use futures_util::future::BoxFuture;
188    /// # use bytes::Bytes;
189    /// # use std::time::Duration;
190    /// # use multi_tier_cache::error::CacheResult;
191    /// # struct MyCustomL1;
192    /// # impl multi_tier_cache::CacheBackend for MyCustomL1 {
193    /// #     fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> { Box::pin(async { None }) }
194    /// #     fn set_with_ttl<'a>(&'a self, _key: &'a str, _value: Bytes, _ttl: Duration) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
195    /// #     fn remove<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
196    /// #     fn health_check(&self) -> BoxFuture<'_, bool> { Box::pin(async { true }) }
197    /// #     fn name(&self) -> &'static str { "MyCustomL1" }
198    /// # }
199    /// # #[tokio::main]
200    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
201    /// use std::sync::Arc;
202    /// use multi_tier_cache::CacheSystemBuilder;
203    ///
204    /// let custom_l1 = Arc::new(MyCustomL1);
205    ///
206    /// let cache = CacheSystemBuilder::new()
207    ///     .with_l1(custom_l1)
208    ///     .build()
209    ///     .await?;
210    /// # Ok(())
211    /// # }
212    /// ```
213    #[must_use]
214    pub fn with_l1(mut self, backend: Arc<dyn CacheBackend>) -> Self {
215        self.l1_backend = Some(backend);
216        self
217    }
218
219    /// Configure custom configuration for default L1 (Moka) backend
220    #[must_use]
221    #[cfg(feature = "moka")]
222    #[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
223    pub fn with_moka_config(mut self, config: MokaCacheConfig) -> Self {
224        self.moka_config = Some(config);
225        self
226    }
227
228    /// Configure a custom L2 (distributed) cache backend
229    ///
230    /// # Arguments
231    ///
232    /// * `backend` - Any type implementing `L2CacheBackend` trait
233    ///
234    /// # Example
235    ///
236    /// ```rust,no_run
237    /// # use std::sync::Arc;
238    /// # use futures_util::future::BoxFuture;
239    /// # use bytes::Bytes;
240    /// # use std::time::Duration;
241    /// # use multi_tier_cache::error::CacheResult;
242    /// # use multi_tier_cache::{CacheBackend, L2CacheBackend};
243    /// # struct MyMemcachedBackend;
244    /// # impl CacheBackend for MyMemcachedBackend {
245    /// #     fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> { Box::pin(async { None }) }
246    /// #     fn set_with_ttl<'a>(&'a self, _key: &'a str, _value: Bytes, _ttl: Duration) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
247    /// #     fn remove<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
248    /// #     fn health_check(&self) -> BoxFuture<'_, bool> { Box::pin(async { true }) }
249    /// #     fn name(&self) -> &'static str { "MyMemcached" }
250    /// # }
251    /// # impl L2CacheBackend for MyMemcachedBackend {
252    /// #     fn get_with_ttl<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>> { Box::pin(async { None }) }
253    /// # }
254    /// # #[tokio::main]
255    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
256    /// use std::sync::Arc;
257    /// use multi_tier_cache::CacheSystemBuilder;
258    ///
259    /// let custom_l2 = Arc::new(MyMemcachedBackend);
260    ///
261    /// let cache = CacheSystemBuilder::new()
262    ///     .with_l2(custom_l2)
263    ///     .build()
264    ///     .await?;
265    /// # Ok(())
266    /// # }
267    /// ```
268    #[must_use]
269    pub fn with_l2(mut self, backend: Arc<dyn L2CacheBackend>) -> Self {
270        self.l2_backend = Some(backend);
271        self
272    }
273
274    /// Configure a custom streaming backend
275    ///
276    /// This is optional. If not provided, streaming functionality will use
277    /// the L2 backend if it implements `StreamingBackend`.
278    ///
279    /// # Arguments
280    ///
281    /// * `backend` - Any type implementing `StreamingBackend` trait
282    ///
283    /// # Example
284    ///
285    /// ```rust,no_run
286    /// # use std::sync::Arc;
287    /// # use futures_util::future::BoxFuture;
288    /// # use multi_tier_cache::error::CacheResult;
289    /// # use multi_tier_cache::StreamingBackend;
290    /// # use multi_tier_cache::traits::StreamEntry;
291    /// # struct MyKafkaBackend;
292    /// # impl StreamingBackend for MyKafkaBackend {
293    /// #     fn stream_add<'a>(&'a self, _s: &'a str, _f: Vec<(String, String)>, _m: Option<usize>) -> BoxFuture<'a, CacheResult<String>> { Box::pin(async { Ok("".to_string()) }) }
294    /// #     fn stream_read_latest<'a>(&'a self, _s: &'a str, _c: usize) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>> { Box::pin(async { Ok(vec![]) }) }
295    /// #     fn stream_read<'a>(&'a self, _s: &'a str, _l: &'a str, _c: usize, _b: Option<usize>) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>> { Box::pin(async { Ok(vec![]) }) }
296    /// #     fn stream_create_group<'a>(&'a self, _s: &'a str, _g: &'a str, _i: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
297    /// #     fn stream_read_group<'a>(&'a self, _s: &'a str, _g: &'a str, _c: &'a str, _co: usize, _b: Option<usize>) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>> { Box::pin(async { Ok(vec![]) }) }
298    /// #     fn stream_ack<'a>(&'a self, _s: &'a str, _g: &'a str, _i: &'a [String]) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
299    /// # }
300    /// # #[tokio::main]
301    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
302    /// use std::sync::Arc;
303    /// use multi_tier_cache::CacheSystemBuilder;
304    ///
305    /// let kafka_backend = Arc::new(MyKafkaBackend);
306    ///
307    /// let cache = CacheSystemBuilder::new()
308    ///     .with_streams(kafka_backend)
309    ///     .build()
310    ///     .await?;
311    /// # Ok(())
312    /// # }
313    /// ```
314    #[must_use]
315    pub fn with_streams(mut self, backend: Arc<dyn StreamingBackend>) -> Self {
316        self.streaming_backend = Some(backend);
317        self
318    }
319
320    /// Configure a cache tier with custom settings (v0.5.0+)
321    ///
322    /// Add a cache tier to the multi-tier architecture. Tiers will be sorted
323    /// by `tier_level` during build.
324    ///
325    /// # Arguments
326    ///
327    /// * `backend` - Any type implementing `L2CacheBackend` trait
328    /// * `config` - Tier configuration (level, promotion, TTL scale)
329    ///
330    /// # Example
331    ///
332    /// ```rust,no_run
333    /// # use std::sync::Arc;
334    /// # use futures_util::future::BoxFuture;
335    /// # use bytes::Bytes;
336    /// # use std::time::Duration;
337    /// # use multi_tier_cache::error::CacheResult;
338    /// # use multi_tier_cache::{CacheBackend, L2CacheBackend};
339    /// # struct RocksDBCache;
340    /// # impl CacheBackend for RocksDBCache {
341    /// #     fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> { Box::pin(async { None }) }
342    /// #     fn set_with_ttl<'a>(&'a self, _key: &'a str, _value: Bytes, _ttl: Duration) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
343    /// #     fn remove<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
344    /// #     fn health_check(&self) -> BoxFuture<'_, bool> { Box::pin(async { true }) }
345    /// #     fn name(&self) -> &'static str { "RocksDB" }
346    /// # }
347    /// # impl L2CacheBackend for RocksDBCache {
348    /// #     fn get_with_ttl<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>> { Box::pin(async { None }) }
349    /// # }
350    /// # #[tokio::main]
351    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
352    /// use multi_tier_cache::{CacheSystemBuilder, TierConfig, L1Cache, L2Cache, MokaCacheConfig};
353    /// use std::sync::Arc;
354    ///
355    /// let l1 = Arc::new(L1Cache::new(MokaCacheConfig::default())?);
356    /// let l2 = Arc::new(L2Cache::new().await?);
357    /// let l3 = Arc::new(RocksDBCache);
358    ///
359    /// let cache = CacheSystemBuilder::new()
360    ///     .with_tier(l1, TierConfig::as_l1())
361    ///     .with_tier(l2, TierConfig::as_l2())
362    ///     .with_tier(l3, TierConfig::as_l3())
363    ///     .build()
364    ///     .await?;
365    /// # Ok(())
366    /// # }
367    /// ```
368    #[must_use]
369    pub fn with_tier(mut self, backend: Arc<dyn L2CacheBackend>, config: TierConfig) -> Self {
370        self.tiers.push((backend, config));
371        self
372    }
373
374    /// Convenience method to add L3 cache tier (v0.5.0+)
375    ///
376    /// Adds a cold storage tier with 2x TTL multiplier.
377    ///
378    /// # Arguments
379    ///
380    /// * `backend` - L3 backend (e.g., `RocksDB`, `LevelDB`)
381    ///
382    /// # Example
383    ///
384    /// ```rust,no_run
385    /// # use std::sync::Arc;
386    /// # use futures_util::future::BoxFuture;
387    /// # use bytes::Bytes;
388    /// # use std::time::Duration;
389    /// # use multi_tier_cache::error::CacheResult;
390    /// # use multi_tier_cache::{CacheBackend, L2CacheBackend};
391    /// # struct RocksDBCache;
392    /// # impl CacheBackend for RocksDBCache {
393    /// #     fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> { Box::pin(async { None }) }
394    /// #     fn set_with_ttl<'a>(&'a self, _key: &'a str, _value: Bytes, _ttl: Duration) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
395    /// #     fn remove<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
396    /// #     fn health_check(&self) -> BoxFuture<'_, bool> { Box::pin(async { true }) }
397    /// #     fn name(&self) -> &'static str { "RocksDB" }
398    /// # }
399    /// # impl L2CacheBackend for RocksDBCache {
400    /// #     fn get_with_ttl<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>> { Box::pin(async { None }) }
401    /// # }
402    /// # #[tokio::main]
403    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
404    /// use std::sync::Arc;
405    /// use multi_tier_cache::CacheSystemBuilder;
406    ///
407    /// let rocksdb = Arc::new(RocksDBCache);
408    ///
409    /// let cache = CacheSystemBuilder::new()
410    ///     .with_l3(rocksdb)
411    ///     .build()
412    ///     .await?;
413    /// # Ok(())
414    /// # }
415    /// ```
416    #[must_use]
417    pub fn with_l3(mut self, backend: Arc<dyn L2CacheBackend>) -> Self {
418        self.tiers.push((backend, TierConfig::as_l3()));
419        self
420    }
421
422    /// Convenience method to add L4 cache tier (v0.5.0+)
423    ///
424    /// Adds an archive storage tier with 8x TTL multiplier.
425    ///
426    /// # Arguments
427    ///
428    /// * `backend` - L4 backend (e.g., S3, file system)
429    ///
430    /// # Example
431    ///
432    /// ```rust,no_run
433    /// # use std::sync::Arc;
434    /// # use futures_util::future::BoxFuture;
435    /// # use bytes::Bytes;
436    /// # use std::time::Duration;
437    /// # use multi_tier_cache::error::CacheResult;
438    /// # use multi_tier_cache::{CacheBackend, L2CacheBackend};
439    /// # struct S3Cache;
440    /// # impl CacheBackend for S3Cache {
441    /// #     fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> { Box::pin(async { None }) }
442    /// #     fn set_with_ttl<'a>(&'a self, _key: &'a str, _value: Bytes, _ttl: Duration) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
443    /// #     fn remove<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async { Ok(()) }) }
444    /// #     fn health_check(&self) -> BoxFuture<'_, bool> { Box::pin(async { true }) }
445    /// #     fn name(&self) -> &'static str { "S3" }
446    /// # }
447    /// # impl L2CacheBackend for S3Cache {
448    /// #     fn get_with_ttl<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>> { Box::pin(async { None }) }
449    /// # }
450    /// # #[tokio::main]
451    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
452    /// use std::sync::Arc;
453    /// use multi_tier_cache::CacheSystemBuilder;
454    ///
455    /// let s3_cache = Arc::new(S3Cache);
456    ///
457    /// let cache = CacheSystemBuilder::new()
458    ///     .with_l4(s3_cache)
459    ///     .build()
460    ///     .await?;
461    /// # Ok(())
462    /// # }
463    /// ```
464    #[must_use]
465    pub fn with_l4(mut self, backend: Arc<dyn L2CacheBackend>) -> Self {
466        self.tiers.push((backend, TierConfig::as_l4()));
467        self
468    }
469
470    /// Build the `CacheSystem` with configured or default backends
471    ///
472    /// If no custom backends were provided via `.with_l1()` or `.with_l2()`,
473    /// this method creates default backends (Moka for L1, Redis for L2).
474    ///
475    /// # Multi-Tier Mode (v0.5.0+)
476    ///
477    /// If tiers were configured via `.with_tier()`, `.with_l3()`, or `.with_l4()`,
478    /// the builder creates a multi-tier `CacheManager` using `new_with_tiers()`.
479    ///
480    /// # Returns
481    ///
482    /// * `Ok(CacheSystem)` - Successfully constructed cache system
483    /// * `Err(e)` - Failed to initialize backends (e.g., Redis connection error)
484    ///
485    /// # Example - Default 2-Tier
486    ///
487    /// ```rust,no_run
488    /// use multi_tier_cache::CacheSystemBuilder;
489    ///
490    /// #[tokio::main]
491    /// async fn main() -> anyhow::Result<()> {
492    ///     let cache = CacheSystemBuilder::new()
493    ///         .build()
494    ///         .await?;
495    ///
496    ///     // Use cache_manager for operations
497    ///     let manager = cache.cache_manager();
498    ///
499    ///     Ok(())
500    /// }
501    /// ```
502    /// # Errors
503    ///
504    /// Returns an error if the default backends cannot be initialized.
505    pub async fn build(self) -> CacheResult<CacheSystem> {
506        info!("Building Multi-Tier Cache System");
507
508        if !self.tiers.is_empty() {
509            self.build_multi_tier()
510        } else if self.l1_backend.is_none() && self.l2_backend.is_none() {
511            self.build_default_2_tier().await
512        } else {
513            self.build_custom_2_tier().await
514        }
515    }
516
517    /// Internal helper for multi-tier mode (v0.5.0+)
518    fn build_multi_tier(self) -> CacheResult<CacheSystem> {
519        info!(
520            tier_count = self.tiers.len(),
521            "Initializing multi-tier architecture"
522        );
523
524        // Sort tiers by tier_level (ascending: L1 first, L4 last)
525        let mut tiers = self.tiers;
526        tiers.sort_by_key(|(_, config)| config.tier_level);
527
528        // Convert to CacheTier instances
529        let cache_tiers: Vec<CacheTier> = tiers
530            .into_iter()
531            .map(|(backend, config)| {
532                CacheTier::new(
533                    backend,
534                    config.tier_level,
535                    config.promotion_enabled,
536                    config.promotion_frequency,
537                    config.ttl_scale,
538                )
539            })
540            .collect();
541
542        // Create cache manager with multi-tier support
543        let cache_manager = Arc::new(CacheManager::new_with_tiers(
544            cache_tiers,
545            self.streaming_backend,
546        )?);
547
548        info!("Multi-Tier Cache System built successfully");
549        info!("Note: Using multi-tier mode - use cache_manager() for all operations");
550
551        Ok(CacheSystem {
552            cache_manager,
553            #[cfg(feature = "moka")]
554            #[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
555            l1_cache: None,
556            #[cfg(feature = "redis")]
557            #[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
558            l2_cache: None,
559        })
560    }
561
562    /// Internal helper for legacy default 2-tier mode
563    async fn build_default_2_tier(self) -> CacheResult<CacheSystem> {
564        info!("Initializing default backends (Moka + Redis)");
565
566        #[cfg(all(feature = "moka", feature = "redis"))]
567        #[cfg_attr(docsrs, doc(cfg(all(feature = "moka", feature = "redis"))))]
568        {
569            let l1_cache = Arc::new(crate::L1Cache::new(self.moka_config.unwrap_or_default())?);
570            let l2_cache: Arc<crate::L2Cache> = Arc::new(crate::L2Cache::new().await?);
571
572            // Use legacy constructor that handles conversion to trait objects
573            let cache_manager =
574                Arc::new(CacheManager::new(l1_cache.clone(), l2_cache.clone()).await?);
575
576            info!("Multi-Tier Cache System built successfully");
577
578            Ok(CacheSystem {
579                cache_manager,
580                #[cfg(feature = "moka")]
581                #[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
582                l1_cache: Some(l1_cache),
583                #[cfg(feature = "redis")]
584                #[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
585                l2_cache: Some(l2_cache),
586            })
587        }
588        #[cfg(not(all(feature = "moka", feature = "redis")))]
589        {
590            Err(CacheError::ConfigError(
591                "Default backends (Moka/Redis) are not enabled. Provide custom backends or enable 'moka' and 'redis' features.".to_string()
592            ))
593        }
594    }
595
596    /// Internal helper for legacy custom 2-tier mode
597    async fn build_custom_2_tier(self) -> CacheResult<CacheSystem> {
598        info!("Building with custom backends");
599
600        let l1_backend: Arc<dyn CacheBackend> = if let Some(backend) = self.l1_backend {
601            backend
602        } else {
603            #[cfg(feature = "moka")]
604            #[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
605            {
606                Arc::new(L1Cache::new(self.moka_config.unwrap_or_default())?)
607            }
608            #[cfg(not(feature = "moka"))]
609            {
610                return Err(CacheError::ConfigError(
611                    "Moka feature not enabled. Provide a custom L1 backend.".to_string(),
612                ));
613            }
614        };
615
616        let l2_backend: Arc<dyn L2CacheBackend> = if let Some(backend) = self.l2_backend {
617            backend
618        } else {
619            #[cfg(feature = "redis")]
620            #[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
621            {
622                Arc::new(L2Cache::new().await?)
623            }
624            #[cfg(not(feature = "redis"))]
625            {
626                return Err(CacheError::ConfigError(
627                    "Redis feature not enabled. Provide a custom L2 backend.".to_string(),
628                ));
629            }
630        };
631
632        let streaming_backend = self.streaming_backend;
633
634        // Create cache manager with trait objects
635        let cache_manager = Arc::new(CacheManager::new_with_backends(
636            l1_backend,
637            l2_backend,
638            streaming_backend,
639        )?);
640
641        info!("Multi-Tier Cache System built with custom backends");
642        info!("Note: Using custom backends - use cache_manager() for all operations");
643
644        Ok(CacheSystem {
645            cache_manager,
646            #[cfg(feature = "moka")]
647            #[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
648            l1_cache: None,
649            #[cfg(feature = "redis")]
650            #[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
651            l2_cache: None,
652        })
653    }
654}
655
656impl Default for CacheSystemBuilder {
657    fn default() -> Self {
658        Self::new()
659    }
660}