Skip to main content

photon_backend/models/
event.rs

1//! Event model and envelope.
2
3use std::fmt;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8/// Published event with payload and metadata.
9///
10/// [`Debug`] redacts `actor_json` and `payload_json` so accidental logging cannot leak
11/// plaintext when transport crypto is enabled (or legacy plaintext rows in labs).
12#[derive(Clone, Serialize, Deserialize)]
13pub struct Event {
14    /// Unique event ID (UUID).
15    pub event_id: String,
16    /// Topic name.
17    pub topic_name: String,
18    /// Key value if keyed topic.
19    pub topic_key: Option<String>,
20    /// Sequence number per topic/key.
21    pub seq: i64,
22    /// Captured identity (actor JSON).
23    pub actor_json: serde_json::Value,
24    /// Serialized payload.
25    pub payload_json: serde_json::Value,
26    /// When the event was published.
27    pub created_at: DateTime<Utc>,
28}
29
30impl fmt::Debug for Event {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.debug_struct("Event")
33            .field("event_id", &self.event_id)
34            .field("topic_name", &self.topic_name)
35            .field("topic_key", &self.topic_key)
36            .field("seq", &self.seq)
37            .field("actor_json", &"<redacted>")
38            .field("payload_json", &"<redacted>")
39            .field("created_at", &self.created_at)
40            .finish()
41    }
42}
43
44/// Event envelope with decoded payload.
45#[derive(Debug, Clone)]
46pub struct Envelope<T> {
47    /// The raw event metadata.
48    pub event: Event,
49    /// Decoded payload.
50    pub payload: T,
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use chrono::TimeZone;
57
58    #[test]
59    fn debug_redacts_actor_and_payload() {
60        let event = Event {
61            event_id: "e1".into(),
62            topic_name: "t".into(),
63            topic_key: None,
64            seq: 1,
65            actor_json: serde_json::json!({"secret": "actor-secret"}),
66            payload_json: serde_json::json!({"secret": "payload-secret"}),
67            created_at: Utc.timestamp_opt(0, 0).unwrap(),
68        };
69        let dbg = format!("{event:?}");
70        assert!(dbg.contains("<redacted>"));
71        assert!(!dbg.contains("actor-secret"));
72        assert!(!dbg.contains("payload-secret"));
73        assert!(dbg.contains("e1"));
74    }
75}