parsers: port Message to use the derive macros

Jonas SchΓ€fer created

Change summary

parsers/ChangeLog                      |  10 
parsers/src/carbons.rs                 |   8 
parsers/src/forwarding.rs              |   4 
parsers/src/mam.rs                     |   4 
parsers/src/message.rs                 | 307 ++++++++++++---------------
tokio-xmpp/examples/echo_bot.rs        |  12 
tokio-xmpp/examples/echo_component.rs  |   6 
xmpp/examples/hello_bot.rs             |   4 
xmpp/src/event.rs                      |  24 +-
xmpp/src/message/receive/group_chat.rs |   2 
xmpp/src/message/send.rs               |   4 
11 files changed, 183 insertions(+), 202 deletions(-)

Detailed changes

parsers/ChangeLog πŸ”—

@@ -74,6 +74,16 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
           - the `from_type` member was replaced by a pair of functions.
           - if the form contains a FORM_TYPE field, it will be part of the
             `fields` member (unlike before, where that field was removed).
+      - message::Message has been ported to use xso (!560). This causes several
+        breaking changes:
+
+        1. the message::Body and message::Subject newtypes have been removed
+           and replaced by bare Strings
+        2. a message::Lang newtype has been introduced for the language of
+           message parts.
+        3. the message::Thread type was converted from a single-element
+           tuple-like struct to a struct with named fields to support the
+           optional `parent` attribute.
     * New parsers/serialisers:
       - Stream Features (RFC 6120) (!400)
       - Spam Reporting (XEP-0377) (!506)

parsers/src/carbons.rs πŸ”—

@@ -67,8 +67,8 @@ mod tests {
         assert_size!(Enable, 0);
         assert_size!(Disable, 0);
         assert_size!(Private, 0);
-        assert_size!(Received, 140);
-        assert_size!(Sent, 140);
+        assert_size!(Received, 152);
+        assert_size!(Sent, 152);
     }
 
     #[cfg(target_pointer_width = "64")]
@@ -77,8 +77,8 @@ mod tests {
         assert_size!(Enable, 0);
         assert_size!(Disable, 0);
         assert_size!(Private, 0);
-        assert_size!(Received, 264);
-        assert_size!(Sent, 264);
+        assert_size!(Received, 288);
+        assert_size!(Sent, 288);
     }
 
     #[test]

parsers/src/forwarding.rs πŸ”—

