notification.rs

 1use crate::proto;
 2use serde::{Deserialize, Serialize};
 3use serde_json::{map, Value};
 4use strum::{EnumVariantNames, VariantNames as _};
 5
 6const KIND: &'static str = "kind";
 7const ACTOR_ID: &'static str = "actor_id";
 8
 9/// A notification that can be stored, associated with a given user.
10///
11/// This struct is stored in the collab database as JSON, so it shouldn't be
12/// changed in a backward-incompatible way. For example, when renaming a
13/// variant, add a serde alias for the old name.
14///
15/// When a notification is initiated by a user, use the `actor_id` field
16/// to store the user's id.
17#[derive(Debug, Clone, PartialEq, Eq, EnumVariantNames, Serialize, Deserialize)]
18#[serde(tag = "kind")]
19pub enum Notification {
20    ContactRequest {
21        actor_id: u64,
22    },
23    ContactRequestAccepted {
24        actor_id: u64,
25    },
26    ChannelInvitation {
27        actor_id: u64,
28        channel_id: u64,
29    },
30    ChannelMessageMention {
31        actor_id: u64,
32        channel_id: u64,
33        message_id: u64,
34    },
35}
36
37impl Notification {
38    pub fn to_proto(&self) -> proto::Notification {
39        let mut value = serde_json::to_value(self).unwrap();
40        let mut actor_id = None;
41        let value = value.as_object_mut().unwrap();
42        let Some(Value::String(kind)) = value.remove(KIND) else {
43            unreachable!()
44        };
45        if let map::Entry::Occupied(e) = value.entry(ACTOR_ID) {
46            if e.get().is_u64() {
47                actor_id = e.remove().as_u64();
48            }
49        }
50        proto::Notification {
51            kind,
52            actor_id,
53            content: serde_json::to_string(&value).unwrap(),
54            ..Default::default()
55        }
56    }
57
58    pub fn from_proto(notification: &proto::Notification) -> Option<Self> {
59        let mut value = serde_json::from_str::<Value>(&notification.content).ok()?;
60        let object = value.as_object_mut()?;
61        object.insert(KIND.into(), notification.kind.to_string().into());
62        if let Some(actor_id) = notification.actor_id {
63            object.insert(ACTOR_ID.into(), actor_id.into());
64        }
65        serde_json::from_value(value).ok()
66    }
67
68    pub fn all_variant_names() -> &'static [&'static str] {
69        Self::VARIANTS
70    }
71}
72
73#[test]
74fn test_notification() {
75    // Notifications can be serialized and deserialized.
76    for notification in [
77        Notification::ContactRequest { actor_id: 1 },
78        Notification::ContactRequestAccepted { actor_id: 2 },
79        Notification::ChannelInvitation {
80            actor_id: 0,
81            channel_id: 100,
82        },
83        Notification::ChannelMessageMention {
84            actor_id: 200,
85            channel_id: 30,
86            message_id: 1,
87        },
88    ] {
89        let message = notification.to_proto();
90        let deserialized = Notification::from_proto(&message).unwrap();
91        assert_eq!(deserialized, notification);
92    }
93
94    // When notifications are serialized, the `kind` and `actor_id` fields are
95    // stored separately, and do not appear redundantly in the JSON.
96    let notification = Notification::ContactRequest { actor_id: 1 };
97    assert_eq!(notification.to_proto().content, "{}");
98}