use serde::{Deserialize, Serialize};
use crate::action::NotificationAction;
use crate::wire::{lossy_string, lossy_u64};
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Notification {
#[serde(deserialize_with = "lossy_u64")]
pub notification_id: u64,
pub app: String,
pub user: String,
pub datetime: String,
pub object_type: String,
#[serde(deserialize_with = "lossy_string")]
pub object_id: String,
pub subject: String,
#[serde(default)]
pub message: String,
#[serde(default)]
pub link: String,
#[serde(default)]
pub actions: Vec<NotificationAction>,
}
impl Notification {
#[allow(clippy::too_many_arguments)]
pub fn new(
notification_id: u64,
app: String,
user: String,
datetime: String,
object_type: String,
object_id: String,
subject: String,
message: String,
link: String,
actions: Vec<NotificationAction>,
) -> Self {
Self {
notification_id,
app,
user,
datetime,
object_type,
object_id,
subject,
message,
link,
actions,
}
}
pub const fn notification_id(&self) -> u64 {
self.notification_id
}
pub fn app(&self) -> &str {
&self.app
}
pub fn user(&self) -> &str {
&self.user
}
pub fn datetime(&self) -> &str {
&self.datetime
}
pub fn object_type(&self) -> &str {
&self.object_type
}
pub fn object_id(&self) -> &str {
&self.object_id
}
pub fn subject(&self) -> &str {
&self.subject
}
pub fn message(&self) -> &str {
&self.message
}
pub fn link(&self) -> &str {
&self.link
}
pub fn actions(&self) -> &[NotificationAction] {
&self.actions
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_notification_from_json() {
let json = r#"{
"notification_id": 61,
"app": "files_sharing",
"user": "admin",
"datetime": "2026-10-24T12:00:00+00:00",
"object_type": "remote_share",
"object_id": "13",
"subject": "Share invitation received",
"message": "User shared a folder",
"link": "https://example.com/share/13",
"actions": [
{
"label": "Accept",
"link": "https://example.com/share/accept",
"type": "POST",
"primary": true
}
]
}"#;
let n: Notification = serde_json::from_str(json).unwrap();
assert_eq!(n.notification_id(), 61);
assert_eq!(n.app(), "files_sharing");
assert_eq!(n.user(), "admin");
assert_eq!(n.datetime(), "2026-10-24T12:00:00+00:00");
assert_eq!(n.object_type(), "remote_share");
assert_eq!(n.object_id(), "13");
assert_eq!(n.subject(), "Share invitation received");
assert_eq!(n.message(), "User shared a folder");
assert_eq!(n.link(), "https://example.com/share/13");
assert_eq!(n.actions().len(), 1);
assert_eq!(n.actions()[0].label(), "Accept");
}
#[test]
fn parses_numeric_object_id_and_string_notification_id() {
let json = r#"{
"notification_id": "99",
"app": "core",
"user": "alice",
"datetime": "2026-10-24T12:00:00+00:00",
"object_type": "user",
"object_id": 1234,
"subject": "Password reset"
}"#;
let n: Notification = serde_json::from_str(json).unwrap();
assert_eq!(n.notification_id(), 99);
assert_eq!(n.object_id(), "1234");
assert_eq!(n.message(), "");
assert_eq!(n.link(), "");
assert!(n.actions().is_empty());
}
}