@@ -37,13 +37,13 @@ mod tests {
     #[cfg(target_pointer_width = "32")]
     #[test]
     fn test_size() {
-        assert_size!(Forwarded, 140);
+        assert_size!(Forwarded, 152);
     }
 
     #[cfg(target_pointer_width = "64")]
     #[test]
     fn test_size() {
-        assert_size!(Forwarded, 264);
+        assert_size!(Forwarded, 288);
     }
 
     #[test]

parsers/src/mam.rs πŸ”—

@@ -150,7 +150,7 @@ mod tests {
     fn test_size() {
         assert_size!(QueryId, 12);
         assert_size!(Query, 108);
-        assert_size!(Result_, 164);
+        assert_size!(Result_, 176);
         assert_size!(Fin, 44);
         assert_size!(Start, 28);
         assert_size!(End, 28);
@@ -163,7 +163,7 @@ mod tests {
     fn test_size() {
         assert_size!(QueryId, 24);
         assert_size!(Query, 216);
-        assert_size!(Result_, 312);
+        assert_size!(Result_, 336);
         assert_size!(Fin, 88);
         assert_size!(Start, 40);
         assert_size!(End, 40);

parsers/src/message.rs πŸ”—

@@ -4,11 +4,18 @@
 // 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 core::fmt;
+use core::ops::{Deref, DerefMut};
+use std::borrow::{Borrow, Cow};
+
 use crate::ns;
 use alloc::collections::BTreeMap;
 use jid::Jid;
 use minidom::Element;
-use xso::error::{Error, FromElementError};
+use xso::{
+    error::{Error, FromElementError},
+    AsOptionalXmlText, AsXml, FromXml, FromXmlText,
+};
 
 /// Should be implemented on every known payload of a `<message/>`.
 pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
@@ -34,8 +41,6 @@ generate_attribute!(
     }, Default = Normal
 );
 
-type Lang = String;
-
 generate_id!(
     /// Id field in a [`Message`], if any.
     ///
@@ -44,59 +49,145 @@ generate_id!(
     Id
 );
 
-generate_elem_id!(
-    /// Represents one `<body/>` element, that is the free form text content of
-    /// a message.
-    Body,
-    "body",
-    DEFAULT_NS
-);
+/// Wrapper type to represent an optional `xml:lang` attribute.
+///
+/// This is necessary because we do not want to emit empty `xml:lang`
+/// attributes.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
+pub struct Lang(pub String);
 
-generate_elem_id!(
-    /// Defines the subject of a room, or of an email-like normal message.
-    Subject,
-    "subject",
-    DEFAULT_NS
-);
+impl Deref for Lang {
+    type Target = String;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for Lang {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl fmt::Display for Lang {
+    fn fmt<'f>(&self, f: &'f mut fmt::Formatter) -> fmt::Result {
+        fmt::Display::fmt(&self.0, f)
+    }
+}
+
+impl FromXmlText for Lang {
+    fn from_xml_text(s: String) -> Result<Self, Error> {
+        Ok(Self(s))
+    }
+}
+
+impl AsOptionalXmlText for Lang {
+    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, Error> {
+        if self.0.is_empty() {
+            Ok(None)
+        } else {
+            Ok(Some(Cow::Borrowed(&self.0)))
+        }
+    }
+}
+
+impl Borrow<str> for Lang {
+    fn borrow(&self) -> &str {
+        &self.0
+    }
+}
+
+impl From<String> for Lang {
+    fn from(other: String) -> Self {
+        Self(other)
+    }
+}
+
+impl From<&str> for Lang {
+    fn from(other: &str) -> Self {
+        Self(other.to_owned())
+    }
+}
+
+impl PartialEq<str> for Lang {
+    fn eq(&self, rhs: &str) -> bool {
+        self.0 == rhs
+    }
+}
+
+impl PartialEq<&str> for Lang {
+    fn eq(&self, rhs: &&str) -> bool {
+        self.0 == *rhs
+    }
+}
+
+impl Lang {
+    /// Create a new, empty `Lang`.
+    pub fn new() -> Self {
+        Self(String::new())
+    }
+}
+
+/// Threading meta-information.
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::DEFAULT_NS, name = "thread")]
+pub struct Thread {
+    /// The parent of the thread, when creating a subthread.
+    #[xml(attribute(default))]
+    pub parent: Option<String>,
 
-generate_elem_id!(
     /// A thread identifier, so that other people can specify to which message
     /// they are replying.
-    Thread,
-    "thread",
-    DEFAULT_NS
-);
+    #[xml(text)]
+    pub id: String,
+}
 
 /// The main structure representing the `<message/>` stanza.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::DEFAULT_NS, name = "message")]
 pub struct Message {
     /// The JID emitting this stanza.
+    #[xml(attribute(default))]
     pub from: Option<Jid>,
 
     /// The recipient of this stanza.
+    #[xml(attribute(default))]
     pub to: Option<Jid>,
 
     /// The @id attribute of this stanza, which is required in order to match a
     /// request with its response.
+    #[xml(attribute(default))]
     pub id: Option<Id>,
 
     /// The type of this message.
+    #[xml(attribute(name = "type", default))]
     pub type_: MessageType,
 
     /// A list of bodies, sorted per language.  Use
     /// [get_best_body()](#method.get_best_body) to access them on reception.
-    pub bodies: BTreeMap<Lang, Body>,
+    #[xml(extract(n = .., name = "body", fields(
+        attribute(name = "xml:lang", type_ = Lang, default),
+        text(type_ = String),
+    )))]
+    pub bodies: BTreeMap<Lang, String>,
 
     /// A list of subjects, sorted per language.  Use
     /// [get_best_subject()](#method.get_best_subject) to access them on
     /// reception.
-    pub subjects: BTreeMap<Lang, Subject>,
+    #[xml(extract(n = .., name = "subject", fields(
+        attribute(name = "xml:lang", type_ = Lang, default),
+        text(type_ = String),
+    )))]
+    pub subjects: BTreeMap<Lang, String>,
 
     /// An optional thread identifier, so that other people can reply directly
     /// to this message.
+    #[xml(child(default))]
     pub thread: Option<Thread>,
 
     /// A list of the extension payloads contained in this stanza.
+    #[xml(element(n = ..))]
     pub payloads: Vec<Element>,
 }
 
