// Copyright (c) 2022 Emmanuel Gil Peyrot // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use xso::{text::EmptyAsNone, AsXml, FromXml}; use crate::ns; use minidom::Element; use xso::error::{Error, FromElementError}; generate_attribute!( /// Events for real-time text. Event, "event", { /// Begin a new real-time message. New => "new", /// Re-initialize the real-time message. Reset => "reset", /// Modify existing real-time message. Edit => "edit", /// Signals activation of real-time text. Init => "init", /// Signals deactivation of real-time text. Cancel => "cancel", }, Default = Edit ); /// Supports the transmission of text, including key presses, and text block inserts. #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = ns::RTT, name = "t")] pub struct Insert { /// Position in the message to start inserting from. If None, this means to start from the /// end of the message. #[xml(attribute(default, name = "p"))] pub pos: Option, /// Text to insert. #[xml(text = EmptyAsNone)] pub text: Option, } impl TryFrom for Insert { type Error = Error; fn try_from(action: Action) -> Result { match action { Action::Insert(insert) => Ok(insert), _ => Err(Error::Other("This is not an insert action.")), } } } // TODO: add a way in the macro to set a default value. /* generate_element!( Erase, "e", RTT, attributes: [ pos: Option = "p", num: Default = "n", ] ); */ /// Supports the behavior of backspace key presses. Text is removed towards beginning of the /// message. This element is also used for all delete operations, including the backspace key, the /// delete key, and text block deletes. #[derive(Debug, Clone, PartialEq)] pub struct Erase { /// Position in the message to start erasing from. If None, this means to start from the end /// of the message. pub pos: Option, /// Amount of characters to erase, to the left. pub num: u32, } impl TryFrom for Erase { type Error = FromElementError; fn try_from(elem: Element) -> Result { check_self!(elem, "e", RTT); check_no_unknown_attributes!(elem, "e", ["p", "n"]); let pos = get_attr!(elem, "p", Option); let num = get_attr!(elem, "n", Option).unwrap_or(1); check_no_children!(elem, "e"); Ok(Erase { pos, num }) } } impl From for Element { fn from(elem: Erase) -> Element { Element::builder("e", ns::RTT) .attr("p", elem.pos) .attr("n", elem.num) .build() } } impl TryFrom for Erase { type Error = Error; fn try_from(action: Action) -> Result { match action { Action::Erase(erase) => Ok(erase), _ => Err(Error::Other("This is not an erase action.")), } } } /// Allow for the transmission of intervals, between real-time text actions, to recreate the /// pauses between key presses. #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = ns::RTT, name = "w")] pub struct Wait { /// Amount of milliseconds to wait before the next action. #[xml(attribute = "n")] pub time: u32, } impl TryFrom for Wait { type Error = Error; fn try_from(action: Action) -> Result { match action { Action::Wait(wait) => Ok(wait), _ => Err(Error::Other("This is not a wait action.")), } } } /// Choice between the three possible actions. #[derive(Debug, Clone, PartialEq)] pub enum Action { /// Insert text action. Insert(Insert), /// Erase text action. Erase(Erase), /// Wait action. Wait(Wait), } impl TryFrom for Action { type Error = FromElementError; fn try_from(elem: Element) -> Result { match elem.name() { "t" => Insert::try_from(elem).map(Action::Insert), "e" => Erase::try_from(elem).map(Action::Erase), "w" => Wait::try_from(elem).map(Action::Wait), _ => Err(FromElementError::Mismatch(elem)), } } } impl From for Element { fn from(action: Action) -> Element { match action { Action::Insert(insert) => Element::from(insert), Action::Erase(erase) => Element::from(erase), Action::Wait(wait) => Element::from(wait), } } } // TODO: Allow a wildcard name to the macro, to simplify the following code: /* generate_element!( Rtt, "rtt", RTT, attributes: [ seq: Required = "seq", event: Default = "event", id: Option = "id", ], children: [ actions: Vec = (*, RTT) => Action, ] ); */ /// Element transmitted at regular interval by the sender client while a message is being composed. #[derive(Debug, Clone, PartialEq)] pub struct Rtt { /// Counter to maintain synchronisation of real-time text. Senders MUST increment this value /// by 1 for each subsequent edit to the same real-time message, including when appending new /// text. Receiving clients MUST monitor this 'seq' value as a lightweight verification on the /// synchronization of real-time text messages. The bounds of 'seq' is 31-bits, the range of /// positive values for a signed 32-bit integer. pub seq: u32, /// This attribute signals events for real-time text. pub event: Event, /// When editing a message using XEP-0308, this references the id of the message being edited. pub id: Option, /// Vector of actions being transmitted by this element. pub actions: Vec, } impl TryFrom for Rtt { type Error = FromElementError; fn try_from(elem: Element) -> Result { check_self!(elem, "rtt", RTT); check_no_unknown_attributes!(elem, "rtt", ["seq", "event", "id"]); let seq = get_attr!(elem, "seq", Required); let event = get_attr!(elem, "event", Default); let id = get_attr!(elem, "id", Option); let mut actions = Vec::new(); for child in elem.children() { if child.ns() != ns::RTT { return Err(Error::Other("Unknown child in rtt element.").into()); } actions.push(Action::try_from(child.clone())?); } Ok(Rtt { seq, event, id, actions, }) } } impl From for Element { fn from(elem: Rtt) -> Element { Element::builder("rtt", ns::RTT) .attr("seq", elem.seq) .attr("event", elem.event) .attr("id", elem.id) .append_all(elem.actions) .build() } } #[cfg(test)] mod tests { use super::*; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { assert_size!(Event, 1); assert_size!(Insert, 20); assert_size!(Erase, 12); assert_size!(Wait, 4); assert_size!(Action, 20); assert_size!(Rtt, 32); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { assert_size!(Event, 1); assert_size!(Insert, 32); assert_size!(Erase, 12); assert_size!(Wait, 4); assert_size!(Action, 32); assert_size!(Rtt, 56); } #[test] fn simple() { let elem: Element = "".parse().unwrap(); let rtt = Rtt::try_from(elem).unwrap(); assert_eq!(rtt.seq, 0); assert_eq!(rtt.event, Event::Edit); assert_eq!(rtt.id, None); assert_eq!(rtt.actions.len(), 0); } #[test] fn sequence() { let elem: Element = "Hello,!" .parse() .unwrap(); let rtt = Rtt::try_from(elem).unwrap(); assert_eq!(rtt.seq, 0); assert_eq!(rtt.event, Event::New); assert_eq!(rtt.id, None); let mut actions = rtt.actions.into_iter(); assert_eq!(actions.len(), 4); let t: Insert = actions.next().unwrap().try_into().unwrap(); assert_eq!(t.pos, None); assert_eq!(t.text, Some(String::from("Hello,"))); let w: Wait = actions.next().unwrap().try_into().unwrap(); assert_eq!(w.time, 50); let e: Erase = actions.next().unwrap().try_into().unwrap(); assert_eq!(e.pos, None); assert_eq!(e.num, 1); let t: Insert = actions.next().unwrap().try_into().unwrap(); assert_eq!(t.pos, None); assert_eq!(t.text, Some(String::from("!"))); } }