push.rs

  1// Copyright (c) 2025 saarko <saarko@tutanota.com>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use crate::data_forms::DataForm;
  8use crate::iq::IqSetPayload;
  9use crate::jid::BareJid;
 10use crate::ns;
 11use crate::pubsub::PubSubPayload;
 12use minidom::Element;
 13use xso::{AsXml, FromXml};
 14
 15/// An enable element for push notifications
 16#[derive(Debug, Clone, FromXml, AsXml)]
 17#[xml(namespace = ns::PUSH, name = "enable")]
 18pub struct Enable {
 19    /// The 'jid' attribute of the XMPP Push Service being enabled.
 20    #[xml(attribute)]
 21    pub jid: BareJid,
 22
 23    /// The 'node' attribute which is set to the provisioned node specified by the App Server.
 24    #[xml(attribute(default))]
 25    pub node: Option<String>,
 26
 27    /// Optional additional information to be provided with each published notification, such as authentication credentials.
 28    #[xml(child(default))]
 29    pub form: Option<DataForm>,
 30}
 31
 32impl IqSetPayload for Enable {}
 33
 34/// A disable element for push notifications
 35#[derive(Debug, Clone, FromXml, AsXml)]
 36#[xml(namespace = ns::PUSH, name = "disable")]
 37pub struct Disable {
 38    /// The 'jid' attribute of the XMPP Push Service being disabled.
 39    #[xml(attribute)]
 40    pub jid: BareJid,
 41
 42    /// The 'node' attribute which was set to the provisioned node specified by the App Server.
 43    #[xml(attribute(default))]
 44    pub node: Option<String>,
 45}
 46
 47impl IqSetPayload for Disable {}
 48
 49/// A notification element containing push notification data
 50#[derive(Debug, Clone, FromXml, AsXml)]
 51#[xml(namespace = ns::PUSH, name = "notification")]
 52pub struct Notification {
 53    /// The 'form' to provide summarized information such as the number of unread messages or number of pending subscription requests.
 54    #[xml(child(default))]
 55    pub form: Option<DataForm>,
 56
 57    /// Child elements for the notification that are not part of the summary data form.
 58    #[xml(element(n = ..))]
 59    pub payloads: Vec<Element>,
 60}
 61
 62impl PubSubPayload for Notification {}
 63
 64#[cfg(test)]
 65mod tests {
 66    use super::*;
 67    use std::str::FromStr;
 68
 69    #[test]
 70    fn test_enable() {
 71        let test_enable = r#"<enable xmlns='urn:xmpp:push:0'
 72            jid='push-5.client.example'
 73            node='yxs32uqsflafdk3iuqo'>
 74            <x xmlns='jabber:x:data' type='submit'>
 75                <field var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#publish-options</value></field>
 76                <field var='secret'><value>eruio234vzxc2kla-91</value></field>
 77            </x>
 78        </enable>"#;
 79
 80        let elem = Element::from_str(test_enable).expect("Failed to parse XML");
 81        let enable = Enable::try_from(elem).expect("Failed to parse enable");
 82
 83        assert_eq!(
 84            enable.jid,
 85            BareJid::from_str("push-5.client.example").unwrap()
 86        );
 87        assert_eq!(enable.node.unwrap(), "yxs32uqsflafdk3iuqo");
 88        assert!(enable.form.is_some());
 89    }
 90
 91    #[test]
 92    fn test_enable_only_with_required_fields() {
 93        let test_enable = r#"<enable xmlns='urn:xmpp:push:0'
 94            jid='push-5.client.example' />"#;
 95
 96        let elem = Element::from_str(test_enable).expect("Failed to parse XML");
 97        let enable = Enable::try_from(elem).expect("Failed to parse enable");
 98
 99        assert_eq!(
100            enable.jid,
101            BareJid::from_str("push-5.client.example").unwrap()
102        );
103        assert!(enable.node.is_none());
104        assert!(enable.form.is_none());
105    }
106
107    #[test]
108    fn test_disable() {
109        let test_disable = r#"<disable xmlns='urn:xmpp:push:0'
110            jid='push-5.client.example'
111            node='yxs32uqsflafdk3iuqo' />"#;
112
113        let elem = Element::from_str(test_disable).expect("Failed to parse XML");
114        let disable = Disable::try_from(elem).expect("Failed to parse disable");
115
116        assert_eq!(
117            disable.jid,
118            BareJid::from_str("push-5.client.example").unwrap()
119        );
120        assert_eq!(disable.node.unwrap(), "yxs32uqsflafdk3iuqo");
121    }
122
123    #[test]
124    fn test_disable_only_with_required_fields() {
125        let test_disable = r#"<disable xmlns='urn:xmpp:push:0'
126            jid='push-5.client.example' />"#;
127
128        let elem = Element::from_str(test_disable).expect("Failed to parse XML");
129        let disable = Disable::try_from(elem).expect("Failed to parse disable");
130
131        assert_eq!(
132            disable.jid,
133            BareJid::from_str("push-5.client.example").unwrap()
134        );
135        assert!(disable.node.is_none());
136    }
137
138    #[test]
139    fn test_notification() {
140        let test_notification = r#"<notification xmlns='urn:xmpp:push:0'>
141            <x xmlns='jabber:x:data' type='result'>
142                <field var='FORM_TYPE'><value>urn:xmpp:push:summary</value></field>
143                <field var='message-count'><value>1</value></field>
144                <field var='last-message-sender'><value>juliet@capulet.example/balcony</value></field>
145                <field var='last-message-body'><value>Wherefore art thou, Romeo?</value></field>
146            </x>
147            <additional xmlns='http://example.com/custom'>Additional custom elements</additional>
148        </notification>"#;
149
150        let elem = Element::from_str(test_notification).expect("Failed to parse XML");
151        let notification = Notification::try_from(elem).expect("Failed to parse notification");
152
153        assert!(notification.form.is_some());
154        assert_eq!(notification.payloads.len(), 1);
155    }
156
157    #[test]
158    fn test_notification_only_with_required_fields() {
159        let test_notification = r#"<notification xmlns='urn:xmpp:push:0'>
160            <additional xmlns='http://example.com/custom'>Additional custom elements</additional>
161        </notification>"#;
162
163        let elem = Element::from_str(test_notification).expect("Failed to parse XML");
164        let notification = Notification::try_from(elem).expect("Failed to parse notification");
165
166        assert!(notification.form.is_none());
167        assert_eq!(notification.payloads.len(), 1);
168    }
169}