Skip to main content

audit_trail/
owned.rs

1//! Owned counterpart to [`Record`] for storage and replay.
2//!
3//! [`Record`] is intentionally borrowed: its string fields hold `&str`
4//! references into caller memory so the append hot path costs nothing on
5//! the heap. Once a record leaves the append path — when a sink wants to
6//! retain it, when a verifier reads it back, when a test mutates it for a
7//! tampering scenario — it needs to own its bytes. [`OwnedRecord`] is that
8//! owned form.
9//!
10//! Requires the `alloc` feature.
11
12use alloc::string::String;
13
14use crate::clock::Timestamp;
15use crate::hash::Digest;
16use crate::record::{Action, Actor, Outcome, Record, RecordId, Target};
17
18/// Owned counterpart to [`Record`]. Holds `String`-backed fields instead
19/// of borrowed `&str`s, so it can outlive the call that produced it.
20///
21/// Convert to a borrowed [`Record`] with [`OwnedRecord::as_record`].
22///
23/// # Example
24///
25/// ```
26/// use audit_trail::{Action, Actor, Digest, Outcome, OwnedRecord, Record, RecordId, Target, Timestamp};
27///
28/// let borrowed = Record::new(
29///     RecordId::GENESIS,
30///     Timestamp::from_nanos(1),
31///     Actor::new("system"),
32///     Action::new("chain.init"),
33///     Target::new("chain:0"),
34///     Outcome::Success,
35///     Digest::ZERO,
36///     Digest::ZERO,
37/// );
38/// let owned = OwnedRecord::from_record(&borrowed);
39/// assert_eq!(owned.as_record(), borrowed);
40/// ```
41#[derive(Clone, Debug, PartialEq, Eq, Hash)]
42pub struct OwnedRecord {
43    /// Record identifier.
44    pub id: RecordId,
45    /// Record timestamp.
46    pub timestamp: Timestamp,
47    /// Actor (who).
48    pub actor: String,
49    /// Action (what).
50    pub action: String,
51    /// Target (where).
52    pub target: String,
53    /// Outcome (result).
54    pub outcome: Outcome,
55    /// Hash of the preceding record in the chain.
56    pub prev_hash: Digest,
57    /// Hash of this record.
58    pub hash: Digest,
59}
60
61impl OwnedRecord {
62    /// Copy the fields of a borrowed [`Record`] into a new [`OwnedRecord`].
63    #[inline]
64    pub fn from_record(record: &Record<'_>) -> Self {
65        Self {
66            id: record.id(),
67            timestamp: record.timestamp(),
68            actor: String::from(record.actor().as_str()),
69            action: String::from(record.action().as_str()),
70            target: String::from(record.target().as_str()),
71            outcome: record.outcome(),
72            prev_hash: record.prev_hash(),
73            hash: record.hash(),
74        }
75    }
76
77    /// Borrow this owned record as a [`Record`] for a single call.
78    #[inline]
79    pub fn as_record(&self) -> Record<'_> {
80        Record::new(
81            self.id,
82            self.timestamp,
83            Actor::new(&self.actor),
84            Action::new(&self.action),
85            Target::new(&self.target),
86            self.outcome,
87            self.prev_hash,
88            self.hash,
89        )
90    }
91}
92
93impl From<&Record<'_>> for OwnedRecord {
94    #[inline]
95    fn from(record: &Record<'_>) -> Self {
96        Self::from_record(record)
97    }
98}