rtt.rs

  1// Copyright (c) 2022 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  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 xso::{text::EmptyAsNone, AsXml, FromXml};
  8
  9use crate::ns;
 10
 11generate_attribute!(
 12    /// Events for real-time text.
 13    Event, "event", {
 14        /// Begin a new real-time message.
 15        New => "new",
 16
 17        /// Re-initialize the real-time message.
 18        Reset => "reset",
 19
 20        /// Modify existing real-time message.
 21        Edit => "edit",
 22
 23        /// Signals activation of real-time text.
 24        Init => "init",
 25
 26        /// Signals deactivation of real-time text.
 27        Cancel => "cancel",
 28    }, Default = Edit
 29);
 30
 31generate_attribute!(
 32    /// The number of codepoints to erase.
 33    Num, "n", u32, Default = 1
 34);
 35
 36/// Choice between the three possible actions.
 37#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 38#[xml(namespace = ns::RTT, exhaustive)]
 39pub enum Action {
 40    /// Supports the transmission of text, including key presses, and text block inserts.
 41    #[xml(name = "t")]
 42    Insert {
 43        /// Position in the message to start inserting from.  If None, this means to start from the
 44        /// end of the message.
 45        #[xml(attribute(default, name = "p"))]
 46        pos: Option<u32>,
 47
 48        /// Text to insert.
 49        #[xml(text = EmptyAsNone)]
 50        text: Option<String>,
 51    },
 52
 53    /// Supports the behavior of backspace key presses.  Text is removed towards beginning of the
 54    /// message.  This element is also used for all delete operations, including the backspace key,
 55    /// the delete key, and text block deletes.
 56    #[xml(name = "e")]
 57    Erase {
 58        /// Position in the message to start erasing from.  If None, this means to start from the end
 59        /// of the message.
 60        #[xml(attribute(default))]
 61        pos: Option<u32>,
 62
 63        /// Amount of characters to erase, to the left.
 64        #[xml(attribute(default))]
 65        num: Num,
 66    },
 67
 68    /// Allow for the transmission of intervals, between real-time text actions, to recreate the
 69    /// pauses between key presses.
 70    #[xml(name = "w")]
 71    Wait {
 72        /// Amount of milliseconds to wait before the next action.
 73        #[xml(attribute = "n")]
 74        time: u32,
 75    },
 76}
 77
 78/// Element transmitted at regular interval by the sender client while a message is being composed.
 79#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 80#[xml(namespace = ns::RTT, name = "rtt")]
 81pub struct Rtt {
 82    /// Counter to maintain synchronisation of real-time text.  Senders MUST increment this value
 83    /// by 1 for each subsequent edit to the same real-time message, including when appending new
 84    /// text.  Receiving clients MUST monitor this 'seq' value as a lightweight verification on the
 85    /// synchronization of real-time text messages.  The bounds of 'seq' is 31-bits, the range of
 86    /// positive values for a signed 32-bit integer.
 87    #[xml(attribute)]
 88    pub seq: u32,
 89
 90    /// This attribute signals events for real-time text.
 91    #[xml(attribute(default))]
 92    pub event: Event,
 93
 94    /// When editing a message using XEP-0308, this references the id of the message being edited.
 95    #[xml(attribute(default))]
 96    pub id: Option<String>,
 97
 98    /// Vector of actions being transmitted by this element.
 99    #[xml(child(n = ..))]
100    pub actions: Vec<Action>,
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use minidom::Element;
107
108    #[cfg(target_pointer_width = "32")]
109    #[test]
110    fn test_size() {
111        assert_size!(Event, 1);
112        assert_size!(Action, 20);
113        assert_size!(Rtt, 32);
114    }
115
116    #[cfg(target_pointer_width = "64")]
117    #[test]
118    fn test_size() {
119        assert_size!(Event, 1);
120        assert_size!(Action, 32);
121        assert_size!(Rtt, 56);
122    }
123
124    #[test]
125    fn simple() {
126        let elem: Element = "<rtt xmlns='urn:xmpp:rtt:0' seq='0'/>".parse().unwrap();
127        let rtt = Rtt::try_from(elem).unwrap();
128        assert_eq!(rtt.seq, 0);
129        assert_eq!(rtt.event, Event::Edit);
130        assert_eq!(rtt.id, None);
131        assert_eq!(rtt.actions.len(), 0);
132    }
133
134    #[test]
135    fn sequence() {
136        let elem: Element = "<rtt xmlns='urn:xmpp:rtt:0' seq='0' event='new'><t>Hello,</t><w n='50'/><e/><t>!</t></rtt>"
137            .parse()
138            .unwrap();
139
140        let rtt = Rtt::try_from(elem).unwrap();
141        assert_eq!(rtt.seq, 0);
142        assert_eq!(rtt.event, Event::New);
143        assert_eq!(rtt.id, None);
144
145        let mut actions = rtt.actions.into_iter();
146        assert_eq!(actions.len(), 4);
147
148        let Action::Insert { pos, text } = actions.next().unwrap() else {
149            panic!()
150        };
151        assert_eq!(pos, None);
152        assert_eq!(text.unwrap(), "Hello,");
153
154        let Action::Wait { time } = actions.next().unwrap() else {
155            panic!()
156        };
157        assert_eq!(time, 50);
158
159        let Action::Erase { pos, num } = actions.next().unwrap() else {
160            panic!()
161        };
162        assert_eq!(pos, None);
163        assert_eq!(num, Num(1));
164
165        let Action::Insert { pos, text } = actions.next().unwrap() else {
166            panic!()
167        };
168        assert_eq!(pos, None);
169        assert_eq!(text.unwrap(), "!");
170    }
171}