Skip to main content

ferro_audit/
target.rs

1//! `AuditTarget` — what the audit entry is about (D-07).
2//!
3//! Two stringly-typed fields: `kind` (dotted-namespace convention per D-08
4//! — e.g. `"inventory.unit"`, `"user"`, `"checkout.session"`) and `id`
5//! (consumer-stringified primary key). Domain-agnostic by design: a closed
6//! enum would force every consumer to upstream their target variants.
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct AuditTarget {
10    /// Dotted-or-snake namespace, e.g. `"inventory.unit"`, `"user"`,
11    /// `"checkout.session"`. Convention only — not enforced at compile time.
12    pub kind: String,
13
14    /// Consumer-stringified primary key (any string that uniquely identifies
15    /// the target within `kind`).
16    pub id: String,
17}
18
19impl AuditTarget {
20    /// Construct a new `AuditTarget` from any string-like kind and any
21    /// stringifiable id.
22    pub fn new(kind: impl Into<String>, id: impl ToString) -> Self {
23        Self {
24            kind: kind.into(),
25            id: id.to_string(),
26        }
27    }
28}
29
30impl<K: Into<String>, I: ToString> From<(K, I)> for AuditTarget {
31    fn from((kind, id): (K, I)) -> Self {
32        Self::new(kind, id)
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn new_constructs_from_str_and_int() {
42        let t = AuditTarget::new("inventory.unit", 42_i64);
43        assert_eq!(t.kind, "inventory.unit");
44        assert_eq!(t.id, "42");
45    }
46
47    #[test]
48    fn new_constructs_from_string_and_string() {
49        let t = AuditTarget::new(String::from("user"), String::from("u_42"));
50        assert_eq!(t.kind, "user");
51        assert_eq!(t.id, "u_42");
52    }
53
54    #[test]
55    fn from_tuple_constructs_target() {
56        let t: AuditTarget = ("checkout.session", "ses_abc123").into();
57        assert_eq!(t.kind, "checkout.session");
58        assert_eq!(t.id, "ses_abc123");
59    }
60
61    #[test]
62    fn from_tuple_with_numeric_id() {
63        let t: AuditTarget = ("inventory.unit", 7_u32).into();
64        assert_eq!(t.kind, "inventory.unit");
65        assert_eq!(t.id, "7");
66    }
67}