1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::borrow::Cow;
4use strum::{EnumVariantNames, IntoStaticStr, 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.
13///
14/// For example, when renaming a variant, add a serde alias for the old name.
15#[derive(Debug, Clone, PartialEq, Eq, EnumVariantNames, IntoStaticStr, Serialize, Deserialize)]
16#[serde(tag = "kind")]
17pub enum Notification {
18 ContactRequest {
19 actor_id: u64,
20 },
21 ContactRequestAccepted {
22 actor_id: u64,
23 },
24 ChannelInvitation {
25 actor_id: u64,
26 channel_id: u64,
27 },
28 ChannelMessageMention {
29 actor_id: u64,
30 channel_id: u64,
31 message_id: u64,
32 },
33}
34
35/// The representation of a notification that is stored in the database and
36/// sent over the wire.
37#[derive(Debug)]
38pub struct AnyNotification {
39 pub kind: Cow<'static, str>,
40 pub actor_id: Option<u64>,
41 pub content: String,
42}
43
44impl Notification {
45 pub fn to_any(&self) -> AnyNotification {
46 let kind: &'static str = self.into();
47 let mut value = serde_json::to_value(self).unwrap();
48 let mut actor_id = None;
49 if let Some(value) = value.as_object_mut() {
50 value.remove("kind");
51 actor_id = value
52 .remove("actor_id")
53 .and_then(|value| Some(value.as_i64()? as u64));
54 }
55 AnyNotification {
56 kind: Cow::Borrowed(kind),
57 actor_id,
58 content: serde_json::to_string(&value).unwrap(),
59 }
60 }
61
62 pub fn from_any(notification: &AnyNotification) -> Option<Self> {
63 let mut value = serde_json::from_str::<Value>(¬ification.content).ok()?;
64 let object = value.as_object_mut()?;
65 object.insert(KIND.into(), notification.kind.to_string().into());
66 if let Some(actor_id) = notification.actor_id {
67 object.insert(ACTOR_ID.into(), actor_id.into());
68 }
69 serde_json::from_value(value).ok()
70 }
71
72 pub fn all_kinds() -> &'static [&'static str] {
73 Self::VARIANTS
74 }
75}
76
77#[test]
78fn test_notification() {
79 // Notifications can be serialized and deserialized.
80 for notification in [
81 Notification::ContactRequest { actor_id: 1 },
82 Notification::ContactRequestAccepted { actor_id: 2 },
83 Notification::ChannelInvitation {
84 actor_id: 0,
85 channel_id: 100,
86 },
87 Notification::ChannelMessageMention {
88 actor_id: 200,
89 channel_id: 30,
90 message_id: 1,
91 },
92 ] {
93 let serialized = notification.to_any();
94 let deserialized = Notification::from_any(&serialized).unwrap();
95 assert_eq!(deserialized, notification);
96 }
97
98 // When notifications are serialized, the `kind` and `actor_id` fields are
99 // stored separately, and do not appear redundantly in the JSON.
100 let notification = Notification::ContactRequest { actor_id: 1 };
101 assert_eq!(notification.to_any().content, "{}");
102}