@@ -157,7 +248,7 @@ impl Message {
 
     /// Appends a body in given lang to the Message
     pub fn with_body(mut self, lang: Lang, body: String) -> Message {
-        self.bodies.insert(lang, Body(body));
+        self.bodies.insert(lang, body);
         self
     }
 
@@ -209,13 +300,13 @@ impl Message {
     /// `Some(("fr", the_second_body))` will be returned.
     ///
     /// If no body matches, an undefined body will be returned.
-    pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Body)> {
-        Message::get_best::<Body>(&self.bodies, preferred_langs)
+    pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &String)> {
+        Message::get_best::<String>(&self.bodies, preferred_langs)
     }
 
     /// Cloned variant of [`Message::get_best_body`]
-    pub fn get_best_body_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, Body)> {
-        Message::get_best_cloned::<Body>(&self.bodies, preferred_langs)
+    pub fn get_best_body_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, String)> {
+        Message::get_best_cloned::<String>(&self.bodies, preferred_langs)
     }
 
     /// Returns the best matching subject from a list of languages.
@@ -225,13 +316,13 @@ impl Message {
     /// languages, `Some(("fr", the_second_subject))` will be returned.
     ///
     /// If no subject matches, an undefined subject will be returned.
-    pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Subject)> {
-        Message::get_best::<Subject>(&self.subjects, preferred_langs)
+    pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &String)> {
+        Message::get_best::<String>(&self.subjects, preferred_langs)
     }
 
     /// Cloned variant of [`Message::get_best_subject`]
-    pub fn get_best_subject_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, Subject)> {
-        Message::get_best_cloned::<Subject>(&self.subjects, preferred_langs)
+    pub fn get_best_subject_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, String)> {
+        Message::get_best_cloned::<String>(&self.subjects, preferred_langs)
     }
 
     /// Try to extract the given payload type from the message's payloads.
@@ -287,141 +378,24 @@ impl Message {
     }
 }
 
