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}