Skip to main content

audit_trail/sinks/
memory.rs

1//! In-memory reference [`Sink`]. Requires the `alloc` feature.
2
3use alloc::vec::Vec;
4
5use crate::error::SinkError;
6use crate::owned::OwnedRecord;
7use crate::record::Record;
8use crate::sink::Sink;
9
10/// In-memory [`Sink`]: appends every record into a `Vec<OwnedRecord>`.
11///
12/// Intended for tests, prototypes, and short-lived buffering. Holds the
13/// entire chain in memory, so it is not suitable for long-running
14/// production workloads — use a file-backed or streaming sink instead.
15///
16/// # Example
17///
18/// ```
19/// use audit_trail::{
20///     Action, Actor, Chain, Clock, Digest, Hasher, MemorySink, Outcome, Record, RecordId,
21///     SinkError, Target, Timestamp, HASH_LEN,
22/// };
23///
24/// // Minimal hasher / clock for the example.
25/// struct XorHasher([u8; HASH_LEN], usize);
26/// impl Hasher for XorHasher {
27///     fn reset(&mut self) { self.0 = [0u8; HASH_LEN]; self.1 = 0; }
28///     fn update(&mut self, bytes: &[u8]) {
29///         for b in bytes { self.0[self.1 % HASH_LEN] ^= *b; self.1 = self.1.wrapping_add(1); }
30///     }
31///     fn finalize(&mut self, out: &mut Digest) { *out = Digest::from_bytes(self.0); }
32/// }
33/// struct Tick(core::cell::Cell<u64>);
34/// impl Clock for Tick {
35///     fn now(&self) -> Timestamp {
36///         let v = self.0.get(); self.0.set(v + 1); Timestamp::from_nanos(v)
37///     }
38/// }
39///
40/// let mut chain = Chain::new(
41///     XorHasher([0u8; HASH_LEN], 0),
42///     MemorySink::new(),
43///     Tick(core::cell::Cell::new(1)),
44/// );
45/// chain.append(Actor::new("a"), Action::new("x"), Target::new("t"), Outcome::Success).unwrap();
46///
47/// assert_eq!(chain.sink().len(), 1);
48/// ```
49#[derive(Clone, Debug, Default)]
50pub struct MemorySink {
51    records: Vec<OwnedRecord>,
52}
53
54impl MemorySink {
55    /// Construct an empty [`MemorySink`].
56    #[inline]
57    pub const fn new() -> Self {
58        Self {
59            records: Vec::new(),
60        }
61    }
62
63    /// Construct a [`MemorySink`] with pre-allocated capacity for
64    /// `cap` records.
65    #[inline]
66    pub fn with_capacity(cap: usize) -> Self {
67        Self {
68            records: Vec::with_capacity(cap),
69        }
70    }
71
72    /// Number of records currently held.
73    #[inline]
74    pub fn len(&self) -> usize {
75        self.records.len()
76    }
77
78    /// `true` if no records have been written.
79    #[inline]
80    pub fn is_empty(&self) -> bool {
81        self.records.is_empty()
82    }
83
84    /// Borrow the recorded log.
85    #[inline]
86    pub fn records(&self) -> &[OwnedRecord] {
87        &self.records
88    }
89
90    /// Consume the sink and return its records.
91    #[inline]
92    pub fn into_records(self) -> Vec<OwnedRecord> {
93        self.records
94    }
95
96    /// Drop all stored records.
97    #[inline]
98    pub fn clear(&mut self) {
99        self.records.clear();
100    }
101}
102
103impl Sink for MemorySink {
104    fn write(&mut self, record: &Record<'_>) -> core::result::Result<(), SinkError> {
105        self.records.push(OwnedRecord::from_record(record));
106        Ok(())
107    }
108}