Skip to main content

es_entity/
forgettable.rs

1//! Support for forgettable event data (e.g., for GDPR compliance).
2//!
3//! The [`Forgettable<T>`] wrapper marks event fields containing personal data that
4//! can be permanently deleted. Sensitive field values are stored in a separate
5//! "forgettable payloads" table. Calling `forget()` on the repository deletes
6//! those payloads, leaving the events intact but with `null` for forgotten fields.
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use std::{fmt, hash, ops::Deref};
11
12/// Wrapper for event fields containing data that can be forgotten (e.g., for GDPR).
13///
14/// This is an opaque struct — internal state is private so callers cannot
15/// pattern-match to extract the raw value. Use [`Forgettable::value()`] to get
16/// a [`ForgettableRef`] that derefs to `T` but does **not** implement `Serialize`,
17/// preventing accidental re-serialization of personal data.
18///
19/// # Serde Behavior
20///
21/// - **Both** set and forgotten values serialize as `null` to prevent data
22///   leakage when events are serialized to secondary stores.
23/// - Deserializing `null` produces a forgotten value, non-null produces a set value.
24/// - Real values are extracted via [`__extract_payload_value`] **before** serde runs,
25///   and stored in the forgettable payloads table.
26///
27/// # Repository Guard
28///
29/// An event type with `Forgettable<T>` fields must be backed by a repository
30/// that enables `forgettable` in `#[es_repo(...)]`; otherwise the payloads are
31/// never scrubbed. The repository derive cannot see the event's
32/// forgettable-ness at macro time, so for a repository that omits the flag it
33/// emits a const assertion on the event's inherent `HAS_FORGETTABLE_FIELDS`.
34/// This is that assertion verbatim, and it fails to compile because the event
35/// is forgettable:
36///
37/// ```compile_fail
38/// use es_entity::*;
39/// use serde::{Deserialize, Serialize};
40///
41/// es_entity::entity_id! { UserId }
42///
43/// #[derive(EsEvent, Serialize, Deserialize)]
44/// #[serde(tag = "type", rename_all = "snake_case")]
45/// #[es_event(id = "UserId")]
46/// pub enum UserEvent {
47///     Initialized { id: UserId, name: Forgettable<String> },
48/// }
49///
50/// const _: () = assert!(
51///     !UserEvent::HAS_FORGETTABLE_FIELDS,
52///     "event type has Forgettable fields but this repo does not enable `forgettable`; add `forgettable` to #[es_repo(...)]"
53/// );
54/// ```
55///
56/// # Example
57///
58/// ```rust
59/// use es_entity::Forgettable;
60///
61/// let name: Forgettable<String> = Forgettable::new("Alice".to_string());
62/// assert_eq!(&*name.value().unwrap(), "Alice");
63///
64/// let forgotten: Forgettable<String> = Forgettable::forgotten();
65/// assert!(forgotten.value().is_none());
66/// ```
67#[derive(Debug, Clone, PartialEq, Eq, Hash)]
68pub struct Forgettable<T>(Option<T>);
69
70impl<T> Default for Forgettable<T> {
71    /// Returns a forgotten (empty) `Forgettable`.
72    fn default() -> Self {
73        Forgettable(None)
74    }
75}
76
77impl<T> From<T> for Forgettable<T> {
78    fn from(value: T) -> Self {
79        Forgettable(Some(value))
80    }
81}
82
83impl<T> Forgettable<T> {
84    /// Creates a new `Forgettable` containing the given value.
85    pub fn new(value: T) -> Self {
86        Forgettable(Some(value))
87    }
88
89    /// Creates a forgotten (empty) `Forgettable`.
90    pub fn forgotten() -> Self {
91        Forgettable(None)
92    }
93
94    /// Returns a [`ForgettableRef`] wrapping the inner value, or `None` if forgotten.
95    ///
96    /// `ForgettableRef` implements `Deref<Target = T>` but **not** `Serialize`,
97    /// so you can read the value but cannot accidentally serialize it.
98    pub fn value(&self) -> Option<ForgettableRef<'_, T>> {
99        self.0.as_ref().map(ForgettableRef)
100    }
101
102    /// Returns `true` if the value is present.
103    pub fn is_set(&self) -> bool {
104        self.0.is_some()
105    }
106
107    /// Returns `true` if the value has been forgotten.
108    pub fn is_forgotten(&self) -> bool {
109        self.0.is_none()
110    }
111}
112
113impl<T: Serialize> Forgettable<T> {
114    /// Extracts the inner value as a `serde_json::Value` for storage in
115    /// the forgettable payloads table. Returns `None` if forgotten.
116    #[doc(hidden)]
117    pub fn __extract_payload_value(&self) -> Option<serde_json::Value> {
118        self.0
119            .as_ref()
120            .map(|v| serde_json::to_value(v).expect("Failed to serialize forgettable field"))
121    }
122}
123
124impl<T: Serialize> Serialize for Forgettable<T> {
125    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
126        serializer.serialize_none()
127    }
128}
129
130impl<'de, T: Deserialize<'de>> Deserialize<'de> for Forgettable<T> {
131    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
132        let value = Option::<T>::deserialize(deserializer)?;
133        match value {
134            Some(v) => Ok(Forgettable(Some(v))),
135            None => Ok(Forgettable(None)),
136        }
137    }
138}
139
140/// A non-serializable reference to the value inside a [`Forgettable<T>`].
141///
142/// Implements `Deref<Target = T>` so you can use it like `&T`, but does **not**
143/// implement `Serialize` or `Clone`, preventing accidental re-serialization or
144/// extraction of personal data.
145pub struct ForgettableRef<'a, T>(&'a T);
146
147impl<T: fmt::Debug> fmt::Debug for ForgettableRef<'_, T> {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        self.0.fmt(f)
150    }
151}
152
153impl<T: fmt::Display> fmt::Display for ForgettableRef<'_, T> {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        self.0.fmt(f)
156    }
157}
158
159impl<T> Deref for ForgettableRef<'_, T> {
160    type Target = T;
161
162    fn deref(&self) -> &T {
163        self.0
164    }
165}
166
167impl<T: PartialEq> PartialEq<T> for ForgettableRef<'_, T> {
168    fn eq(&self, other: &T) -> bool {
169        self.0 == other
170    }
171}
172
173impl<T: PartialEq> PartialEq for ForgettableRef<'_, T> {
174    fn eq(&self, other: &Self) -> bool {
175        self.0 == other.0
176    }
177}
178
179impl<T: Eq> Eq for ForgettableRef<'_, T> {}
180
181impl<T: hash::Hash> hash::Hash for ForgettableRef<'_, T> {
182    fn hash<H: hash::Hasher>(&self, state: &mut H) {
183        self.0.hash(state);
184    }
185}
186
187/// Injects forgettable payload values back into an event JSON object.
188///
189/// Merges all keys from the payload into the event JSON, overwriting `null` values
190/// with the original data.
191#[doc(hidden)]
192pub fn inject_forgettable_payload(event_json: &mut serde_json::Value, payload: serde_json::Value) {
193    if let (Some(event_obj), serde_json::Value::Object(payload_obj)) =
194        (event_json.as_object_mut(), payload)
195    {
196        for (key, value) in payload_obj {
197            event_obj.insert(key, value);
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn serialize_set_emits_null() {
208        let value: Forgettable<String> = Forgettable::new("Alice".to_string());
209        let json = serde_json::to_value(&value).unwrap();
210        assert_eq!(json, serde_json::json!(null));
211    }
212
213    #[test]
214    fn serialize_forgotten_emits_null() {
215        let value: Forgettable<String> = Forgettable::forgotten();
216        let json = serde_json::to_value(&value).unwrap();
217        assert_eq!(json, serde_json::json!(null));
218    }
219
220    #[test]
221    fn deserialize_value() {
222        let json = serde_json::json!("Alice");
223        let value: Forgettable<String> = serde_json::from_value(json).unwrap();
224        assert_eq!(value, Forgettable::new("Alice".to_string()));
225    }
226
227    #[test]
228    fn deserialize_null() {
229        let json = serde_json::json!(null);
230        let value: Forgettable<String> = serde_json::from_value(json).unwrap();
231        assert_eq!(value, Forgettable::forgotten());
232    }
233
234    #[test]
235    fn serialize_struct_with_forgettable_emits_null() {
236        #[derive(Serialize, Deserialize, Debug, PartialEq)]
237        struct Event {
238            #[serde(rename = "type")]
239            kind: String,
240            name: Forgettable<String>,
241            email: String,
242        }
243
244        let event = Event {
245            kind: "initialized".to_string(),
246            name: Forgettable::new("Alice".to_string()),
247            email: "alice@test.com".to_string(),
248        };
249        let json = serde_json::to_value(&event).unwrap();
250        // Set serializes as null to prevent data leakage
251        assert_eq!(json["name"], serde_json::json!(null));
252        assert_eq!(json["email"], serde_json::json!("alice@test.com"));
253
254        // Deserializing null yields Forgotten (real values come from payload table)
255        let deserialized: Event = serde_json::from_value(json).unwrap();
256        assert_eq!(deserialized.name, Forgettable::forgotten());
257
258        // Forgotten also serializes as null
259        let event_forgotten = Event {
260            kind: "initialized".to_string(),
261            name: Forgettable::forgotten(),
262            email: "alice@test.com".to_string(),
263        };
264        let json = serde_json::to_value(&event_forgotten).unwrap();
265        assert_eq!(json["name"], serde_json::json!(null));
266
267        let deserialized: Event = serde_json::from_value(json).unwrap();
268        assert_eq!(deserialized, event_forgotten);
269    }
270
271    #[test]
272    fn inject_payload() {
273        let mut json = serde_json::json!({
274            "type": "initialized",
275            "id": "uuid",
276            "name": null,
277            "email": "alice@test.com"
278        });
279
280        let payload = serde_json::json!({"name": "Alice"});
281        inject_forgettable_payload(&mut json, payload);
282
283        assert_eq!(json["name"], serde_json::json!("Alice"));
284        assert_eq!(json["email"], serde_json::json!("alice@test.com"));
285    }
286
287    #[test]
288    fn value_helpers() {
289        let set: Forgettable<String> = Forgettable::new("test".to_string());
290        assert!(set.is_set());
291        assert!(!set.is_forgotten());
292        assert_eq!(&*set.value().unwrap(), "test");
293
294        let forgotten: Forgettable<String> = Forgettable::forgotten();
295        assert!(!forgotten.is_set());
296        assert!(forgotten.is_forgotten());
297        assert!(forgotten.value().is_none());
298    }
299
300    #[test]
301    fn extract_payload_value() {
302        let set: Forgettable<String> = Forgettable::new("Alice".to_string());
303        assert_eq!(
304            set.__extract_payload_value(),
305            Some(serde_json::json!("Alice"))
306        );
307
308        let forgotten: Forgettable<String> = Forgettable::forgotten();
309        assert_eq!(forgotten.__extract_payload_value(), None);
310    }
311
312    #[test]
313    fn forgettable_ref_deref() {
314        let f = Forgettable::new("hello".to_string());
315        let r = f.value().unwrap();
316        // Deref to &String
317        assert_eq!(r.len(), 5);
318        assert_eq!(&*r, "hello");
319    }
320
321    #[test]
322    fn forgettable_ref_display() {
323        let f = Forgettable::new("Alice".to_string());
324        let r = f.value().unwrap();
325        assert_eq!(format!("{r}"), "Alice");
326    }
327
328    #[test]
329    fn forgettable_ref_partial_eq() {
330        let f = Forgettable::new("Alice".to_string());
331        let r = f.value().unwrap();
332        assert_eq!(r, "Alice".to_string());
333    }
334
335    #[test]
336    fn default_is_forgotten() {
337        let f: Forgettable<String> = Default::default();
338        assert!(f.is_forgotten());
339        assert!(f.value().is_none());
340    }
341
342    #[test]
343    fn from_value() {
344        let f: Forgettable<String> = "Alice".to_string().into();
345        assert!(f.is_set());
346        assert_eq!(&*f.value().unwrap(), "Alice");
347    }
348}