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/.
67use xso::{text::EmptyAsNone, AsXml, FromXml};
89use crate::ns;
1011generate_attribute!(
12/// Events for real-time text.
13Event, "event", {
14/// Begin a new real-time message.
15New => "new",
1617/// Re-initialize the real-time message.
18Reset => "reset",
1920/// Modify existing real-time message.
21Edit => "edit",
2223/// Signals activation of real-time text.
24Init => "init",
2526/// Signals deactivation of real-time text.
27Cancel => "cancel",
28 }, Default = Edit
29);
3031generate_attribute!(
32/// The number of codepoints to erase.
33Num, "n", u32, Default = 1
34);
3536/// 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")]
42Insert {
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"))]
46pos: Option<u32>,
4748/// Text to insert.
49#[xml(text = EmptyAsNone)]
50text: Option<String>,
51 },
5253/// 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")]
57Erase {
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))]
61pos: Option<u32>,
6263/// Amount of characters to erase, to the left.
64#[xml(attribute(default))]
65num: Num,
66 },
6768/// Allow for the transmission of intervals, between real-time text actions, to recreate the
69 /// pauses between key presses.
70#[xml(name = "w")]
71Wait {
72/// Amount of milliseconds to wait before the next action.
73#[xml(attribute = "n")]
74time: u32,
75 },
76}
7778/// 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)]
88pub seq: u32,
8990/// This attribute signals events for real-time text.
91#[xml(attribute(default))]
92pub event: Event,
9394/// When editing a message using XEP-0308, this references the id of the message being edited.
95#[xml(attribute(default))]
96pub id: Option<String>,
9798/// Vector of actions being transmitted by this element.
99#[xml(child(n = ..))]
100pub actions: Vec<Action>,
101}
102103#[cfg(test)]
104mod tests {
105use super::*;
106use minidom::Element;
107108#[cfg(target_pointer_width = "32")]
109 #[test]
110fn test_size() {
111assert_size!(Event, 1);
112assert_size!(Action, 20);
113assert_size!(Rtt, 32);
114 }
115116#[cfg(target_pointer_width = "64")]
117 #[test]
118fn test_size() {
119assert_size!(Event, 1);
120assert_size!(Action, 32);
121assert_size!(Rtt, 56);
122 }
123124#[test]
125fn simple() {
126let elem: Element = "<rtt xmlns='urn:xmpp:rtt:0' seq='0'/>".parse().unwrap();
127let rtt = Rtt::try_from(elem).unwrap();
128assert_eq!(rtt.seq, 0);
129assert_eq!(rtt.event, Event::Edit);
130assert_eq!(rtt.id, None);
131assert_eq!(rtt.actions.len(), 0);
132 }
133134#[test]
135fn sequence() {
136let 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();
139140let rtt = Rtt::try_from(elem).unwrap();
141assert_eq!(rtt.seq, 0);
142assert_eq!(rtt.event, Event::New);
143assert_eq!(rtt.id, None);
144145let mut actions = rtt.actions.into_iter();
146assert_eq!(actions.len(), 4);
147148let Action::Insert { pos, text } = actions.next().unwrap() else {
149panic!()
150 };
151assert_eq!(pos, None);
152assert_eq!(text.unwrap(), "Hello,");
153154let Action::Wait { time } = actions.next().unwrap() else {
155panic!()
156 };
157assert_eq!(time, 50);
158159let Action::Erase { pos, num } = actions.next().unwrap() else {
160panic!()
161 };
162assert_eq!(pos, None);
163assert_eq!(num, Num(1));
164165let Action::Insert { pos, text } = actions.next().unwrap() else {
166panic!()
167 };
168assert_eq!(pos, None);
169assert_eq!(text.unwrap(), "!");
170 }
171}