helm_schema_k8s/diagnostic/sink.rs
1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex};
3
4use super::diagnostic::{Diagnostic, DiagnosticKey};
5
6/// Thread-safe sink for [`Diagnostic`] events keyed by
7/// [`DiagnosticKey`]. Uses `BTreeMap` so iteration order is
8/// deterministic (driven by `DiagnosticKey`'s `Ord`) independent of
9/// insertion order. First writer per key wins; canonicalisation at
10/// insertion time means payloads for the same key are identical by
11/// construction.
12#[derive(Debug, Default, Clone)]
13pub struct DiagnosticSink {
14 inner: Arc<Mutex<BTreeMap<DiagnosticKey, Diagnostic>>>,
15}
16
17impl DiagnosticSink {
18 /// Creates an empty deterministic diagnostic sink.
19 #[must_use]
20 pub fn new() -> Self {
21 Self::default()
22 }
23
24 /// Insert a diagnostic if no diagnostic with the same key has been
25 /// inserted yet. Canonicalises the payload before insertion so
26 /// later equality checks are stable.
27 pub fn push(&self, mut diagnostic: Diagnostic) {
28 diagnostic.canonicalise();
29 if let Ok(mut guard) = self.inner.lock() {
30 guard.entry(diagnostic.key()).or_insert(diagnostic);
31 }
32 }
33
34 /// Run a closure over the `BTreeMap` iterator. Held under the mutex
35 /// for the duration of the closure; keep it short.
36 pub fn for_each<F: FnMut(&Diagnostic)>(&self, mut f: F) {
37 if let Ok(guard) = self.inner.lock() {
38 for diagnostic in guard.values() {
39 f(diagnostic);
40 }
41 }
42 }
43
44 /// Snapshot the diagnostics as a new `Vec`. Useful for tests; in
45 /// hot paths prefer `for_each`.
46 #[must_use]
47 pub fn snapshot(&self) -> Vec<Diagnostic> {
48 self.inner
49 .lock()
50 .map(|g| g.values().cloned().collect())
51 .unwrap_or_default()
52 }
53
54 /// Number of distinct diagnostic keys currently held.
55 #[must_use]
56 pub fn len(&self) -> usize {
57 self.inner.lock().map(|g| g.len()).unwrap_or(0)
58 }
59
60 /// True when no diagnostics have been emitted.
61 #[must_use]
62 pub fn is_empty(&self) -> bool {
63 self.len() == 0
64 }
65}