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>(¬ification.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}