-impl TryFrom<Element> for Message {
-    type Error = FromElementError;
-
-    fn try_from(root: Element) -> Result<Message, FromElementError> {
-        check_self!(root, "message", DEFAULT_NS);
-        let from = get_attr!(root, "from", Option);
-        let to = get_attr!(root, "to", Option);
-        let id = get_attr!(root, "id", Option);
-        let type_ = get_attr!(root, "type", Default);
-        let mut bodies = BTreeMap::new();
-        let mut subjects = BTreeMap::new();
-        let mut thread = None;
-        let mut payloads = vec![];
-        for elem in root.children() {
-            if elem.is("body", ns::DEFAULT_NS) {
-                check_no_children!(elem, "body");
-                let lang = get_attr!(elem, "xml:lang", Default);
-                let body = Body(elem.text());
-                if bodies.insert(lang, body).is_some() {
-                    return Err(
-                        Error::Other("Body element present twice for the same xml:lang.").into(),
-                    );
-                }
-            } else if elem.is("subject", ns::DEFAULT_NS) {
-                check_no_children!(elem, "subject");
-                let lang = get_attr!(elem, "xml:lang", Default);
-                let subject = Subject(elem.text());
-                if subjects.insert(lang, subject).is_some() {
-                    return Err(Error::Other(
-                        "Subject element present twice for the same xml:lang.",
-                    )
-                    .into());
-                }
-            } else if elem.is("thread", ns::DEFAULT_NS) {
-                if thread.is_some() {
-                    return Err(Error::Other("Thread element present twice.").into());
-                }
-                check_no_children!(elem, "thread");
-                thread = Some(Thread(elem.text()));
-            } else {
-                payloads.push(elem.clone())
-            }
-        }
-        Ok(Message {
-            from,
-            to,
-            id,
-            type_,
-            bodies,
-            subjects,
-            thread,
-            payloads,
-        })
-    }
-}
-
-impl From<Message> for Element {
-    fn from(message: Message) -> Element {
-        Element::builder("message", ns::DEFAULT_NS)
-            .attr("from", message.from)
-            .attr("to", message.to)
-            .attr("id", message.id)
-            .attr("type", message.type_)
-            .append_all(message.subjects.into_iter().map(|(lang, subject)| {
-                let mut subject = Element::from(subject);
-                subject.set_attr(
-                    "xml:lang",
-                    match lang.as_ref() {
-                        "" => None,
-                        lang => Some(lang),
-                    },
-                );
-                subject
-            }))
-            .append_all(message.bodies.into_iter().map(|(lang, body)| {
-                let mut body = Element::from(body);
-                body.set_attr(
-                    "xml:lang",
-                    match lang.as_ref() {
-                        "" => None,
-                        lang => Some(lang),
-                    },
-                );
-                body
-            }))
-            .append_all(message.payloads)
-            .build()
-    }
-}
-
-impl ::xso::FromXml for Message {
-    type Builder = ::xso::minidom_compat::FromEventsViaElement<Message>;
-
-    fn from_events(
-        qname: ::xso::exports::rxml::QName,
-        attrs: ::xso::exports::rxml::AttrMap,
-    ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
-        if qname.0 != crate::ns::DEFAULT_NS || qname.1 != "message" {
-            return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
-        }
-        Self::Builder::new(qname, attrs)
-    }
-}
-
-impl ::xso::AsXml for Message {
-    type ItemIter<'x> = ::xso::minidom_compat::AsItemsViaElement<'x>;
-
-    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, ::xso::error::Error> {
-        ::xso::minidom_compat::AsItemsViaElement::new(self.clone())
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
-    use core::str::FromStr;
 
     #[cfg(target_pointer_width = "32")]
     #[test]
     fn test_size() {
         assert_size!(MessageType, 1);
-        assert_size!(Body, 12);
-        assert_size!(Subject, 12);
-        assert_size!(Thread, 12);
-        assert_size!(Message, 96);
+        assert_size!(Thread, 24);
+        assert_size!(Message, 108);
     }
 
     #[cfg(target_pointer_width = "64")]
     #[test]
     fn test_size() {
         assert_size!(MessageType, 1);
-        assert_size!(Body, 24);
-        assert_size!(Subject, 24);
-        assert_size!(Thread, 24);
-        assert_size!(Message, 192);
+        assert_size!(Thread, 48);
+        assert_size!(Message, 216);
     }
 
     #[test]
@@ -462,12 +436,12 @@ mod tests {
         let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
         let elem1 = elem.clone();
         let message = Message::try_from(elem).unwrap();
-        assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap());
+        assert_eq!(message.bodies[""], "Hello world!");
 
         {
             let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
             assert_eq!(lang, "");
-            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
+            assert_eq!(body, &"Hello world!");
         }
 
         let elem2 = message.into();
@@ -483,7 +457,7 @@ mod tests {
         let mut message = Message::new(Jid::new("coucou@example.org").unwrap());
         message
             .bodies
-            .insert(String::from(""), Body::from_str("Hello world!").unwrap());
+            .insert(Lang::from(""), "Hello world!".to_owned());
         let elem2 = message.into();
         assert_eq!(elem, elem2);
     }
@@ -496,22 +470,19 @@ mod tests {
         let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
         let elem1 = elem.clone();
         let message = Message::try_from(elem).unwrap();
-        assert_eq!(
-            message.subjects[""],
-            Subject::from_str("Hello world!").unwrap()
-        );
+        assert_eq!(message.subjects[""], "Hello world!",);
 
         {
             let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
             assert_eq!(lang, "");
-            assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
+            assert_eq!(subject, "Hello world!");
         }
 
         // Test cloned variant.
         {
             let (lang, subject) = message.get_best_subject_cloned(vec!["en"]).unwrap();
             assert_eq!(lang, "");
-            assert_eq!(subject, Subject::from_str("Hello world!").unwrap());
+            assert_eq!(subject, "Hello world!");
         }
 
         let elem2 = message.into();
@@ -530,35 +501,35 @@ mod tests {
         {
             let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
             assert_eq!(lang, "fr");
-            assert_eq!(body, &Body::from_str("Salut le mondeβ€―!").unwrap());
+            assert_eq!(body, "Salut le mondeβ€―!");
         }
 
         // Tests order.
         {
             let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
             assert_eq!(lang, "de");
-            assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
+            assert_eq!(body, "Hallo Welt!");
         }
 
         // Tests fallback.
         {
             let (lang, body) = message.get_best_body(vec![]).unwrap();
             assert_eq!(lang, "");
-            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
+            assert_eq!(body, "Hello world!");
         }
 
         // Tests fallback.
         {
             let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
             assert_eq!(lang, "");
-            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
+            assert_eq!(body, "Hello world!");
         }
 
         // Test cloned variant.
         {
             let (lang, body) = message.get_best_body_cloned(vec!["ja"]).unwrap();
             assert_eq!(lang, "");
-            assert_eq!(body, Body::from_str("Hello world!").unwrap());
+            assert_eq!(body, "Hello world!");
         }
 
         let message = Message::new(None);

tokio-xmpp/examples/echo_bot.rs πŸ”—

@@ -4,7 +4,7 @@ use std::process::exit;
 use std::str::FromStr;
 use tokio_xmpp::Client;
 use xmpp_parsers::jid::{BareJid, Jid};
-use xmpp_parsers::message::{Body, Message, MessageType};
+use xmpp_parsers::message::{Lang, Message, MessageType};
 use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
 
 #[tokio::main]
@@ -39,14 +39,14 @@ async fn main() {
             .and_then(|stanza| Message::try_from(stanza).ok())
         {
             match (message.from, message.bodies.get("")) {
-                (Some(ref from), Some(ref body)) if body.0 == "die" => {
+                (Some(ref from), Some(body)) if body == "die" => {
                     println!("Secret die command triggered by {}", from);
                     break;
                 }
-                (Some(ref from), Some(ref body)) => {
+                (Some(ref from), Some(body)) => {
                     if message.type_ != MessageType::Error {
                         // This is a message we'll echo
-                        let reply = make_reply(from.clone(), &body.0);
+                        let reply = make_reply(from.clone(), body.to_owned());
                         client.send_stanza(reply.into()).await.unwrap();
                     }
                 }
@@ -69,8 +69,8 @@ fn make_presence() -> Presence {
 }
 
 // Construct a chat <message/>
-fn make_reply(to: Jid, body: &str) -> Message {
+fn make_reply(to: Jid, body: String) -> Message {
     let mut message = Message::new(Some(to));
-    message.bodies.insert(String::new(), Body(body.to_owned()));
+    message.bodies.insert(Lang::default(), body);
     message
 }

tokio-xmpp/examples/echo_component.rs πŸ”—

@@ -3,7 +3,7 @@ use std::env::args;
 use std::process::exit;
 use std::str::FromStr;
 use xmpp_parsers::jid::Jid;
-use xmpp_parsers::message::{Body, Message, MessageType};
+use xmpp_parsers::message::{Lang, Message, MessageType};
 use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
 
 use tokio_xmpp::{connect::DnsConfig, Component};
@@ -52,7 +52,7 @@ async fn main() {
                 match (message.from, message.bodies.get("")) {
                     (Some(from), Some(body)) => {
                         if message.type_ != MessageType::Error {
-                            let reply = make_reply(from, &body.0);
+                            let reply = make_reply(from, &body);
                             component.send_stanza(reply.into()).await.unwrap();
                         }
                     }
@@ -80,6 +80,6 @@ fn make_presence(from: Jid, to: Jid) -> Presence {
 // Construct a chat <message/>
 fn make_reply(to: Jid, body: &str) -> Message {
     let mut message = Message::new(Some(to));
-    message.bodies.insert(String::new(), Body(body.to_owned()));
+    message.bodies.insert(Lang::default(), body.to_owned());
     message
 }

xmpp/examples/hello_bot.rs πŸ”—

@@ -99,7 +99,7 @@ async fn handle_events(client: &mut Agent, event: Event, rooms: &Vec<BareJid>) {
                 "{} {}: {}",
                 time_info.received.time().format("%H:%M"),
                 jid,
-                body.0
+                body
             );
         }
         Event::RoomJoined(jid) => {
@@ -111,7 +111,7 @@ async fn handle_events(client: &mut Agent, event: Event, rooms: &Vec<BareJid>) {
         Event::RoomMessage(_id, jid, nick, body, time_info) => {
             println!(
                 "Message in room {} from {} at {}: {}",
-                jid, nick, time_info.received, body.0
+                jid, nick, time_info.received, body
             );
         }
         _ => {

xmpp/src/event.rs πŸ”—

@@ -7,7 +7,7 @@
 use tokio_xmpp::jid::BareJid;
 #[cfg(feature = "avatars")]
 use tokio_xmpp::jid::Jid;
-use tokio_xmpp::parsers::{message::Body, roster::Item as RosterItem};
+use tokio_xmpp::parsers::roster::Item as RosterItem;
 
 use crate::{delay::StanzaTimeInfo, Error, MessageId, RoomNick};
 
@@ -23,25 +23,25 @@ pub enum Event {
     /// A chat message was received. It may have been delayed on the network.
     /// - The [`MessageId`] is a unique identifier for this message.
     /// - The [`BareJid`] is the sender's JID.
-    /// - The [`Body`] is the message body.
+    /// - The [`String`] is the message body.
     /// - The [`StanzaTimeInfo`] about when message was received, and when the message was claimed sent.
-    ChatMessage(Option<MessageId>, BareJid, Body, StanzaTimeInfo),
+    ChatMessage(Option<MessageId>, BareJid, String, StanzaTimeInfo),
     /// A message in a one-to-one chat was corrected/edited.
     /// - The [`MessageId`] is the ID of the message that was corrected.
     /// - The [`BareJid`] is the JID of the other participant in the chat.
-    /// - The [`Body`] is the new body of the message, to replace the old one.
+    /// - The [`String`] is the new body of the message, to replace the old one.
     /// - The [`StanzaTimeInfo`] is the time the message correction was sent/received
-    ChatMessageCorrection(MessageId, BareJid, Body, StanzaTimeInfo),
+    ChatMessageCorrection(MessageId, BareJid, String, StanzaTimeInfo),
     RoomJoined(BareJid),
     RoomLeft(BareJid),
-    RoomMessage(Option<MessageId>, BareJid, RoomNick, Body, StanzaTimeInfo),
+    RoomMessage(Option<MessageId>, BareJid, RoomNick, String, StanzaTimeInfo),
     /// A message in a MUC was corrected/edited.
     /// - The [`MessageId`] is the ID of the message that was corrected.
     /// - The [`BareJid`] is the JID of the room where the message was sent.
     /// - The [`RoomNick`] is the nickname of the sender of the message.
-    /// - The [`Body`] is the new body of the message, to replace the old one.
+    /// - The [`String`] is the new body of the message, to replace the old one.
     /// - The [`StanzaTimeInfo`] is the time the message correction was sent/received
-    RoomMessageCorrection(MessageId, BareJid, RoomNick, Body, StanzaTimeInfo),
+    RoomMessageCorrection(MessageId, BareJid, RoomNick, String, StanzaTimeInfo),
     /// The subject of a room was received.
     /// - The BareJid is the room's address.
     /// - The RoomNick is the nickname of the room member who set the subject.
@@ -49,14 +49,14 @@ pub enum Event {
     RoomSubject(BareJid, Option<RoomNick>, String, StanzaTimeInfo),
     /// A private message received from a room, containing the message ID, the room's BareJid,
     /// the sender's nickname, and the message body.
-    RoomPrivateMessage(Option<MessageId>, BareJid, RoomNick, Body, StanzaTimeInfo),
+    RoomPrivateMessage(Option<MessageId>, BareJid, RoomNick, String, StanzaTimeInfo),
     /// A private message in a MUC was corrected/edited.
     /// - The [`MessageId`] is the ID of the message that was corrected.
     /// - The [`BareJid`] is the JID of the room where the message was sent.
     /// - The [`RoomNick`] is the nickname of the sender of the message.
-    /// - The [`Body`] is the new body of the message, to replace the old one.
+    /// - The [`String`] is the new body of the message, to replace the old one.
     /// - The [`StanzaTimeInfo`] is the time the message correction was sent/received
-    RoomPrivateMessageCorrection(MessageId, BareJid, RoomNick, Body, StanzaTimeInfo),
-    ServiceMessage(Option<MessageId>, BareJid, Body, StanzaTimeInfo),
+    RoomPrivateMessageCorrection(MessageId, BareJid, RoomNick, String, StanzaTimeInfo),
+    ServiceMessage(Option<MessageId>, BareJid, String, StanzaTimeInfo),
     HttpUploadedFile(String),
 }

xmpp/src/message/receive/group_chat.rs πŸ”—

@@ -25,7 +25,7 @@ pub async fn handle_message_group_chat(
         events.push(Event::RoomSubject(
             from.to_bare(),
             from.resource().map(RoomNick::from_resource_ref),
-            subject.0.clone(),
+            subject.clone(),
             time_info.clone(),
         ));
         found_subject = true;

xmpp/src/message/send.rs πŸ”—

@@ -7,7 +7,7 @@
 use crate::{
     jid::{BareJid, Jid},
     minidom::Element,
-    parsers::message::{Body, Message, MessagePayload, MessageType},
+    parsers::message::{Message, MessagePayload, MessageType},
 };
 
 use crate::Agent;
@@ -66,7 +66,7 @@ pub async fn send_raw_message<'a>(agent: &mut Agent, settings: RawMessageSetting
     stanza.type_ = message_type;
     stanza
         .bodies
-        .insert(lang.unwrap_or("").to_string(), Body(String::from(message)));
+        .insert(lang.unwrap_or("").into(), String::from(message));
     agent.client.send_stanza(stanza.into()).await.unwrap();
 }