notification.rs

  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>(&notification.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}