Skip to main content

rmqtt_storage/
circuit_breaker.rs

1//! Circuit breaker wrappers for storage backends using `tower_resilience_circuitbreaker`.
2//!
3//! Provides [`CircuitBrokenDB`], [`CircuitBrokenMap`], and [`CircuitBrokenList`] which funnel
4//! all operations through a **single shared** DB-level circuit breaker.  Map / List / DB
5//! operations share one failure metric so that a failing backend trips the breaker globally.
6//!
7//! The Tower chain is:
8//!
9//! ```text
10//! CircuitBreaker<CbTimeoutWrapper<CBStorageService>, DefaultClassifier>
11//! ```
12//!
13//! Inside `CbTimeoutWrapper` applies the optional per-operation timeout —
14//! timeouts are counted as failures by the `DefaultClassifier` and correctly
15//! contribute to the sliding window.
16//!
17//! # Concurrency
18//!
19//! The `CircuitBreaker` is held behind an `Arc` and cloned per-call (cheap —
20//! only Arcs are copied).  The inner `Circuit` state is shared via its own
21//! `Arc<tokio::sync::Mutex<Circuit>>`, providing fine-grained locking.  No
22//! outer `Mutex` is needed — `is_open()` is an atomic read, and `call()`
23//! acquires the inner lock only during `try_acquire()` / result recording
24//! (microseconds).
25//!
26//! # Feature gate
27//!
28//! This module is only compiled when the `circuit-breaker` feature is enabled **and** at least
29//! one storage backend (sled / redis / redis-cluster) is also enabled.
30//!
31//! # Example
32//!
33//! ```ignore
34//! use rmqtt_storage::circuit_breaker::{CircuitBrokenDB, CircuitBreakerConfig};
35//!
36//! let inner = init_db(&cfg).await.unwrap();
37//! let db = CircuitBrokenDB::new(inner, CircuitBreakerConfig::default());
38//! let val: Option<String> = db.get("my-key").await.unwrap();
39//! ```
40
41use std::fmt;
42use std::sync::Arc;
43use std::time::Duration;
44
45use anyhow::anyhow;
46use async_trait::async_trait;
47use serde::de::DeserializeOwned;
48use serde::Serialize;
49#[allow(unused_imports)]
50use tower::{Layer, Service, ServiceExt};
51use tower_resilience_circuitbreaker::{
52    CircuitBreakerError, CircuitBreakerLayer, DefaultClassifier, SlidingWindowType,
53};
54
55use crate::storage::{
56    AsyncIterator, DefaultStorageDB, IterItem, Key, List, Map, StorageDB, StorageList, StorageMap,
57};
58use crate::{Result, TimestampMillis};
59
60// StorageMap doesn't derive Debug; provide one so CBStorageRequest can.
61impl fmt::Debug for StorageMap {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            #[cfg(feature = "sled")]
65            StorageMap::Sled(_) => write!(f, "SledMap"),
66            #[cfg(feature = "redis")]
67            StorageMap::Redis(_) => write!(f, "RedisMap"),
68            #[cfg(feature = "redis-cluster")]
69            StorageMap::RedisCluster(_) => write!(f, "RedisClusterMap"),
70            #[cfg(feature = "redb")]
71            StorageMap::Redb(_) => write!(f, "RedbMap"),
72        }
73    }
74}
75
76// ---------------------------------------------------------------------------
77// Configuration
78// ---------------------------------------------------------------------------
79
80/// Circuit breaker configuration.
81///
82/// Defaults match the Redis-network workload typical of this crate.
83#[derive(Debug, Clone)]
84pub struct CircuitBreakerConfig {
85    /// Failure rate threshold (0.0 – 1.0). Default: 0.25
86    pub failure_rate_threshold: f64,
87    /// Sliding window configuration (enum — see [`WindowConfig`]).
88    /// Default: `WindowConfig::CountBased` with [`CountBasedWindowConfig`] defaults.
89    pub window: WindowConfig,
90    /// Minimum number of calls before the breaker can trip. Default: 10
91    pub minimum_number_of_calls: usize,
92    /// Duration the breaker stays Open before transitioning to HalfOpen. Default: 30 s
93    pub wait_duration_in_open: Duration,
94    /// Slow call duration threshold. Default: 2 s
95    pub slow_call_duration_threshold: Duration,
96    /// Slow call rate threshold. Default: 1.0 (disabled)
97    pub slow_call_rate_threshold: f64,
98    /// Per-operation timeout. If `Some(dur)`, every call that takes longer than
99    /// `dur` is aborted and counted as a failure by the circuit breaker.
100    /// Default: `None` (no timeout).
101    pub operation_timeout: Option<Duration>,
102    /// Name label for observability. Default: "storage"
103    pub name: String,
104}
105
106/// CountBased 滑动窗口专有配置。
107///
108/// 仅在 `WindowConfig::CountBased` 时生效。
109#[derive(Debug, Clone)]
110pub struct CountBasedWindowConfig {
111    /// 滑动窗口大小(调用次数)。默认: 20
112    pub sliding_window_size: usize,
113}
114
115impl Default for CountBasedWindowConfig {
116    fn default() -> Self {
117        Self {
118            sliding_window_size: 20,
119        }
120    }
121}
122
123impl From<CountBasedWindowConfig> for WindowConfig {
124    fn from(cfg: CountBasedWindowConfig) -> Self {
125        Self::CountBased(cfg)
126    }
127}
128
129/// TimeBased 滑动窗口专有配置。
130///
131/// 仅在 `WindowConfig::TimeBased` 时生效。
132#[derive(Debug, Clone)]
133pub struct TimeBasedWindowConfig {
134    /// 滑动窗口时间跨度。默认: 45s
135    pub sliding_window_duration: Duration,
136    /// 最大跟踪调用数(影响 `minimum_number_of_calls` 默认值)。默认: 20
137    pub sliding_window_size: usize,
138}
139
140impl Default for TimeBasedWindowConfig {
141    fn default() -> Self {
142        Self {
143            sliding_window_duration: Duration::from_secs(45),
144            sliding_window_size: 20,
145        }
146    }
147}
148
149impl From<TimeBasedWindowConfig> for WindowConfig {
150    fn from(cfg: TimeBasedWindowConfig) -> Self {
151        Self::TimeBased(cfg)
152    }
153}
154
155/// 滑动窗口配置枚举。
156///
157/// - `CountBased` — 基于调用次数的滑动窗口
158/// - `TimeBased` — 基于时间跨度的滑动窗口
159#[derive(Debug, Clone)]
160pub enum WindowConfig {
161    /// 基于调用次数的滑动窗口(携带 `CountBasedWindowConfig`)
162    CountBased(CountBasedWindowConfig),
163    /// 基于时间跨度的滑动窗口(携带 `TimeBasedWindowConfig`)
164    TimeBased(TimeBasedWindowConfig),
165}
166
167impl Default for WindowConfig {
168    fn default() -> Self {
169        Self::TimeBased(TimeBasedWindowConfig::default())
170    }
171}
172
173impl Default for CircuitBreakerConfig {
174    fn default() -> Self {
175        Self {
176            failure_rate_threshold: 0.25,
177            window: WindowConfig::default(),
178            minimum_number_of_calls: 10,
179            wait_duration_in_open: Duration::from_secs(30),
180            slow_call_duration_threshold: Duration::from_secs(2),
181            slow_call_rate_threshold: 1.0,
182            operation_timeout: None,
183            name: "storage".into(),
184        }
185    }
186}
187
188// ---------------------------------------------------------------------------
189// DB-level Request / Response
190// ---------------------------------------------------------------------------
191
192/// Unified request enum for all `DefaultStorageDB` operations.
193#[derive(Debug, Clone)]
194pub enum CBStorageRequest {
195    Insert {
196        key: Vec<u8>,
197        val: Vec<u8>,
198    },
199    Get {
200        key: Vec<u8>,
201    },
202    Remove {
203        key: Vec<u8>,
204    },
205    ContainsKey {
206        key: Vec<u8>,
207    },
208    #[allow(dead_code)]
209    DbMap {
210        name: Vec<u8>,
211        expire: Option<TimestampMillis>,
212    },
213    DbMapRemove {
214        name: Vec<u8>,
215    },
216    DbMapContainsKey {
217        key: Vec<u8>,
218    },
219    #[allow(dead_code)]
220    DbList {
221        name: Vec<u8>,
222        expire: Option<TimestampMillis>,
223    },
224    DbListRemove {
225        name: Vec<u8>,
226    },
227    DbListContainsKey {
228        key: Vec<u8>,
229    },
230    CounterIncr {
231        key: Vec<u8>,
232        increment: isize,
233    },
234    CounterDecr {
235        key: Vec<u8>,
236        decrement: isize,
237    },
238    CounterGet {
239        key: Vec<u8>,
240    },
241    CounterSet {
242        key: Vec<u8>,
243        val: isize,
244    },
245    BatchInsert {
246        key_vals: Vec<(Vec<u8>, Vec<u8>)>,
247    },
248    BatchRemove {
249        keys: Vec<Vec<u8>>,
250    },
251    DbSize,
252    Info,
253    #[cfg(feature = "len")]
254    Len,
255    #[cfg(feature = "ttl")]
256    ExpireAt {
257        key: Vec<u8>,
258        at: TimestampMillis,
259    },
260    #[cfg(feature = "ttl")]
261    Expire {
262        key: Vec<u8>,
263        dur: TimestampMillis,
264    },
265    #[cfg(feature = "ttl")]
266    Ttl {
267        key: Vec<u8>,
268    },
269    // ── Map operations ──
270    MapInsert {
271        map: StorageMap,
272        key: Vec<u8>,
273        val: Vec<u8>,
274    },
275    MapGet {
276        map: StorageMap,
277        key: Vec<u8>,
278    },
279    MapRemove {
280        map: StorageMap,
281        key: Vec<u8>,
282    },
283    MapContainsKey {
284        map: StorageMap,
285        key: Vec<u8>,
286    },
287    MapIsEmpty {
288        map: StorageMap,
289    },
290    MapClear {
291        map: StorageMap,
292    },
293    MapRemoveAndFetch {
294        map: StorageMap,
295        key: Vec<u8>,
296    },
297    MapRemoveWithPrefix {
298        map: StorageMap,
299        prefix: Vec<u8>,
300    },
301    MapBatchInsert {
302        map: StorageMap,
303        key_vals: Vec<(Vec<u8>, Vec<u8>)>,
304    },
305    MapBatchRemove {
306        map: StorageMap,
307        keys: Vec<Vec<u8>>,
308    },
309    #[cfg(feature = "map_len")]
310    MapLen {
311        map: StorageMap,
312    },
313    #[cfg(feature = "ttl")]
314    MapExpireAt {
315        map: StorageMap,
316        at: TimestampMillis,
317    },
318    #[cfg(feature = "ttl")]
319    MapExpire {
320        map: StorageMap,
321        dur: TimestampMillis,
322    },
323    #[cfg(feature = "ttl")]
324    MapTtl {
325        map: StorageMap,
326    },
327    // ── List operations ──
328    ListPush {
329        list: StorageList,
330        val: Vec<u8>,
331    },
332    ListPushs {
333        list: StorageList,
334        vals: Vec<Vec<u8>>,
335    },
336    ListPushLimit {
337        list: StorageList,
338        val: Vec<u8>,
339        limit: usize,
340        pop_front_if_limited: bool,
341    },
342    ListPop {
343        list: StorageList,
344    },
345    ListGetAll {
346        list: StorageList,
347    },
348    ListGetIndex {
349        list: StorageList,
350        idx: usize,
351    },
352    ListLen {
353        list: StorageList,
354    },
355    ListIsEmpty {
356        list: StorageList,
357    },
358    ListClear {
359        list: StorageList,
360    },
361    #[cfg(feature = "ttl")]
362    ListExpireAt {
363        list: StorageList,
364        at: TimestampMillis,
365    },
366    #[cfg(feature = "ttl")]
367    ListExpire {
368        list: StorageList,
369        dur: TimestampMillis,
370    },
371    #[cfg(feature = "ttl")]
372    ListTtl {
373        list: StorageList,
374    },
375}
376
377/// Unified response enum for all `DefaultStorageDB` operations.
378// Manual Debug impl because StorageMap / StorageList don't implement Debug.
379pub enum CBStorageResponse {
380    Insert,
381    Get(Option<Vec<u8>>),
382    Remove,
383    ContainsKey(bool),
384    #[allow(dead_code)]
385    DbMap(StorageMap),
386    DbMapRemove,
387    DbMapContainsKey(bool),
388    #[allow(dead_code)]
389    DbList(StorageList),
390    DbListRemove,
391    DbListContainsKey(bool),
392    CounterIncr,
393    CounterDecr,
394    CounterGet(Option<isize>),
395    CounterSet,
396    BatchInsert,
397    BatchRemove,
398    DbSize(usize),
399    Info(serde_json::Value),
400    #[cfg(feature = "len")]
401    Len(usize),
402    #[cfg(feature = "ttl")]
403    ExpireAt(bool),
404    #[cfg(feature = "ttl")]
405    Expire(bool),
406    #[cfg(feature = "ttl")]
407    Ttl(Option<TimestampMillis>),
408    // ── Map responses ──
409    MapInsert,
410    MapGet(Option<Vec<u8>>),
411    MapRemove,
412    MapContainsKey(bool),
413    MapIsEmpty(bool),
414    MapClear,
415    MapRemoveAndFetch(Option<Vec<u8>>),
416    MapRemoveWithPrefix,
417    MapBatchInsert,
418    MapBatchRemove,
419    #[cfg(feature = "map_len")]
420    MapLen(usize),
421    #[cfg(feature = "ttl")]
422    MapExpireAt(bool),
423    #[cfg(feature = "ttl")]
424    MapExpire(bool),
425    #[cfg(feature = "ttl")]
426    MapTtl(Option<TimestampMillis>),
427    // ── List responses ──
428    ListPush,
429    ListPushs,
430    ListPushLimit(Option<Vec<u8>>),
431    ListPop(Option<Vec<u8>>),
432    ListGetAll(Vec<Vec<u8>>),
433    ListGetIndex(Option<Vec<u8>>),
434    ListLen(usize),
435    ListIsEmpty(bool),
436    ListClear,
437    #[cfg(feature = "ttl")]
438    ListExpireAt(bool),
439    #[cfg(feature = "ttl")]
440    ListExpire(bool),
441    #[cfg(feature = "ttl")]
442    ListTtl(Option<TimestampMillis>),
443}
444
445impl fmt::Debug for CBStorageResponse {
446    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447        match self {
448            CBStorageResponse::Insert => write!(f, "Insert"),
449            CBStorageResponse::Get(v) => f.debug_tuple("Get").field(v).finish(),
450            CBStorageResponse::Remove => write!(f, "Remove"),
451            CBStorageResponse::ContainsKey(b) => f.debug_tuple("ContainsKey").field(b).finish(),
452            CBStorageResponse::DbMap(_) => write!(f, "Map(..)"),
453            CBStorageResponse::DbMapRemove => write!(f, "MapRemove"),
454            CBStorageResponse::DbMapContainsKey(b) => {
455                f.debug_tuple("MapContainsKey").field(b).finish()
456            }
457            CBStorageResponse::DbList(_) => write!(f, "List(..)"),
458            CBStorageResponse::DbListRemove => write!(f, "ListRemove"),
459            CBStorageResponse::DbListContainsKey(b) => {
460                f.debug_tuple("ListContainsKey").field(b).finish()
461            }
462            CBStorageResponse::CounterIncr => write!(f, "CounterIncr"),
463            CBStorageResponse::CounterDecr => write!(f, "CounterDecr"),
464            CBStorageResponse::CounterGet(v) => f.debug_tuple("CounterGet").field(v).finish(),
465            CBStorageResponse::CounterSet => write!(f, "CounterSet"),
466            CBStorageResponse::BatchInsert => write!(f, "BatchInsert"),
467            CBStorageResponse::BatchRemove => write!(f, "BatchRemove"),
468            CBStorageResponse::DbSize(s) => f.debug_tuple("DbSize").field(s).finish(),
469            CBStorageResponse::Info(v) => f.debug_tuple("Info").field(v).finish(),
470            #[cfg(feature = "len")]
471            CBStorageResponse::Len(n) => f.debug_tuple("Len").field(n).finish(),
472            #[cfg(feature = "ttl")]
473            CBStorageResponse::ExpireAt(b) => f.debug_tuple("ExpireAt").field(b).finish(),
474            #[cfg(feature = "ttl")]
475            CBStorageResponse::Expire(b) => f.debug_tuple("Expire").field(b).finish(),
476            #[cfg(feature = "ttl")]
477            CBStorageResponse::Ttl(v) => f.debug_tuple("Ttl").field(v).finish(),
478            // ── Map responses ──
479            CBStorageResponse::MapInsert => write!(f, "MapInsert"),
480            CBStorageResponse::MapGet(v) => f.debug_tuple("MapGet").field(v).finish(),
481            CBStorageResponse::MapRemove => write!(f, "MapRemove"),
482            CBStorageResponse::MapContainsKey(b) => {
483                f.debug_tuple("MapContainsKey").field(b).finish()
484            }
485            CBStorageResponse::MapIsEmpty(b) => f.debug_tuple("MapIsEmpty").field(b).finish(),
486            CBStorageResponse::MapClear => write!(f, "MapClear"),
487            CBStorageResponse::MapRemoveAndFetch(v) => {
488                f.debug_tuple("MapRemoveAndFetch").field(v).finish()
489            }
490            CBStorageResponse::MapRemoveWithPrefix => write!(f, "MapRemoveWithPrefix"),
491            CBStorageResponse::MapBatchInsert => write!(f, "MapBatchInsert"),
492            CBStorageResponse::MapBatchRemove => write!(f, "MapBatchRemove"),
493            #[cfg(feature = "map_len")]
494            CBStorageResponse::MapLen(n) => f.debug_tuple("MapLen").field(n).finish(),
495            #[cfg(feature = "ttl")]
496            CBStorageResponse::MapExpireAt(b) => f.debug_tuple("MapExpireAt").field(b).finish(),
497            #[cfg(feature = "ttl")]
498            CBStorageResponse::MapExpire(b) => f.debug_tuple("MapExpire").field(b).finish(),
499            #[cfg(feature = "ttl")]
500            CBStorageResponse::MapTtl(v) => f.debug_tuple("MapTtl").field(v).finish(),
501            // ── List responses ──
502            CBStorageResponse::ListPush => write!(f, "ListPush"),
503            CBStorageResponse::ListPushs => write!(f, "ListPushs"),
504            CBStorageResponse::ListPushLimit(v) => f.debug_tuple("ListPushLimit").field(v).finish(),
505            CBStorageResponse::ListPop(v) => f.debug_tuple("ListPop").field(v).finish(),
506            CBStorageResponse::ListGetAll(v) => {
507                f.debug_tuple("ListGetAll").field(&v.len()).finish()
508            }
509            CBStorageResponse::ListGetIndex(v) => f.debug_tuple("ListGetIndex").field(v).finish(),
510            CBStorageResponse::ListLen(n) => f.debug_tuple("ListLen").field(n).finish(),
511            CBStorageResponse::ListIsEmpty(b) => f.debug_tuple("ListIsEmpty").field(b).finish(),
512            CBStorageResponse::ListClear => write!(f, "ListClear"),
513            #[cfg(feature = "ttl")]
514            CBStorageResponse::ListExpireAt(b) => f.debug_tuple("ListExpireAt").field(b).finish(),
515            #[cfg(feature = "ttl")]
516            CBStorageResponse::ListExpire(b) => f.debug_tuple("ListExpire").field(b).finish(),
517            #[cfg(feature = "ttl")]
518            CBStorageResponse::ListTtl(v) => f.debug_tuple("ListTtl").field(v).finish(),
519        }
520    }
521}
522
523// ---------------------------------------------------------------------------
524// DB-level Tower Service
525// ---------------------------------------------------------------------------
526
527/// A Tower [`Service`] that dispatches [`CBStorageRequest`] to the inner [`DefaultStorageDB`].
528#[derive(Clone)]
529pub struct CBStorageService {
530    inner: DefaultStorageDB,
531}
532
533impl tower::Service<CBStorageRequest> for CBStorageService {
534    type Response = CBStorageResponse;
535    type Error = anyhow::Error;
536    type Future =
537        std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self::Response>> + Send>>;
538
539    fn poll_ready(
540        &mut self,
541        _cx: &mut std::task::Context<'_>,
542    ) -> std::task::Poll<std::result::Result<(), Self::Error>> {
543        std::task::Poll::Ready(Ok(()))
544    }
545
546    fn call(&mut self, req: CBStorageRequest) -> Self::Future {
547        let this = self.inner.clone();
548        Box::pin(async move {
549            match req {
550                CBStorageRequest::Insert { key, val } => this
551                    .insert_raw(&key, &val)
552                    .await
553                    .map(|_| CBStorageResponse::Insert),
554                CBStorageRequest::Get { key } => {
555                    let val = this.get_raw(&key).await?;
556                    Ok(CBStorageResponse::Get(val))
557                }
558                CBStorageRequest::Remove { key } => {
559                    this.remove(&key).await.map(|_| CBStorageResponse::Remove)
560                }
561                CBStorageRequest::ContainsKey { key } => this
562                    .contains_key(&key)
563                    .await
564                    .map(CBStorageResponse::ContainsKey),
565                CBStorageRequest::DbMap { name, expire } => {
566                    let m = this.map(&name, expire).await;
567                    Ok(CBStorageResponse::DbMap(m))
568                }
569                CBStorageRequest::DbMapRemove { name } => this
570                    .map_remove(&name)
571                    .await
572                    .map(|_| CBStorageResponse::DbMapRemove),
573                CBStorageRequest::DbMapContainsKey { key } => this
574                    .map_contains_key(&key)
575                    .await
576                    .map(CBStorageResponse::DbMapContainsKey),
577                CBStorageRequest::DbList { name, expire } => {
578                    let l = this.list(&name, expire).await;
579                    Ok(CBStorageResponse::DbList(l))
580                }
581                CBStorageRequest::DbListRemove { name } => this
582                    .list_remove(&name)
583                    .await
584                    .map(|_| CBStorageResponse::DbListRemove),
585                CBStorageRequest::DbListContainsKey { key } => this
586                    .list_contains_key(&key)
587                    .await
588                    .map(CBStorageResponse::DbListContainsKey),
589                CBStorageRequest::CounterIncr { key, increment } => this
590                    .counter_incr(&key, increment)
591                    .await
592                    .map(|_| CBStorageResponse::CounterIncr),
593                CBStorageRequest::CounterDecr { key, decrement } => this
594                    .counter_decr(&key, decrement)
595                    .await
596                    .map(|_| CBStorageResponse::CounterDecr),
597                CBStorageRequest::CounterGet { key } => this
598                    .counter_get(&key)
599                    .await
600                    .map(CBStorageResponse::CounterGet),
601                CBStorageRequest::CounterSet { key, val } => this
602                    .counter_set(&key, val)
603                    .await
604                    .map(|_| CBStorageResponse::CounterSet),
605                CBStorageRequest::BatchInsert { key_vals } => this
606                    .batch_insert_raw(key_vals)
607                    .await
608                    .map(|_| CBStorageResponse::BatchInsert),
609                CBStorageRequest::BatchRemove { keys } => this
610                    .batch_remove(keys)
611                    .await
612                    .map(|_| CBStorageResponse::BatchRemove),
613                CBStorageRequest::DbSize => this.db_size().await.map(CBStorageResponse::DbSize),
614                CBStorageRequest::Info => this.info().await.map(CBStorageResponse::Info),
615                #[cfg(feature = "len")]
616                CBStorageRequest::Len => this.len().await.map(CBStorageResponse::Len),
617                #[cfg(feature = "ttl")]
618                CBStorageRequest::ExpireAt { key, at } => this
619                    .expire_at(&key, at)
620                    .await
621                    .map(CBStorageResponse::ExpireAt),
622                #[cfg(feature = "ttl")]
623                CBStorageRequest::Expire { key, dur } => {
624                    this.expire(&key, dur).await.map(CBStorageResponse::Expire)
625                }
626                #[cfg(feature = "ttl")]
627                CBStorageRequest::Ttl { key } => this.ttl(&key).await.map(CBStorageResponse::Ttl),
628                // ── Map dispatch ──
629                CBStorageRequest::MapInsert { map, key, val } => map
630                    .insert_raw(&key, &val)
631                    .await
632                    .map(|_| CBStorageResponse::MapInsert),
633                CBStorageRequest::MapGet { map, key } => {
634                    map.get_raw(&key).await.map(CBStorageResponse::MapGet)
635                }
636                CBStorageRequest::MapRemove { map, key } => {
637                    map.remove(&key).await.map(|_| CBStorageResponse::MapRemove)
638                }
639                CBStorageRequest::MapContainsKey { map, key } => map
640                    .contains_key(&key)
641                    .await
642                    .map(CBStorageResponse::MapContainsKey),
643                CBStorageRequest::MapIsEmpty { map } => {
644                    map.is_empty().await.map(CBStorageResponse::MapIsEmpty)
645                }
646                CBStorageRequest::MapClear { map } => {
647                    map.clear().await.map(|_| CBStorageResponse::MapClear)
648                }
649                CBStorageRequest::MapRemoveAndFetch { map, key } => map
650                    .remove_and_fetch_raw(&key)
651                    .await
652                    .map(CBStorageResponse::MapRemoveAndFetch),
653                CBStorageRequest::MapRemoveWithPrefix { map, prefix } => map
654                    .remove_with_prefix(&prefix)
655                    .await
656                    .map(|_| CBStorageResponse::MapRemoveWithPrefix),
657                CBStorageRequest::MapBatchInsert { map, key_vals } => map
658                    .batch_insert_raw(key_vals)
659                    .await
660                    .map(|_| CBStorageResponse::MapBatchInsert),
661                CBStorageRequest::MapBatchRemove { map, keys } => map
662                    .batch_remove(keys)
663                    .await
664                    .map(|_| CBStorageResponse::MapBatchRemove),
665                #[cfg(feature = "map_len")]
666                CBStorageRequest::MapLen { map } => map.len().await.map(CBStorageResponse::MapLen),
667                #[cfg(feature = "ttl")]
668                CBStorageRequest::MapExpireAt { map, at } => {
669                    map.expire_at(at).await.map(CBStorageResponse::MapExpireAt)
670                }
671                #[cfg(feature = "ttl")]
672                CBStorageRequest::MapExpire { map, dur } => {
673                    map.expire(dur).await.map(CBStorageResponse::MapExpire)
674                }
675                #[cfg(feature = "ttl")]
676                CBStorageRequest::MapTtl { map } => map.ttl().await.map(CBStorageResponse::MapTtl),
677                // ── List dispatch ──
678                CBStorageRequest::ListPush { list, val } => list
679                    .push_raw(&val)
680                    .await
681                    .map(|_| CBStorageResponse::ListPush),
682                CBStorageRequest::ListPushs { list, vals } => list
683                    .pushs_raw(vals)
684                    .await
685                    .map(|_| CBStorageResponse::ListPushs),
686                CBStorageRequest::ListPushLimit {
687                    list,
688                    val,
689                    limit,
690                    pop_front_if_limited,
691                } => list
692                    .push_limit_raw(&val, limit, pop_front_if_limited)
693                    .await
694                    .map(CBStorageResponse::ListPushLimit),
695                CBStorageRequest::ListPop { list } => {
696                    list.pop_raw().await.map(CBStorageResponse::ListPop)
697                }
698                CBStorageRequest::ListGetAll { list } => {
699                    list.all_raw().await.map(CBStorageResponse::ListGetAll)
700                }
701                CBStorageRequest::ListGetIndex { list, idx } => list
702                    .get_index_raw(idx)
703                    .await
704                    .map(CBStorageResponse::ListGetIndex),
705                CBStorageRequest::ListLen { list } => {
706                    list.len().await.map(CBStorageResponse::ListLen)
707                }
708                CBStorageRequest::ListIsEmpty { list } => {
709                    list.is_empty().await.map(CBStorageResponse::ListIsEmpty)
710                }
711                CBStorageRequest::ListClear { list } => {
712                    list.clear().await.map(|_| CBStorageResponse::ListClear)
713                }
714                #[cfg(feature = "ttl")]
715                CBStorageRequest::ListExpireAt { list, at } => list
716                    .expire_at(at)
717                    .await
718                    .map(CBStorageResponse::ListExpireAt),
719                #[cfg(feature = "ttl")]
720                CBStorageRequest::ListExpire { list, dur } => {
721                    list.expire(dur).await.map(CBStorageResponse::ListExpire)
722                }
723                #[cfg(feature = "ttl")]
724                CBStorageRequest::ListTtl { list } => {
725                    list.ttl().await.map(CBStorageResponse::ListTtl)
726                }
727            }
728        })
729    }
730}
731
732// ---------------------------------------------------------------------------
733// CbTimeoutWrapper — inserts per-operation timeout into the Tower chain
734// ---------------------------------------------------------------------------
735
736/// Tower [`Service`] wrapper that applies an optional timeout to each call.
737///
738/// When the timeout fires, the inner service's future is dropped and an
739/// `anyhow::Error` is returned. The enclosing `CircuitBreaker` then sees an
740/// `Err` and the [`DefaultClassifier`] correctly counts it as a failure,
741/// so timeouts are properly tracked in the sliding window.
742///
743/// When `timeout` is `None`, calls pass through unchanged (zero overhead).
744#[derive(Clone)]
745pub(crate) struct CbTimeoutWrapper<S> {
746    inner: S,
747    timeout: Option<Duration>,
748}
749
750impl<S, Req> tower::Service<Req> for CbTimeoutWrapper<S>
751where
752    S: tower::Service<Req> + Clone + Send + 'static,
753    S::Response: Send + 'static,
754    S::Error: Into<anyhow::Error> + Send + 'static,
755    S::Future: Send + 'static,
756    Req: Send + 'static,
757{
758    type Response = S::Response;
759    type Error = anyhow::Error;
760    type Future = std::pin::Pin<
761        Box<
762            dyn std::future::Future<Output = std::result::Result<Self::Response, Self::Error>>
763                + Send,
764        >,
765    >;
766
767    fn poll_ready(
768        &mut self,
769        cx: &mut std::task::Context<'_>,
770    ) -> std::task::Poll<std::result::Result<(), Self::Error>> {
771        self.inner.poll_ready(cx).map_err(|e| e.into())
772    }
773
774    fn call(&mut self, req: Req) -> Self::Future {
775        let fut = self.inner.call(req);
776        let timeout = self.timeout;
777        Box::pin(async move {
778            match timeout {
779                Some(dur) => match tokio::time::timeout(dur, fut).await {
780                    Ok(r) => r.map_err(|e| e.into()),
781                    Err(_) => Err(anyhow!("operation timed out after {:?}", dur)),
782                },
783                None => fut.await.map_err(|e| e.into()),
784            }
785        })
786    }
787}
788
789// ---------------------------------------------------------------------------
790// Helper: build Tower service chains with circuit breaker
791// ---------------------------------------------------------------------------
792
793/// Build the circuit breaker for DB-level service.
794fn build_db_cb(
795    inner: CBStorageService,
796    config: &CircuitBreakerConfig,
797    name: &str,
798) -> tower_resilience_circuitbreaker::CircuitBreaker<
799    CbTimeoutWrapper<CBStorageService>,
800    DefaultClassifier,
801> {
802    let timeout_svc = CbTimeoutWrapper {
803        inner,
804        timeout: config.operation_timeout,
805    };
806    build_cb_inner(timeout_svc, config, name)
807}
808
809fn build_cb_inner<S, Req>(
810    inner: S,
811    config: &CircuitBreakerConfig,
812    name: &str,
813) -> tower_resilience_circuitbreaker::CircuitBreaker<S, DefaultClassifier>
814where
815    S: tower::Service<Req> + Clone + Send + 'static,
816    S::Response: Send + 'static,
817    S::Error: Into<anyhow::Error> + Send + 'static,
818    S::Future: Send + 'static,
819    Req: Send + 'static,
820{
821    let n = name.to_owned();
822    let mut builder = CircuitBreakerLayer::builder()
823        .name(name)
824        .failure_rate_threshold(config.failure_rate_threshold)
825        .minimum_number_of_calls(config.minimum_number_of_calls)
826        .wait_duration_in_open(config.wait_duration_in_open)
827        .slow_call_duration_threshold(config.slow_call_duration_threshold)
828        .slow_call_rate_threshold(config.slow_call_rate_threshold);
829
830    match &config.window {
831        WindowConfig::CountBased(cfg) => {
832            builder = builder
833                .sliding_window_type(SlidingWindowType::CountBased)
834                .sliding_window_size(cfg.sliding_window_size);
835        }
836        WindowConfig::TimeBased(cfg) => {
837            builder = builder
838                .sliding_window_type(SlidingWindowType::TimeBased)
839                .sliding_window_duration(cfg.sliding_window_duration)
840                .sliding_window_size(cfg.sliding_window_size);
841        }
842    }
843
844    builder
845        .on_state_transition(move |from, to| {
846            log::info!(
847                "[circuit-breaker:{}] state changed: {:?} -> {:?}",
848                n,
849                from,
850                to
851            );
852        })
853        .build()
854        .layer(inner)
855}
856
857/// Shared circuit breaker type: an `Arc`-wrapped Tower `CircuitBreaker` wrapping
858/// a `CbTimeoutWrapper<CBStorageService>` with the default failure classifier.
859/// All three layers (DB / Map / List) use this same type.
860type SharedBreaker = Arc<
861    tower_resilience_circuitbreaker::CircuitBreaker<
862        CbTimeoutWrapper<CBStorageService>,
863        DefaultClassifier,
864    >,
865>;
866
867// ---------------------------------------------------------------------------
868// CircuitBrokenDB — public wrapper
869// ---------------------------------------------------------------------------
870
871/// A circuit-breaker-protected wrapper around [`DefaultStorageDB`].
872///
873/// Every operation goes through a Tower service chain that tracks success/failure.
874/// When the failure rate exceeds the configured threshold, the circuit opens and
875/// subsequent calls fail fast with an error rather than waiting for a timeout.
876pub struct CircuitBrokenDB {
877    /// The DB-level circuit breaker — cloned per-call for concurrency.
878    service: SharedBreaker,
879    config: CircuitBreakerConfig,
880    /// Raw inner DB for pass-through operations that don't need CB protection
881    /// (e.g., map_iter, list_iter, scan). Cloned cheaply from the DB passed to
882    /// the Tower service chain during construction.
883    inner: DefaultStorageDB,
884}
885
886impl CircuitBrokenDB {
887    /// Wrap a `DefaultStorageDB` with a circuit breaker.
888    pub fn new(db: DefaultStorageDB, config: CircuitBreakerConfig) -> Self {
889        let name = config.name.clone();
890        let inner = db.clone();
891        let chain_svc = CBStorageService { inner: db };
892        // CbTimeoutWrapper is inserted inside the CircuitBreaker by build_db_cb,
893        // so timeout → Err → DefaultClassifier counts it as a failure.
894        let service = Arc::new(build_db_cb(chain_svc, &config, &name));
895        Self {
896            service,
897            config,
898            inner,
899        }
900    }
901
902    /// Return a reference to the circuit breaker configuration.
903    pub fn config(&self) -> &CircuitBreakerConfig {
904        &self.config
905    }
906
907    async fn raw_call(&self, req: CBStorageRequest) -> Result<CBStorageResponse> {
908        // Clone once per call — shares circuit state (Arc), no lock contention.
909        // The CircuitBreaker handles try_acquire (with Open→HalfOpen transition),
910        // dispatch through CbTimeoutWrapper (with optional timeout), and automatic
911        // result recording via DefaultClassifier — all in a single Tower call.
912        let mut svc = self.service.as_ref().clone();
913
914        match svc.call(req).await {
915            Ok(r) => Ok(r),
916            Err(CircuitBreakerError::OpenCircuit) => Err(anyhow!(
917                "storage unavailable (circuit breaker open for '{}')",
918                self.config.name
919            )),
920            Err(CircuitBreakerError::Inner(e)) => Err(e),
921        }
922    }
923
924    /// Return a JSON snapshot of the circuit breaker's runtime state & config.
925    ///
926    /// Analogous to [`StorageDB::info()`] but for the breaker itself. Reads
927    /// atomic-loaded fields (`state`, `is_open`) without acquiring the
928    /// circuit's internal lock, and acquires the lock only for the richer
929    /// [`metrics()`](tower_resilience_circuitbreaker::CircuitBreaker::metrics) snapshot.
930    pub async fn cb_info(&self) -> serde_json::Value {
931        let metrics = self.service.metrics().await;
932
933        serde_json::json!({
934            "name": self.config.name,
935            "state": format!("{:?}", metrics.state),
936            "state_code": metrics.state as u8,
937            "is_open": self.service.is_open(),
938            "metrics": {
939                "total_calls": metrics.total_calls,
940                "failure_count": metrics.failure_count,
941                "success_count": metrics.success_count,
942                "slow_call_count": metrics.slow_call_count,
943                "failure_rate": metrics.failure_rate,
944                "slow_call_rate": metrics.slow_call_rate,
945                "time_since_state_change_ms": metrics.time_since_state_change.as_millis() as u64,
946            },
947            "config": {
948                "failure_rate_threshold": self.config.failure_rate_threshold,
949                "window": match &self.config.window {
950                    WindowConfig::CountBased(cfg) => serde_json::json!({
951                        "type": "CountBased",
952                        "sliding_window_size": cfg.sliding_window_size,
953                    }),
954                    WindowConfig::TimeBased(cfg) => serde_json::json!({
955                        "type": "TimeBased",
956                        "sliding_window_duration_ms": cfg.sliding_window_duration.as_millis() as u64,
957                        "sliding_window_size": cfg.sliding_window_size,
958                    }),
959                },
960                "minimum_number_of_calls": self.config.minimum_number_of_calls,
961                "wait_duration_in_open_ms": self.config.wait_duration_in_open.as_millis() as u64,
962                "slow_call_duration_threshold_ms": self.config.slow_call_duration_threshold.as_millis() as u64,
963                "slow_call_rate_threshold": self.config.slow_call_rate_threshold,
964                "operation_timeout_ms": self.config.operation_timeout.map(|d| d.as_millis() as u64),
965            },
966        })
967    }
968}
969
970impl Clone for CircuitBrokenDB {
971    fn clone(&self) -> Self {
972        Self {
973            service: Arc::clone(&self.service),
974            config: self.config.clone(),
975            inner: self.inner.clone(),
976        }
977    }
978}
979
980// ---------------------------------------------------------------------------
981// StorageDB trait implementation for CircuitBrokenDB
982// ---------------------------------------------------------------------------
983
984#[async_trait]
985impl StorageDB for CircuitBrokenDB {
986    type MapType = CircuitBrokenMap;
987    type ListType = CircuitBrokenList;
988
989    async fn map<N: AsRef<[u8]> + Sync + Send>(
990        &self,
991        name: N,
992        expire: Option<TimestampMillis>,
993    ) -> Self::MapType {
994        let raw = self.inner.map(name, expire).await;
995        CircuitBrokenMap::new(raw, Arc::clone(&self.service), self.config.name.clone())
996    }
997
998    async fn map_remove<K>(&self, name: K) -> Result<()>
999    where
1000        K: AsRef<[u8]> + Sync + Send,
1001    {
1002        self.raw_call(CBStorageRequest::DbMapRemove {
1003            name: name.as_ref().to_vec(),
1004        })
1005        .await
1006        .map(|_| ())
1007    }
1008
1009    async fn map_contains_key<K: AsRef<[u8]> + Sync + Send>(&self, key: K) -> Result<bool> {
1010        match self
1011            .raw_call(CBStorageRequest::DbMapContainsKey {
1012                key: key.as_ref().to_vec(),
1013            })
1014            .await?
1015        {
1016            CBStorageResponse::DbMapContainsKey(b) => Ok(b),
1017            _ => Err(anyhow!("unexpected response type for map_contains_key()")),
1018        }
1019    }
1020
1021    async fn list<V: AsRef<[u8]> + Sync + Send>(
1022        &self,
1023        name: V,
1024        expire: Option<TimestampMillis>,
1025    ) -> Self::ListType {
1026        let raw = self.inner.list(name, expire).await;
1027        CircuitBrokenList::new(raw, Arc::clone(&self.service), self.config.name.clone())
1028    }
1029
1030    async fn list_remove<K>(&self, name: K) -> Result<()>
1031    where
1032        K: AsRef<[u8]> + Sync + Send,
1033    {
1034        self.raw_call(CBStorageRequest::DbListRemove {
1035            name: name.as_ref().to_vec(),
1036        })
1037        .await
1038        .map(|_| ())
1039    }
1040
1041    async fn list_contains_key<K: AsRef<[u8]> + Sync + Send>(&self, key: K) -> Result<bool> {
1042        match self
1043            .raw_call(CBStorageRequest::DbListContainsKey {
1044                key: key.as_ref().to_vec(),
1045            })
1046            .await?
1047        {
1048            CBStorageResponse::DbListContainsKey(b) => Ok(b),
1049            _ => Err(anyhow!("unexpected response type for list_contains_key()")),
1050        }
1051    }
1052
1053    async fn insert<K, V>(&self, key: K, val: &V) -> Result<()>
1054    where
1055        K: AsRef<[u8]> + Sync + Send,
1056        V: Serialize + Sync + Send,
1057    {
1058        let bytes = postcard::to_stdvec(val)?;
1059        self.raw_call(CBStorageRequest::Insert {
1060            key: key.as_ref().to_vec(),
1061            val: bytes,
1062        })
1063        .await
1064        .map(|_| ())
1065    }
1066
1067    async fn get<K, V>(&self, key: K) -> Result<Option<V>>
1068    where
1069        K: AsRef<[u8]> + Sync + Send,
1070        V: DeserializeOwned + Sync + Send,
1071    {
1072        let req = CBStorageRequest::Get {
1073            key: key.as_ref().to_vec(),
1074        };
1075        match self.raw_call(req).await? {
1076            CBStorageResponse::Get(Some(bytes)) => Ok(Some(postcard::from_bytes(&bytes)?)),
1077            CBStorageResponse::Get(None) => Ok(None),
1078            _ => Err(anyhow!("unexpected response type for get()")),
1079        }
1080    }
1081
1082    async fn remove<K>(&self, key: K) -> Result<()>
1083    where
1084        K: AsRef<[u8]> + Sync + Send,
1085    {
1086        self.raw_call(CBStorageRequest::Remove {
1087            key: key.as_ref().to_vec(),
1088        })
1089        .await
1090        .map(|_| ())
1091    }
1092
1093    async fn batch_insert<V>(&self, key_vals: Vec<(Key, V)>) -> Result<()>
1094    where
1095        V: Serialize + Sync + Send,
1096    {
1097        let mut pairs = Vec::with_capacity(key_vals.len());
1098        for (k, v) in key_vals {
1099            pairs.push((k, postcard::to_stdvec(&v)?));
1100        }
1101        self.raw_call(CBStorageRequest::BatchInsert { key_vals: pairs })
1102            .await
1103            .map(|_| ())
1104    }
1105
1106    async fn batch_remove(&self, keys: Vec<Key>) -> Result<()> {
1107        self.raw_call(CBStorageRequest::BatchRemove { keys })
1108            .await
1109            .map(|_| ())
1110    }
1111
1112    async fn counter_incr<K>(&self, key: K, increment: isize) -> Result<()>
1113    where
1114        K: AsRef<[u8]> + Sync + Send,
1115    {
1116        self.raw_call(CBStorageRequest::CounterIncr {
1117            key: key.as_ref().to_vec(),
1118            increment,
1119        })
1120        .await
1121        .map(|_| ())
1122    }
1123
1124    async fn counter_decr<K>(&self, key: K, decrement: isize) -> Result<()>
1125    where
1126        K: AsRef<[u8]> + Sync + Send,
1127    {
1128        self.raw_call(CBStorageRequest::CounterDecr {
1129            key: key.as_ref().to_vec(),
1130            decrement,
1131        })
1132        .await
1133        .map(|_| ())
1134    }
1135
1136    async fn counter_get<K>(&self, key: K) -> Result<Option<isize>>
1137    where
1138        K: AsRef<[u8]> + Sync + Send,
1139    {
1140        match self
1141            .raw_call(CBStorageRequest::CounterGet {
1142                key: key.as_ref().to_vec(),
1143            })
1144            .await?
1145        {
1146            CBStorageResponse::CounterGet(v) => Ok(v),
1147            _ => Err(anyhow!("unexpected response type for counter_get()")),
1148        }
1149    }
1150
1151    async fn counter_set<K>(&self, key: K, val: isize) -> Result<()>
1152    where
1153        K: AsRef<[u8]> + Sync + Send,
1154    {
1155        self.raw_call(CBStorageRequest::CounterSet {
1156            key: key.as_ref().to_vec(),
1157            val,
1158        })
1159        .await
1160        .map(|_| ())
1161    }
1162
1163    async fn contains_key<K: AsRef<[u8]> + Sync + Send>(&self, key: K) -> Result<bool> {
1164        match self
1165            .raw_call(CBStorageRequest::ContainsKey {
1166                key: key.as_ref().to_vec(),
1167            })
1168            .await?
1169        {
1170            CBStorageResponse::ContainsKey(b) => Ok(b),
1171            _ => Err(anyhow!("unexpected response type for contains_key()")),
1172        }
1173    }
1174
1175    #[cfg(feature = "len")]
1176    async fn len(&self) -> Result<usize> {
1177        match self.raw_call(CBStorageRequest::Len).await? {
1178            CBStorageResponse::Len(n) => Ok(n),
1179            _ => Err(anyhow!("unexpected response type for len()")),
1180        }
1181    }
1182
1183    async fn db_size(&self) -> Result<usize> {
1184        match self.raw_call(CBStorageRequest::DbSize).await? {
1185            CBStorageResponse::DbSize(s) => Ok(s),
1186            _ => Err(anyhow!("unexpected response type for db_size()")),
1187        }
1188    }
1189
1190    #[cfg(feature = "ttl")]
1191    async fn expire_at<K>(&self, key: K, at: TimestampMillis) -> Result<bool>
1192    where
1193        K: AsRef<[u8]> + Sync + Send,
1194    {
1195        match self
1196            .raw_call(CBStorageRequest::ExpireAt {
1197                key: key.as_ref().to_vec(),
1198                at,
1199            })
1200            .await?
1201        {
1202            CBStorageResponse::ExpireAt(b) => Ok(b),
1203            _ => Err(anyhow!("unexpected response type for expire_at()")),
1204        }
1205    }
1206
1207    #[cfg(feature = "ttl")]
1208    async fn expire<K>(&self, key: K, dur: TimestampMillis) -> Result<bool>
1209    where
1210        K: AsRef<[u8]> + Sync + Send,
1211    {
1212        match self
1213            .raw_call(CBStorageRequest::Expire {
1214                key: key.as_ref().to_vec(),
1215                dur,
1216            })
1217            .await?
1218        {
1219            CBStorageResponse::Expire(b) => Ok(b),
1220            _ => Err(anyhow!("unexpected response type for expire()")),
1221        }
1222    }
1223
1224    #[cfg(feature = "ttl")]
1225    async fn ttl<K>(&self, key: K) -> Result<Option<TimestampMillis>>
1226    where
1227        K: AsRef<[u8]> + Sync + Send,
1228    {
1229        match self
1230            .raw_call(CBStorageRequest::Ttl {
1231                key: key.as_ref().to_vec(),
1232            })
1233            .await?
1234        {
1235            CBStorageResponse::Ttl(v) => Ok(v),
1236            _ => Err(anyhow!("unexpected response type for ttl()")),
1237        }
1238    }
1239
1240    async fn map_iter<'a>(
1241        &'a mut self,
1242    ) -> Result<Box<dyn AsyncIterator<Item = Result<StorageMap>> + Send + 'a>> {
1243        self.inner.map_iter().await
1244    }
1245
1246    async fn list_iter<'a>(
1247        &'a mut self,
1248    ) -> Result<Box<dyn AsyncIterator<Item = Result<StorageList>> + Send + 'a>> {
1249        self.inner.list_iter().await
1250    }
1251
1252    async fn scan<'a, P>(
1253        &'a mut self,
1254        pattern: P,
1255    ) -> Result<Box<dyn AsyncIterator<Item = Result<Key>> + Send + 'a>>
1256    where
1257        P: AsRef<[u8]> + Send + Sync,
1258    {
1259        self.inner.scan(pattern).await
1260    }
1261
1262    async fn info(&self) -> Result<serde_json::Value> {
1263        let mut base = match self.raw_call(CBStorageRequest::Info).await {
1264            Ok(CBStorageResponse::Info(v)) => v,
1265            Err(e) => serde_json::Value::String(e.to_string()),
1266            _ => return Err(anyhow!("unexpected response type for info()")),
1267        };
1268
1269        if let Some(obj) = base.as_object_mut() {
1270            obj.insert("circuit_breaker".into(), self.cb_info().await);
1271        } else {
1272            let cb_state = self.cb_info().await;
1273            base = serde_json::json!({
1274                "storage": base,
1275                "circuit_breaker": cb_state,
1276            });
1277        }
1278
1279        Ok(base)
1280    }
1281}
1282
1283// ---------------------------------------------------------------------------
1284// CircuitBrokenMap — public wrapper
1285// ---------------------------------------------------------------------------
1286
1287/// A circuit-breaker-protected wrapper around [`StorageMap`].
1288///
1289/// All operations funnel through the **shared DB-level** `CircuitBreaker`,
1290/// so Map/List/DB share a single failure metric.  Clone per-call (cheap).
1291///
1292/// Iterator methods (`iter`, `key_iter`, `prefix_iter`) bypass the breaker
1293/// and operate directly on the inner `StorageMap`.
1294pub struct CircuitBrokenMap {
1295    /// Reference to the shared DB-level circuit breaker.
1296    breaker: SharedBreaker,
1297    /// Raw inner map for name() and iterator methods that bypass the CB.
1298    inner: StorageMap,
1299    /// Breaker name for error messages.
1300    name: String,
1301}
1302
1303impl Clone for CircuitBrokenMap {
1304    fn clone(&self) -> Self {
1305        Self {
1306            breaker: Arc::clone(&self.breaker),
1307            inner: self.inner.clone(),
1308            name: self.name.clone(),
1309        }
1310    }
1311}
1312
1313impl CircuitBrokenMap {
1314    fn new(m: StorageMap, breaker: SharedBreaker, name: String) -> Self {
1315        Self {
1316            breaker,
1317            inner: m,
1318            name,
1319        }
1320    }
1321}
1322
1323// ---------------------------------------------------------------------------
1324
1325#[async_trait]
1326impl Map for CircuitBrokenMap {
1327    fn name(&self) -> &[u8] {
1328        self.inner.name()
1329    }
1330
1331    async fn insert<K, V>(&self, key: K, val: &V) -> Result<()>
1332    where
1333        K: AsRef<[u8]> + Sync + Send,
1334        V: Serialize + Sync + Send + ?Sized,
1335    {
1336        let bytes = postcard::to_stdvec(val)?;
1337        let mut svc = self.breaker.as_ref().clone();
1338        svc.call(CBStorageRequest::MapInsert {
1339            map: self.inner.clone(),
1340            key: key.as_ref().to_vec(),
1341            val: bytes,
1342        })
1343        .await
1344        .map_err(|_| {
1345            anyhow!(
1346                "storage unavailable (circuit breaker open for '{}')",
1347                self.name
1348            )
1349        })?;
1350        Ok(())
1351    }
1352
1353    async fn get<K, V>(&self, key: K) -> Result<Option<V>>
1354    where
1355        K: AsRef<[u8]> + Sync + Send,
1356        V: DeserializeOwned + Sync + Send,
1357    {
1358        let mut svc = self.breaker.as_ref().clone();
1359        let resp = svc
1360            .call(CBStorageRequest::MapGet {
1361                map: self.inner.clone(),
1362                key: key.as_ref().to_vec(),
1363            })
1364            .await
1365            .map_err(|_| {
1366                anyhow!(
1367                    "storage unavailable (circuit breaker open for '{}')",
1368                    self.name
1369                )
1370            })?;
1371        match resp {
1372            CBStorageResponse::MapGet(Some(bytes)) => Ok(Some(postcard::from_bytes(&bytes)?)),
1373            CBStorageResponse::MapGet(None) => Ok(None),
1374            _ => Err(anyhow!("unexpected response type for map::get()")),
1375        }
1376    }
1377
1378    async fn remove<K>(&self, key: K) -> Result<()>
1379    where
1380        K: AsRef<[u8]> + Sync + Send,
1381    {
1382        let mut svc = self.breaker.as_ref().clone();
1383        svc.call(CBStorageRequest::MapRemove {
1384            map: self.inner.clone(),
1385            key: key.as_ref().to_vec(),
1386        })
1387        .await
1388        .map_err(|_| {
1389            anyhow!(
1390                "storage unavailable (circuit breaker open for '{}')",
1391                self.name
1392            )
1393        })?;
1394        Ok(())
1395    }
1396
1397    async fn contains_key<K: AsRef<[u8]> + Sync + Send>(&self, key: K) -> Result<bool> {
1398        let mut svc = self.breaker.as_ref().clone();
1399        let resp = svc
1400            .call(CBStorageRequest::MapContainsKey {
1401                map: self.inner.clone(),
1402                key: key.as_ref().to_vec(),
1403            })
1404            .await
1405            .map_err(|_| {
1406                anyhow!(
1407                    "storage unavailable (circuit breaker open for '{}')",
1408                    self.name
1409                )
1410            })?;
1411        match resp {
1412            CBStorageResponse::MapContainsKey(b) => Ok(b),
1413            _ => Err(anyhow!("unexpected response type for map::contains_key()")),
1414        }
1415    }
1416
1417    #[cfg(feature = "map_len")]
1418    async fn len(&self) -> Result<usize> {
1419        let mut svc = self.breaker.as_ref().clone();
1420        let resp = svc
1421            .call(CBStorageRequest::MapLen {
1422                map: self.inner.clone(),
1423            })
1424            .await
1425            .map_err(|_| {
1426                anyhow!(
1427                    "storage unavailable (circuit breaker open for '{}')",
1428                    self.name
1429                )
1430            })?;
1431        match resp {
1432            CBStorageResponse::MapLen(n) => Ok(n),
1433            _ => Err(anyhow!("unexpected response type for map::len()")),
1434        }
1435    }
1436
1437    async fn is_empty(&self) -> Result<bool> {
1438        let mut svc = self.breaker.as_ref().clone();
1439        let resp = svc
1440            .call(CBStorageRequest::MapIsEmpty {
1441                map: self.inner.clone(),
1442            })
1443            .await
1444            .map_err(|_| {
1445                anyhow!(
1446                    "storage unavailable (circuit breaker open for '{}')",
1447                    self.name
1448                )
1449            })?;
1450        match resp {
1451            CBStorageResponse::MapIsEmpty(b) => Ok(b),
1452            _ => Err(anyhow!("unexpected response type for map::is_empty()")),
1453        }
1454    }
1455
1456    async fn clear(&self) -> Result<()> {
1457        let mut svc = self.breaker.as_ref().clone();
1458        svc.call(CBStorageRequest::MapClear {
1459            map: self.inner.clone(),
1460        })
1461        .await
1462        .map_err(|_| {
1463            anyhow!(
1464                "storage unavailable (circuit breaker open for '{}')",
1465                self.name
1466            )
1467        })?;
1468        Ok(())
1469    }
1470
1471    async fn remove_and_fetch<K, V>(&self, key: K) -> Result<Option<V>>
1472    where
1473        K: AsRef<[u8]> + Sync + Send,
1474        V: DeserializeOwned + Sync + Send,
1475    {
1476        let mut svc = self.breaker.as_ref().clone();
1477        let resp = svc
1478            .call(CBStorageRequest::MapRemoveAndFetch {
1479                map: self.inner.clone(),
1480                key: key.as_ref().to_vec(),
1481            })
1482            .await
1483            .map_err(|_| {
1484                anyhow!(
1485                    "storage unavailable (circuit breaker open for '{}')",
1486                    self.name
1487                )
1488            })?;
1489        match resp {
1490            CBStorageResponse::MapRemoveAndFetch(Some(bytes)) => {
1491                Ok(Some(postcard::from_bytes(&bytes)?))
1492            }
1493            CBStorageResponse::MapRemoveAndFetch(None) => Ok(None),
1494            _ => Err(anyhow!(
1495                "unexpected response type for map::remove_and_fetch()"
1496            )),
1497        }
1498    }
1499
1500    async fn remove_with_prefix<K>(&self, prefix: K) -> Result<()>
1501    where
1502        K: AsRef<[u8]> + Sync + Send,
1503    {
1504        let mut svc = self.breaker.as_ref().clone();
1505        svc.call(CBStorageRequest::MapRemoveWithPrefix {
1506            map: self.inner.clone(),
1507            prefix: prefix.as_ref().to_vec(),
1508        })
1509        .await
1510        .map_err(|_| {
1511            anyhow!(
1512                "storage unavailable (circuit breaker open for '{}')",
1513                self.name
1514            )
1515        })?;
1516        Ok(())
1517    }
1518
1519    async fn batch_insert<V>(&self, key_vals: Vec<(Key, V)>) -> Result<()>
1520    where
1521        V: Serialize + Sync + Send,
1522    {
1523        let mut pairs = Vec::with_capacity(key_vals.len());
1524        for (k, v) in key_vals {
1525            pairs.push((k, postcard::to_stdvec(&v)?));
1526        }
1527        let mut svc = self.breaker.as_ref().clone();
1528        svc.call(CBStorageRequest::MapBatchInsert {
1529            map: self.inner.clone(),
1530            key_vals: pairs,
1531        })
1532        .await
1533        .map_err(|_| {
1534            anyhow!(
1535                "storage unavailable (circuit breaker open for '{}')",
1536                self.name
1537            )
1538        })?;
1539        Ok(())
1540    }
1541
1542    async fn batch_remove(&self, keys: Vec<Key>) -> Result<()> {
1543        let mut svc = self.breaker.as_ref().clone();
1544        svc.call(CBStorageRequest::MapBatchRemove {
1545            map: self.inner.clone(),
1546            keys,
1547        })
1548        .await
1549        .map_err(|_| {
1550            anyhow!(
1551                "storage unavailable (circuit breaker open for '{}')",
1552                self.name
1553            )
1554        })?;
1555        Ok(())
1556    }
1557
1558    // ---- pass-through: iterator methods bypass CB ----
1559    async fn iter<'a, V>(
1560        &'a mut self,
1561    ) -> Result<Box<dyn AsyncIterator<Item = IterItem<V>> + Send + 'a>>
1562    where
1563        V: DeserializeOwned + Sync + Send + 'a + 'static,
1564    {
1565        self.inner.iter().await
1566    }
1567
1568    async fn key_iter<'a>(
1569        &'a mut self,
1570    ) -> Result<Box<dyn AsyncIterator<Item = Result<Key>> + Send + 'a>> {
1571        self.inner.key_iter().await
1572    }
1573
1574    async fn prefix_iter<'a, P, V>(
1575        &'a mut self,
1576        prefix: P,
1577    ) -> Result<Box<dyn AsyncIterator<Item = IterItem<V>> + Send + 'a>>
1578    where
1579        P: AsRef<[u8]> + Send + Sync,
1580        V: DeserializeOwned + Sync + Send + 'a + 'static,
1581    {
1582        self.inner.prefix_iter(prefix).await
1583    }
1584
1585    #[cfg(feature = "ttl")]
1586    async fn expire_at(&self, at: TimestampMillis) -> Result<bool> {
1587        let mut svc = self.breaker.as_ref().clone();
1588        let resp = svc
1589            .call(CBStorageRequest::MapExpireAt {
1590                map: self.inner.clone(),
1591                at,
1592            })
1593            .await
1594            .map_err(|_| {
1595                anyhow!(
1596                    "storage unavailable (circuit breaker open for '{}')",
1597                    self.name
1598                )
1599            })?;
1600        match resp {
1601            CBStorageResponse::MapExpireAt(b) => Ok(b),
1602            _ => Err(anyhow!("unexpected response type for map::expire_at()")),
1603        }
1604    }
1605
1606    #[cfg(feature = "ttl")]
1607    async fn expire(&self, dur: TimestampMillis) -> Result<bool> {
1608        let mut svc = self.breaker.as_ref().clone();
1609        let resp = svc
1610            .call(CBStorageRequest::MapExpire {
1611                map: self.inner.clone(),
1612                dur,
1613            })
1614            .await
1615            .map_err(|_| {
1616                anyhow!(
1617                    "storage unavailable (circuit breaker open for '{}')",
1618                    self.name
1619                )
1620            })?;
1621        match resp {
1622            CBStorageResponse::MapExpire(b) => Ok(b),
1623            _ => Err(anyhow!("unexpected response type for map::expire()")),
1624        }
1625    }
1626
1627    #[cfg(feature = "ttl")]
1628    async fn ttl(&self) -> Result<Option<TimestampMillis>> {
1629        let mut svc = self.breaker.as_ref().clone();
1630        let resp = svc
1631            .call(CBStorageRequest::MapTtl {
1632                map: self.inner.clone(),
1633            })
1634            .await
1635            .map_err(|_| {
1636                anyhow!(
1637                    "storage unavailable (circuit breaker open for '{}')",
1638                    self.name
1639                )
1640            })?;
1641        match resp {
1642            CBStorageResponse::MapTtl(v) => Ok(v),
1643            _ => Err(anyhow!("unexpected response type for map::ttl()")),
1644        }
1645    }
1646}
1647
1648// ---------------------------------------------------------------------------
1649// CircuitBrokenList — public wrapper
1650// ---------------------------------------------------------------------------
1651
1652/// A circuit-breaker-protected wrapper around [`StorageList`].
1653///
1654/// All operations funnel through the **shared DB-level** `CircuitBreaker`,
1655/// so Map/List/DB share a single failure metric.  Clone per-call (cheap).
1656///
1657/// Iterator methods (`iter`) bypass the breaker and operate directly on
1658/// the inner `StorageList`.
1659pub struct CircuitBrokenList {
1660    /// Reference to the shared DB-level circuit breaker.
1661    breaker: SharedBreaker,
1662    /// Raw inner list for name() and iter() that bypass the CB.
1663    inner: StorageList,
1664    /// Breaker name for error messages & identifying the list in requests.
1665    name: String,
1666}
1667
1668impl Clone for CircuitBrokenList {
1669    fn clone(&self) -> Self {
1670        Self {
1671            breaker: Arc::clone(&self.breaker),
1672            inner: self.inner.clone(),
1673            name: self.name.clone(),
1674        }
1675    }
1676}
1677
1678impl CircuitBrokenList {
1679    fn new(l: StorageList, breaker: SharedBreaker, name: String) -> Self {
1680        Self {
1681            breaker,
1682            inner: l,
1683            name,
1684        }
1685    }
1686}
1687
1688// ---------------------------------------------------------------------------
1689
1690#[async_trait]
1691impl List for CircuitBrokenList {
1692    fn name(&self) -> &[u8] {
1693        self.inner.name()
1694    }
1695
1696    async fn push<V>(&self, val: &V) -> Result<()>
1697    where
1698        V: Serialize + Sync + Send,
1699    {
1700        let bytes = postcard::to_stdvec(val)?;
1701        let mut svc = self.breaker.as_ref().clone();
1702        svc.call(CBStorageRequest::ListPush {
1703            list: self.inner.clone(),
1704            val: bytes,
1705        })
1706        .await
1707        .map_err(|_| {
1708            anyhow!(
1709                "storage unavailable (circuit breaker open for '{}')",
1710                self.name
1711            )
1712        })?;
1713        Ok(())
1714    }
1715
1716    async fn pushs<V>(&self, vals: Vec<V>) -> Result<()>
1717    where
1718        V: Serialize + Sync + Send,
1719    {
1720        let mut bytes_vec = Vec::with_capacity(vals.len());
1721        for v in vals {
1722            bytes_vec.push(postcard::to_stdvec(&v)?);
1723        }
1724        let mut svc = self.breaker.as_ref().clone();
1725        svc.call(CBStorageRequest::ListPushs {
1726            list: self.inner.clone(),
1727            vals: bytes_vec,
1728        })
1729        .await
1730        .map_err(|_| {
1731            anyhow!(
1732                "storage unavailable (circuit breaker open for '{}')",
1733                self.name
1734            )
1735        })?;
1736        Ok(())
1737    }
1738
1739    async fn push_limit<V>(
1740        &self,
1741        val: &V,
1742        limit: usize,
1743        pop_front_if_limited: bool,
1744    ) -> Result<Option<V>>
1745    where
1746        V: Serialize + Sync + Send + DeserializeOwned,
1747    {
1748        let bytes = postcard::to_stdvec(val)?;
1749        let mut svc = self.breaker.as_ref().clone();
1750        let resp = svc
1751            .call(CBStorageRequest::ListPushLimit {
1752                list: self.inner.clone(),
1753                val: bytes,
1754                limit,
1755                pop_front_if_limited,
1756            })
1757            .await
1758            .map_err(|_| {
1759                anyhow!(
1760                    "storage unavailable (circuit breaker open for '{}')",
1761                    self.name
1762                )
1763            })?;
1764        match resp {
1765            CBStorageResponse::ListPushLimit(Some(popped)) => {
1766                Ok(Some(postcard::from_bytes(&popped)?))
1767            }
1768            CBStorageResponse::ListPushLimit(None) => Ok(None),
1769            _ => Err(anyhow!("unexpected response type for list::push_limit()")),
1770        }
1771    }
1772
1773    async fn pop<V>(&self) -> Result<Option<V>>
1774    where
1775        V: DeserializeOwned + Sync + Send,
1776    {
1777        let mut svc = self.breaker.as_ref().clone();
1778        let resp = svc
1779            .call(CBStorageRequest::ListPop {
1780                list: self.inner.clone(),
1781            })
1782            .await
1783            .map_err(|_| {
1784                anyhow!(
1785                    "storage unavailable (circuit breaker open for '{}')",
1786                    self.name
1787                )
1788            })?;
1789        match resp {
1790            CBStorageResponse::ListPop(Some(bytes)) => Ok(Some(postcard::from_bytes(&bytes)?)),
1791            CBStorageResponse::ListPop(None) => Ok(None),
1792            _ => Err(anyhow!("unexpected response type for list::pop()")),
1793        }
1794    }
1795
1796    async fn all<V>(&self) -> Result<Vec<V>>
1797    where
1798        V: DeserializeOwned + Sync + Send,
1799    {
1800        let mut svc = self.breaker.as_ref().clone();
1801        let resp = svc
1802            .call(CBStorageRequest::ListGetAll {
1803                list: self.inner.clone(),
1804            })
1805            .await
1806            .map_err(|_| {
1807                anyhow!(
1808                    "storage unavailable (circuit breaker open for '{}')",
1809                    self.name
1810                )
1811            })?;
1812        match resp {
1813            CBStorageResponse::ListGetAll(vals) => vals
1814                .into_iter()
1815                .map(|bytes| postcard::from_bytes(&bytes).map_err(Into::into))
1816                .collect(),
1817            _ => Err(anyhow!("unexpected response type for list::all()")),
1818        }
1819    }
1820
1821    async fn get_index<V>(&self, idx: usize) -> Result<Option<V>>
1822    where
1823        V: DeserializeOwned + Sync + Send,
1824    {
1825        let mut svc = self.breaker.as_ref().clone();
1826        let resp = svc
1827            .call(CBStorageRequest::ListGetIndex {
1828                list: self.inner.clone(),
1829                idx,
1830            })
1831            .await
1832            .map_err(|_| {
1833                anyhow!(
1834                    "storage unavailable (circuit breaker open for '{}')",
1835                    self.name
1836                )
1837            })?;
1838        match resp {
1839            CBStorageResponse::ListGetIndex(Some(bytes)) => Ok(Some(postcard::from_bytes(&bytes)?)),
1840            CBStorageResponse::ListGetIndex(None) => Ok(None),
1841            _ => Err(anyhow!("unexpected response type for list::get_index()")),
1842        }
1843    }
1844
1845    async fn len(&self) -> Result<usize> {
1846        let mut svc = self.breaker.as_ref().clone();
1847        let resp = svc
1848            .call(CBStorageRequest::ListLen {
1849                list: self.inner.clone(),
1850            })
1851            .await
1852            .map_err(|_| {
1853                anyhow!(
1854                    "storage unavailable (circuit breaker open for '{}')",
1855                    self.name
1856                )
1857            })?;
1858        match resp {
1859            CBStorageResponse::ListLen(n) => Ok(n),
1860            _ => Err(anyhow!("unexpected response type for list::len()")),
1861        }
1862    }
1863
1864    async fn is_empty(&self) -> Result<bool> {
1865        let mut svc = self.breaker.as_ref().clone();
1866        let resp = svc
1867            .call(CBStorageRequest::ListIsEmpty {
1868                list: self.inner.clone(),
1869            })
1870            .await
1871            .map_err(|_| {
1872                anyhow!(
1873                    "storage unavailable (circuit breaker open for '{}')",
1874                    self.name
1875                )
1876            })?;
1877        match resp {
1878            CBStorageResponse::ListIsEmpty(b) => Ok(b),
1879            _ => Err(anyhow!("unexpected response type for list::is_empty()")),
1880        }
1881    }
1882
1883    async fn clear(&self) -> Result<()> {
1884        let mut svc = self.breaker.as_ref().clone();
1885        svc.call(CBStorageRequest::ListClear {
1886            list: self.inner.clone(),
1887        })
1888        .await
1889        .map_err(|_| {
1890            anyhow!(
1891                "storage unavailable (circuit breaker open for '{}')",
1892                self.name
1893            )
1894        })?;
1895        Ok(())
1896    }
1897
1898    // ---- pass-through: iterator bypasses CB ----
1899    async fn iter<'a, V>(
1900        &'a mut self,
1901    ) -> Result<Box<dyn AsyncIterator<Item = Result<V>> + Send + 'a>>
1902    where
1903        V: DeserializeOwned + Sync + Send + 'a + 'static,
1904    {
1905        self.inner.iter().await
1906    }
1907
1908    #[cfg(feature = "ttl")]
1909    async fn expire_at(&self, at: TimestampMillis) -> Result<bool> {
1910        let mut svc = self.breaker.as_ref().clone();
1911        let resp = svc
1912            .call(CBStorageRequest::ListExpireAt {
1913                list: self.inner.clone(),
1914                at,
1915            })
1916            .await
1917            .map_err(|_| {
1918                anyhow!(
1919                    "storage unavailable (circuit breaker open for '{}')",
1920                    self.name
1921                )
1922            })?;
1923        match resp {
1924            CBStorageResponse::ListExpireAt(b) => Ok(b),
1925            _ => Err(anyhow!("unexpected response type for list::expire_at()")),
1926        }
1927    }
1928
1929    #[cfg(feature = "ttl")]
1930    async fn expire(&self, dur: TimestampMillis) -> Result<bool> {
1931        let mut svc = self.breaker.as_ref().clone();
1932        let resp = svc
1933            .call(CBStorageRequest::ListExpire {
1934                list: self.inner.clone(),
1935                dur,
1936            })
1937            .await
1938            .map_err(|_| {
1939                anyhow!(
1940                    "storage unavailable (circuit breaker open for '{}')",
1941                    self.name
1942                )
1943            })?;
1944        match resp {
1945            CBStorageResponse::ListExpire(b) => Ok(b),
1946            _ => Err(anyhow!("unexpected response type for list::expire()")),
1947        }
1948    }
1949
1950    #[cfg(feature = "ttl")]
1951    async fn ttl(&self) -> Result<Option<TimestampMillis>> {
1952        let mut svc = self.breaker.as_ref().clone();
1953        let resp = svc
1954            .call(CBStorageRequest::ListTtl {
1955                list: self.inner.clone(),
1956            })
1957            .await
1958            .map_err(|_| {
1959                anyhow!(
1960                    "storage unavailable (circuit breaker open for '{}')",
1961                    self.name
1962                )
1963            })?;
1964        match resp {
1965            CBStorageResponse::ListTtl(v) => Ok(v),
1966            _ => Err(anyhow!("unexpected response type for list::ttl()")),
1967        }
1968    }
1969}
1970
1971// =========================================================================
1972// Tests
1973// =========================================================================
1974
1975#[cfg(test)]
1976mod cb_unit_tests {
1977    use std::pin::Pin;
1978    use std::sync::atomic::{AtomicU64, Ordering};
1979    use std::sync::Arc;
1980    use std::task::{Context, Poll};
1981    use std::time::Duration;
1982
1983    use anyhow::anyhow;
1984    use tower::Service;
1985
1986    use super::*;
1987    use crate::Result;
1988
1989    // -----------------------------------------------------------------------
1990    // Mock service: fails first `fail_until` calls, then succeeds
1991    // -----------------------------------------------------------------------
1992
1993    #[derive(Clone)]
1994    struct MockSvc {
1995        /// First `fail_until` calls return Err; calls at or after return Ok.
1996        fail_until: u64,
1997        /// Total calls made (shared across clones).
1998        call_count: Arc<AtomicU64>,
1999    }
2000
2001    impl MockSvc {
2002        fn always_ok() -> Self {
2003            Self {
2004                fail_until: 0,
2005                call_count: Arc::new(AtomicU64::new(0)),
2006            }
2007        }
2008        fn fail_for(n: u64) -> Self {
2009            Self {
2010                fail_until: n,
2011                call_count: Arc::new(AtomicU64::new(0)),
2012            }
2013        }
2014    }
2015
2016    impl Service<CBStorageRequest> for MockSvc {
2017        type Response = CBStorageResponse;
2018        type Error = anyhow::Error;
2019        type Future = Pin<Box<dyn std::future::Future<Output = Result<Self::Response>> + Send>>;
2020
2021        fn poll_ready(
2022            &mut self,
2023            _cx: &mut Context<'_>,
2024        ) -> Poll<std::result::Result<(), Self::Error>> {
2025            Poll::Ready(Ok(()))
2026        }
2027
2028        fn call(&mut self, _req: CBStorageRequest) -> Self::Future {
2029            let count = self.call_count.fetch_add(1, Ordering::SeqCst);
2030            let fail_until = self.fail_until;
2031            Box::pin(async move {
2032                if count < fail_until {
2033                    Err(anyhow!("mock failure (call {})", count))
2034                } else {
2035                    Ok(CBStorageResponse::Insert)
2036                }
2037            })
2038        }
2039    }
2040
2041    // -----------------------------------------------------------------------
2042    // Map-level mock (uses CBStorageRequest for uniform testing)
2043    // -----------------------------------------------------------------------
2044
2045    #[derive(Clone)]
2046    struct MockMapSvc {
2047        fail_until: u64,
2048        call_count: Arc<AtomicU64>,
2049    }
2050
2051    impl MockMapSvc {
2052        fn fail_for(n: u64) -> Self {
2053            Self {
2054                fail_until: n,
2055                call_count: Arc::new(AtomicU64::new(0)),
2056            }
2057        }
2058    }
2059
2060    impl Service<CBStorageRequest> for MockMapSvc {
2061        type Response = CBStorageResponse;
2062        type Error = anyhow::Error;
2063        type Future = Pin<Box<dyn std::future::Future<Output = Result<Self::Response>> + Send>>;
2064
2065        fn poll_ready(
2066            &mut self,
2067            _cx: &mut Context<'_>,
2068        ) -> Poll<std::result::Result<(), Self::Error>> {
2069            Poll::Ready(Ok(()))
2070        }
2071
2072        fn call(&mut self, _req: CBStorageRequest) -> Self::Future {
2073            let count = self.call_count.fetch_add(1, Ordering::SeqCst);
2074            let fail_until = self.fail_until;
2075            Box::pin(async move {
2076                if count < fail_until {
2077                    Err(anyhow!("mock map failure"))
2078                } else {
2079                    Ok(CBStorageResponse::MapIsEmpty(true))
2080                }
2081            })
2082        }
2083    }
2084
2085    // -----------------------------------------------------------------------
2086    // List-level mock
2087    // -----------------------------------------------------------------------
2088
2089    #[derive(Clone)]
2090    struct MockListSvc {
2091        fail_until: u64,
2092        call_count: Arc<AtomicU64>,
2093    }
2094
2095    impl MockListSvc {
2096        fn fail_for(n: u64) -> Self {
2097            Self {
2098                fail_until: n,
2099                call_count: Arc::new(AtomicU64::new(0)),
2100            }
2101        }
2102    }
2103
2104    impl Service<CBStorageRequest> for MockListSvc {
2105        type Response = CBStorageResponse;
2106        type Error = anyhow::Error;
2107        type Future = Pin<Box<dyn std::future::Future<Output = Result<Self::Response>> + Send>>;
2108
2109        fn poll_ready(
2110            &mut self,
2111            _cx: &mut Context<'_>,
2112        ) -> Poll<std::result::Result<(), Self::Error>> {
2113            Poll::Ready(Ok(()))
2114        }
2115
2116        fn call(&mut self, _req: CBStorageRequest) -> Self::Future {
2117            let count = self.call_count.fetch_add(1, Ordering::SeqCst);
2118            let fail_until = self.fail_until;
2119            Box::pin(async move {
2120                if count < fail_until {
2121                    Err(anyhow!("mock list failure"))
2122                } else {
2123                    Ok(CBStorageResponse::ListLen(0))
2124                }
2125            })
2126        }
2127    }
2128
2129    // -----------------------------------------------------------------------
2130    // Helpers
2131    // -----------------------------------------------------------------------
2132
2133    /// Aggressive config: trips after 3 calls with ≥50% failure.
2134    fn fast_cb_config(name: &str) -> CircuitBreakerConfig {
2135        CircuitBreakerConfig {
2136            name: name.into(),
2137            failure_rate_threshold: 0.5,
2138            window: CountBasedWindowConfig {
2139                sliding_window_size: 3,
2140            }
2141            .into(),
2142            minimum_number_of_calls: 3,
2143            wait_duration_in_open: Duration::from_millis(200),
2144            slow_call_duration_threshold: Duration::from_secs(1),
2145            slow_call_rate_threshold: 1.0,
2146            operation_timeout: None,
2147        }
2148    }
2149
2150    // -----------------------------------------------------------------------
2151    // Tests — DB level
2152    // -----------------------------------------------------------------------
2153
2154    #[tokio::test]
2155    async fn test_ok_calls_pass_through() {
2156        let mock = MockSvc::always_ok();
2157        let cfg = fast_cb_config("pass_through");
2158        let mut cb = build_cb_inner(mock, &cfg, &cfg.name);
2159
2160        for i in 0..10 {
2161            let req = CBStorageRequest::Get {
2162                key: format!("k{}", i).into_bytes(),
2163            };
2164            let resp = cb.ready().await.unwrap().call(req).await;
2165            assert!(resp.is_ok(), "call {} should succeed", i);
2166        }
2167    }
2168
2169    #[tokio::test]
2170    async fn test_cb_propagates_inner_errors() {
2171        let mock = MockSvc::fail_for(5);
2172        let cfg = fast_cb_config("propagate_errs");
2173        let mut cb = build_cb_inner(mock, &cfg, &cfg.name);
2174
2175        let req = CBStorageRequest::Get { key: b"x".to_vec() };
2176        let resp = cb.ready().await.unwrap().call(req).await;
2177        assert!(resp.is_err(), "inner error should propagate");
2178        let err = resp.unwrap_err().to_string();
2179        assert!(err.contains("mock failure"), "unexpected error: {}", err);
2180    }
2181
2182    // -----------------------------------------------------------------------
2183    // Tests — Map level
2184    // -----------------------------------------------------------------------
2185
2186    #[tokio::test]
2187    async fn test_cb_map_propagates_errors() {
2188        let mock = MockMapSvc::fail_for(5);
2189        let cfg = fast_cb_config("map_errs");
2190        let mut cb = build_cb_inner(mock, &cfg, &cfg.name);
2191
2192        let resp = cb
2193            .ready()
2194            .await
2195            .unwrap()
2196            .call(CBStorageRequest::DbSize)
2197            .await;
2198        assert!(resp.is_err(), "map error should propagate");
2199    }
2200
2201    // -----------------------------------------------------------------------
2202    // Tests — List level
2203    // -----------------------------------------------------------------------
2204
2205    #[tokio::test]
2206    async fn test_cb_list_propagates_errors() {
2207        let mock = MockListSvc::fail_for(5);
2208        let cfg = fast_cb_config("list_errs");
2209        let mut cb = build_cb_inner(mock, &cfg, &cfg.name);
2210
2211        let resp = cb
2212            .ready()
2213            .await
2214            .unwrap()
2215            .call(CBStorageRequest::DbSize)
2216            .await;
2217        assert!(resp.is_err(), "list error should propagate");
2218    }
2219}