key_vault/monitor/composite.rs
1//! [`CompositeMonitor`] — fan-out across several `SecurityMonitor` impls.
2
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5use core::fmt;
6
7use super::{AccessContext, FailureContext, SecurityMonitor, ThresholdContext};
8
9/// `SecurityMonitor` that fans every event out to a list of inner
10/// monitors.
11///
12/// Useful when you want, for example, a `LogMonitor` for human-readable
13/// alerts and a custom `MetricsMonitor` for dashboards from the same
14/// vault. Inner monitors are called in registration order; one failing
15/// monitor does not affect the others (monitor implementations should
16/// not panic per the trait contract).
17///
18/// # Examples
19///
20/// ```
21/// use std::sync::Arc;
22/// use key_vault::{CompositeMonitor, NoMonitor, SecurityMonitor};
23///
24/// let composite = CompositeMonitor::new(vec![
25/// Arc::new(NoMonitor) as Arc<dyn SecurityMonitor>,
26/// Arc::new(NoMonitor) as Arc<dyn SecurityMonitor>,
27/// ]);
28/// assert_eq!(composite.len(), 2);
29/// ```
30#[derive(Clone)]
31pub struct CompositeMonitor {
32 inner: Vec<Arc<dyn SecurityMonitor>>,
33}
34
35impl CompositeMonitor {
36 /// Construct a composite over the supplied inner monitors. An empty
37 /// list is permitted and yields a no-op monitor (effectively the same
38 /// as [`NoMonitor`](super::NoMonitor)).
39 #[must_use]
40 pub fn new(inner: Vec<Arc<dyn SecurityMonitor>>) -> Self {
41 Self { inner }
42 }
43
44 /// Number of inner monitors.
45 #[must_use]
46 pub fn len(&self) -> usize {
47 self.inner.len()
48 }
49
50 /// `true` if no inner monitors are registered.
51 #[must_use]
52 pub fn is_empty(&self) -> bool {
53 self.inner.is_empty()
54 }
55}
56
57impl fmt::Debug for CompositeMonitor {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.debug_struct("CompositeMonitor")
60 .field("inner_count", &self.inner.len())
61 .finish()
62 }
63}
64
65impl SecurityMonitor for CompositeMonitor {
66 fn on_decryption_failure(&self, ctx: &FailureContext) {
67 for m in &self.inner {
68 m.on_decryption_failure(ctx);
69 }
70 }
71
72 fn on_anomalous_access(&self, ctx: &AccessContext) {
73 for m in &self.inner {
74 m.on_anomalous_access(ctx);
75 }
76 }
77
78 fn on_threshold_breach(&self, ctx: &ThresholdContext) {
79 for m in &self.inner {
80 m.on_threshold_breach(ctx);
81 }
82 }
83}