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}