Skip to main content

nextcloud_notifications/
action.rs

1//! Notification action model representing interactive buttons and links.
2
3use serde::{Deserialize, Serialize};
4
5/// An interactive action button or link attached to a Nextcloud notification.
6///
7/// Actions represent follow-up operations a user can trigger directly from
8/// the notification (such as accepting or declining a shared file).
9///
10/// See the official [Nextcloud Notifications API v2 Action Element documentation](https://github.com/nextcloud/notifications/blob/master/docs/ocs-endpoint-v2.md#action-element).
11#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
12pub struct NotificationAction {
13    /// Translated short label of the action or button presented to the user.
14    pub label: String,
15
16    /// Target URL link to execute or navigate to when the action is triggered.
17    pub link: String,
18
19    /// HTTP method used for the action request (`"GET"`, `"POST"`, `"DELETE"`, `"PUT"`),
20    /// or `"WEB"` for redirecting the browser.
21    #[serde(rename = "type")]
22    pub method: String,
23
24    /// Whether this action is marked as the primary (default) action.
25    #[serde(default, rename = "primary")]
26    pub is_primary: bool,
27}
28
29impl NotificationAction {
30    /// Creates a new notification action.
31    pub const fn new(label: String, link: String, method: String, is_primary: bool) -> Self {
32        Self {
33            label,
34            link,
35            method,
36            is_primary,
37        }
38    }
39
40    /// Translated label of the action.
41    pub fn label(&self) -> &str {
42        &self.label
43    }
44
45    /// Target link of the action.
46    pub fn link(&self) -> &str {
47        &self.link
48    }
49
50    /// HTTP method or action type (`GET`, `POST`, `DELETE`, `PUT`, or `WEB`).
51    pub fn method(&self) -> &str {
52        &self.method
53    }
54
55    /// Whether this is the primary action.
56    pub const fn is_primary(&self) -> bool {
57        self.is_primary
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn parses_action_from_json() {
67        let json = r#"{
68            "label": "Accept",
69            "link": "https://example.com/share/accept",
70            "type": "POST",
71            "primary": true
72        }"#;
73
74        let action: NotificationAction = serde_json::from_str(json).unwrap();
75        assert_eq!(action.label(), "Accept");
76        assert_eq!(action.link(), "https://example.com/share/accept");
77        assert_eq!(action.method(), "POST");
78        assert!(action.is_primary());
79    }
80
81    #[test]
82    fn defaults_is_primary_to_false() {
83        let json = r#"{
84            "label": "Decline",
85            "link": "https://example.com/share/decline",
86            "type": "DELETE"
87        }"#;
88
89        let action: NotificationAction = serde_json::from_str(json).unwrap();
90        assert_eq!(action.label(), "Decline");
91        assert!(!action.is_primary());
92    }
93
94    #[test]
95    fn serializes_matching_wire_field_names() {
96        let action = NotificationAction::new(
97            "View".to_owned(),
98            "https://example.com/view".to_owned(),
99            "GET".to_owned(),
100            false,
101        );
102
103        let value = serde_json::to_value(&action).unwrap();
104        assert_eq!(value["type"], "GET");
105        assert_eq!(value["primary"], false);
106    }
107}