parsers: port Message to use the derive macros
This commit is contained in:
parent
0fd69d2ff8
commit
5172fb5e0f
11 changed files with 183 additions and 202 deletions
|
|
@ -74,6 +74,16 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
|
||||||
- the `from_type` member was replaced by a pair of functions.
|
- 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
|
- if the form contains a FORM_TYPE field, it will be part of the
|
||||||
`fields` member (unlike before, where that field was removed).
|
`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:
|
* New parsers/serialisers:
|
||||||
- Stream Features (RFC 6120) (!400)
|
- Stream Features (RFC 6120) (!400)
|
||||||
- Spam Reporting (XEP-0377) (!506)
|
- Spam Reporting (XEP-0377) (!506)
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,8 @@ mod tests {
|
||||||
assert_size!(Enable, 0);
|
assert_size!(Enable, 0);
|
||||||
assert_size!(Disable, 0);
|
assert_size!(Disable, 0);
|
||||||
assert_size!(Private, 0);
|
assert_size!(Private, 0);
|
||||||
assert_size!(Received, 140);
|
assert_size!(Received, 152);
|
||||||
assert_size!(Sent, 140);
|
assert_size!(Sent, 152);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
|
@ -77,8 +77,8 @@ mod tests {
|
||||||
assert_size!(Enable, 0);
|
assert_size!(Enable, 0);
|
||||||
assert_size!(Disable, 0);
|
assert_size!(Disable, 0);
|
||||||
assert_size!(Private, 0);
|
assert_size!(Private, 0);
|
||||||
assert_size!(Received, 264);
|
assert_size!(Received, 288);
|
||||||
assert_size!(Sent, 264);
|
assert_size!(Sent, 288);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,13 @@ mod tests {
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(Forwarded, 140);
|
assert_size!(Forwarded, 152);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(Forwarded, 264);
|
assert_size!(Forwarded, 288);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ mod tests {
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(QueryId, 12);
|
assert_size!(QueryId, 12);
|
||||||
assert_size!(Query, 108);
|
assert_size!(Query, 108);
|
||||||
assert_size!(Result_, 164);
|
assert_size!(Result_, 176);
|
||||||
assert_size!(Fin, 44);
|
assert_size!(Fin, 44);
|
||||||
assert_size!(Start, 28);
|
assert_size!(Start, 28);
|
||||||
assert_size!(End, 28);
|
assert_size!(End, 28);
|
||||||
|
|
@ -163,7 +163,7 @@ mod tests {
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(QueryId, 24);
|
assert_size!(QueryId, 24);
|
||||||
assert_size!(Query, 216);
|
assert_size!(Query, 216);
|
||||||
assert_size!(Result_, 312);
|
assert_size!(Result_, 336);
|
||||||
assert_size!(Fin, 88);
|
assert_size!(Fin, 88);
|
||||||
assert_size!(Start, 40);
|
assert_size!(Start, 40);
|
||||||
assert_size!(End, 40);
|
assert_size!(End, 40);
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,18 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// 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/.
|
// 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 crate::ns;
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
use jid::Jid;
|
use jid::Jid;
|
||||||
use minidom::Element;
|
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/>`.
|
/// Should be implemented on every known payload of a `<message/>`.
|
||||||
pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
|
pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
|
||||||
|
|
@ -34,8 +41,6 @@ generate_attribute!(
|
||||||
}, Default = Normal
|
}, Default = Normal
|
||||||
);
|
);
|
||||||
|
|
||||||
type Lang = String;
|
|
||||||
|
|
||||||
generate_id!(
|
generate_id!(
|
||||||
/// Id field in a [`Message`], if any.
|
/// Id field in a [`Message`], if any.
|
||||||
///
|
///
|
||||||
|
|
@ -44,59 +49,145 @@ generate_id!(
|
||||||
Id
|
Id
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_elem_id!(
|
/// Wrapper type to represent an optional `xml:lang` attribute.
|
||||||
/// Represents one `<body/>` element, that is the free form text content of
|
///
|
||||||
/// a message.
|
/// This is necessary because we do not want to emit empty `xml:lang`
|
||||||
Body,
|
/// attributes.
|
||||||
"body",
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
||||||
DEFAULT_NS
|
pub struct Lang(pub String);
|
||||||
);
|
|
||||||
|
|
||||||
generate_elem_id!(
|
impl Deref for Lang {
|
||||||
/// Defines the subject of a room, or of an email-like normal message.
|
type Target = String;
|
||||||
Subject,
|
|
||||||
"subject",
|
fn deref(&self) -> &Self::Target {
|
||||||
DEFAULT_NS
|
&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
|
/// A thread identifier, so that other people can specify to which message
|
||||||
/// they are replying.
|
/// they are replying.
|
||||||
Thread,
|
#[xml(text)]
|
||||||
"thread",
|
pub id: String,
|
||||||
DEFAULT_NS
|
}
|
||||||
);
|
|
||||||
|
|
||||||
/// The main structure representing the `<message/>` stanza.
|
/// 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 {
|
pub struct Message {
|
||||||
/// The JID emitting this stanza.
|
/// The JID emitting this stanza.
|
||||||
|
#[xml(attribute(default))]
|
||||||
pub from: Option<Jid>,
|
pub from: Option<Jid>,
|
||||||
|
|
||||||
/// The recipient of this stanza.
|
/// The recipient of this stanza.
|
||||||
|
#[xml(attribute(default))]
|
||||||
pub to: Option<Jid>,
|
pub to: Option<Jid>,
|
||||||
|
|
||||||
/// The @id attribute of this stanza, which is required in order to match a
|
/// The @id attribute of this stanza, which is required in order to match a
|
||||||
/// request with its response.
|
/// request with its response.
|
||||||
|
#[xml(attribute(default))]
|
||||||
pub id: Option<Id>,
|
pub id: Option<Id>,
|
||||||
|
|
||||||
/// The type of this message.
|
/// The type of this message.
|
||||||
|
#[xml(attribute(name = "type", default))]
|
||||||
pub type_: MessageType,
|
pub type_: MessageType,
|
||||||
|
|
||||||
/// A list of bodies, sorted per language. Use
|
/// A list of bodies, sorted per language. Use
|
||||||
/// [get_best_body()](#method.get_best_body) to access them on reception.
|
/// [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
|
/// A list of subjects, sorted per language. Use
|
||||||
/// [get_best_subject()](#method.get_best_subject) to access them on
|
/// [get_best_subject()](#method.get_best_subject) to access them on
|
||||||
/// reception.
|
/// 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
|
/// An optional thread identifier, so that other people can reply directly
|
||||||
/// to this message.
|
/// to this message.
|
||||||
|
#[xml(child(default))]
|
||||||
pub thread: Option<Thread>,
|
pub thread: Option<Thread>,
|
||||||
|
|
||||||
/// A list of the extension payloads contained in this stanza.
|
/// A list of the extension payloads contained in this stanza.
|
||||||
|
#[xml(element(n = ..))]
|
||||||
pub payloads: Vec<Element>,
|
pub payloads: Vec<Element>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +248,7 @@ impl Message {
|
||||||
|
|
||||||
/// Appends a body in given lang to the Message
|
/// Appends a body in given lang to the Message
|
||||||
pub fn with_body(mut self, lang: Lang, body: String) -> Message {
|
pub fn with_body(mut self, lang: Lang, body: String) -> Message {
|
||||||
self.bodies.insert(lang, Body(body));
|
self.bodies.insert(lang, body);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,13 +300,13 @@ impl Message {
|
||||||
/// `Some(("fr", the_second_body))` will be returned.
|
/// `Some(("fr", the_second_body))` will be returned.
|
||||||
///
|
///
|
||||||
/// If no body matches, an undefined 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)> {
|
pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &String)> {
|
||||||
Message::get_best::<Body>(&self.bodies, preferred_langs)
|
Message::get_best::<String>(&self.bodies, preferred_langs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cloned variant of [`Message::get_best_body`]
|
/// Cloned variant of [`Message::get_best_body`]
|
||||||
pub fn get_best_body_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, Body)> {
|
pub fn get_best_body_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, String)> {
|
||||||
Message::get_best_cloned::<Body>(&self.bodies, preferred_langs)
|
Message::get_best_cloned::<String>(&self.bodies, preferred_langs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the best matching subject from a list of languages.
|
/// 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.
|
/// languages, `Some(("fr", the_second_subject))` will be returned.
|
||||||
///
|
///
|
||||||
/// If no subject matches, an undefined 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)> {
|
pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &String)> {
|
||||||
Message::get_best::<Subject>(&self.subjects, preferred_langs)
|
Message::get_best::<String>(&self.subjects, preferred_langs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cloned variant of [`Message::get_best_subject`]
|
/// Cloned variant of [`Message::get_best_subject`]
|
||||||
pub fn get_best_subject_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, Subject)> {
|
pub fn get_best_subject_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, String)> {
|
||||||
Message::get_best_cloned::<Subject>(&self.subjects, preferred_langs)
|
Message::get_best_cloned::<String>(&self.subjects, preferred_langs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to extract the given payload type from the message's payloads.
|
/// 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use core::str::FromStr;
|
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(MessageType, 1);
|
assert_size!(MessageType, 1);
|
||||||
assert_size!(Body, 12);
|
assert_size!(Thread, 24);
|
||||||
assert_size!(Subject, 12);
|
assert_size!(Message, 108);
|
||||||
assert_size!(Thread, 12);
|
|
||||||
assert_size!(Message, 96);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(MessageType, 1);
|
assert_size!(MessageType, 1);
|
||||||
assert_size!(Body, 24);
|
assert_size!(Thread, 48);
|
||||||
assert_size!(Subject, 24);
|
assert_size!(Message, 216);
|
||||||
assert_size!(Thread, 24);
|
|
||||||
assert_size!(Message, 192);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 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 elem1 = elem.clone();
|
||||||
let message = Message::try_from(elem).unwrap();
|
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();
|
let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
|
||||||
assert_eq!(lang, "");
|
assert_eq!(lang, "");
|
||||||
assert_eq!(body, &Body::from_str("Hello world!").unwrap());
|
assert_eq!(body, &"Hello world!");
|
||||||
}
|
}
|
||||||
|
|
||||||
let elem2 = message.into();
|
let elem2 = message.into();
|
||||||
|
|
@ -483,7 +457,7 @@ mod tests {
|
||||||
let mut message = Message::new(Jid::new("coucou@example.org").unwrap());
|
let mut message = Message::new(Jid::new("coucou@example.org").unwrap());
|
||||||
message
|
message
|
||||||
.bodies
|
.bodies
|
||||||
.insert(String::from(""), Body::from_str("Hello world!").unwrap());
|
.insert(Lang::from(""), "Hello world!".to_owned());
|
||||||
let elem2 = message.into();
|
let elem2 = message.into();
|
||||||
assert_eq!(elem, elem2);
|
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 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 elem1 = elem.clone();
|
||||||
let message = Message::try_from(elem).unwrap();
|
let message = Message::try_from(elem).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(message.subjects[""], "Hello world!",);
|
||||||
message.subjects[""],
|
|
||||||
Subject::from_str("Hello world!").unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
|
let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
|
||||||
assert_eq!(lang, "");
|
assert_eq!(lang, "");
|
||||||
assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
|
assert_eq!(subject, "Hello world!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test cloned variant.
|
// Test cloned variant.
|
||||||
{
|
{
|
||||||
let (lang, subject) = message.get_best_subject_cloned(vec!["en"]).unwrap();
|
let (lang, subject) = message.get_best_subject_cloned(vec!["en"]).unwrap();
|
||||||
assert_eq!(lang, "");
|
assert_eq!(lang, "");
|
||||||
assert_eq!(subject, Subject::from_str("Hello world!").unwrap());
|
assert_eq!(subject, "Hello world!");
|
||||||
}
|
}
|
||||||
|
|
||||||
let elem2 = message.into();
|
let elem2 = message.into();
|
||||||
|
|
@ -530,35 +501,35 @@ mod tests {
|
||||||
{
|
{
|
||||||
let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
|
let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
|
||||||
assert_eq!(lang, "fr");
|
assert_eq!(lang, "fr");
|
||||||
assert_eq!(body, &Body::from_str("Salut le monde !").unwrap());
|
assert_eq!(body, "Salut le monde !");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests order.
|
// Tests order.
|
||||||
{
|
{
|
||||||
let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
|
let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
|
||||||
assert_eq!(lang, "de");
|
assert_eq!(lang, "de");
|
||||||
assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
|
assert_eq!(body, "Hallo Welt!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests fallback.
|
// Tests fallback.
|
||||||
{
|
{
|
||||||
let (lang, body) = message.get_best_body(vec![]).unwrap();
|
let (lang, body) = message.get_best_body(vec![]).unwrap();
|
||||||
assert_eq!(lang, "");
|
assert_eq!(lang, "");
|
||||||
assert_eq!(body, &Body::from_str("Hello world!").unwrap());
|
assert_eq!(body, "Hello world!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests fallback.
|
// Tests fallback.
|
||||||
{
|
{
|
||||||
let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
|
let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
|
||||||
assert_eq!(lang, "");
|
assert_eq!(lang, "");
|
||||||
assert_eq!(body, &Body::from_str("Hello world!").unwrap());
|
assert_eq!(body, "Hello world!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test cloned variant.
|
// Test cloned variant.
|
||||||
{
|
{
|
||||||
let (lang, body) = message.get_best_body_cloned(vec!["ja"]).unwrap();
|
let (lang, body) = message.get_best_body_cloned(vec!["ja"]).unwrap();
|
||||||
assert_eq!(lang, "");
|
assert_eq!(lang, "");
|
||||||
assert_eq!(body, Body::from_str("Hello world!").unwrap());
|
assert_eq!(body, "Hello world!");
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = Message::new(None);
|
let message = Message::new(None);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::process::exit;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tokio_xmpp::Client;
|
use tokio_xmpp::Client;
|
||||||
use xmpp_parsers::jid::{BareJid, Jid};
|
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};
|
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -39,14 +39,14 @@ async fn main() {
|
||||||
.and_then(|stanza| Message::try_from(stanza).ok())
|
.and_then(|stanza| Message::try_from(stanza).ok())
|
||||||
{
|
{
|
||||||
match (message.from, message.bodies.get("")) {
|
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);
|
println!("Secret die command triggered by {}", from);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
(Some(ref from), Some(ref body)) => {
|
(Some(ref from), Some(body)) => {
|
||||||
if message.type_ != MessageType::Error {
|
if message.type_ != MessageType::Error {
|
||||||
// This is a message we'll echo
|
// 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();
|
client.send_stanza(reply.into()).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,8 +69,8 @@ fn make_presence() -> Presence {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a chat <message/>
|
// 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));
|
let mut message = Message::new(Some(to));
|
||||||
message.bodies.insert(String::new(), Body(body.to_owned()));
|
message.bodies.insert(Lang::default(), body);
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::env::args;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use xmpp_parsers::jid::Jid;
|
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 xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
||||||
|
|
||||||
use tokio_xmpp::{connect::DnsConfig, Component};
|
use tokio_xmpp::{connect::DnsConfig, Component};
|
||||||
|
|
@ -52,7 +52,7 @@ async fn main() {
|
||||||
match (message.from, message.bodies.get("")) {
|
match (message.from, message.bodies.get("")) {
|
||||||
(Some(from), Some(body)) => {
|
(Some(from), Some(body)) => {
|
||||||
if message.type_ != MessageType::Error {
|
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();
|
component.send_stanza(reply.into()).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -80,6 +80,6 @@ fn make_presence(from: Jid, to: Jid) -> Presence {
|
||||||
// Construct a chat <message/>
|
// Construct a chat <message/>
|
||||||
fn make_reply(to: Jid, body: &str) -> Message {
|
fn make_reply(to: Jid, body: &str) -> Message {
|
||||||
let mut message = Message::new(Some(to));
|
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
|
message
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ async fn handle_events(client: &mut Agent, event: Event, rooms: &Vec<BareJid>) {
|
||||||
"{} {}: {}",
|
"{} {}: {}",
|
||||||
time_info.received.time().format("%H:%M"),
|
time_info.received.time().format("%H:%M"),
|
||||||
jid,
|
jid,
|
||||||
body.0
|
body
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::RoomJoined(jid) => {
|
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) => {
|
Event::RoomMessage(_id, jid, nick, body, time_info) => {
|
||||||
println!(
|
println!(
|
||||||
"Message in room {} from {} at {}: {}",
|
"Message in room {} from {} at {}: {}",
|
||||||
jid, nick, time_info.received, body.0
|
jid, nick, time_info.received, body
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
use tokio_xmpp::jid::BareJid;
|
use tokio_xmpp::jid::BareJid;
|
||||||
#[cfg(feature = "avatars")]
|
#[cfg(feature = "avatars")]
|
||||||
use tokio_xmpp::jid::Jid;
|
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};
|
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.
|
/// A chat message was received. It may have been delayed on the network.
|
||||||
/// - The [`MessageId`] is a unique identifier for this message.
|
/// - The [`MessageId`] is a unique identifier for this message.
|
||||||
/// - The [`BareJid`] is the sender's JID.
|
/// - 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.
|
/// - 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.
|
/// A message in a one-to-one chat was corrected/edited.
|
||||||
/// - The [`MessageId`] is the ID of the message that was corrected.
|
/// - The [`MessageId`] is the ID of the message that was corrected.
|
||||||
/// - The [`BareJid`] is the JID of the other participant in the chat.
|
/// - 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
|
/// - The [`StanzaTimeInfo`] is the time the message correction was sent/received
|
||||||
ChatMessageCorrection(MessageId, BareJid, Body, StanzaTimeInfo),
|
ChatMessageCorrection(MessageId, BareJid, String, StanzaTimeInfo),
|
||||||
RoomJoined(BareJid),
|
RoomJoined(BareJid),
|
||||||
RoomLeft(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.
|
/// A message in a MUC was corrected/edited.
|
||||||
/// - The [`MessageId`] is the ID of the message that was corrected.
|
/// - 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 [`BareJid`] is the JID of the room where the message was sent.
|
||||||
/// - The [`RoomNick`] is the nickname of the sender of the message.
|
/// - 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
|
/// - 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 subject of a room was received.
|
||||||
/// - The BareJid is the room's address.
|
/// - The BareJid is the room's address.
|
||||||
/// - The RoomNick is the nickname of the room member who set the subject.
|
/// - 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),
|
RoomSubject(BareJid, Option<RoomNick>, String, StanzaTimeInfo),
|
||||||
/// A private message received from a room, containing the message ID, the room's BareJid,
|
/// A private message received from a room, containing the message ID, the room's BareJid,
|
||||||
/// the sender's nickname, and the message body.
|
/// 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.
|
/// A private message in a MUC was corrected/edited.
|
||||||
/// - The [`MessageId`] is the ID of the message that was corrected.
|
/// - 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 [`BareJid`] is the JID of the room where the message was sent.
|
||||||
/// - The [`RoomNick`] is the nickname of the sender of the message.
|
/// - 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
|
/// - The [`StanzaTimeInfo`] is the time the message correction was sent/received
|
||||||
RoomPrivateMessageCorrection(MessageId, BareJid, RoomNick, Body, StanzaTimeInfo),
|
RoomPrivateMessageCorrection(MessageId, BareJid, RoomNick, String, StanzaTimeInfo),
|
||||||
ServiceMessage(Option<MessageId>, BareJid, Body, StanzaTimeInfo),
|
ServiceMessage(Option<MessageId>, BareJid, String, StanzaTimeInfo),
|
||||||
HttpUploadedFile(String),
|
HttpUploadedFile(String),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ pub async fn handle_message_group_chat(
|
||||||
events.push(Event::RoomSubject(
|
events.push(Event::RoomSubject(
|
||||||
from.to_bare(),
|
from.to_bare(),
|
||||||
from.resource().map(RoomNick::from_resource_ref),
|
from.resource().map(RoomNick::from_resource_ref),
|
||||||
subject.0.clone(),
|
subject.clone(),
|
||||||
time_info.clone(),
|
time_info.clone(),
|
||||||
));
|
));
|
||||||
found_subject = true;
|
found_subject = true;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
jid::{BareJid, Jid},
|
jid::{BareJid, Jid},
|
||||||
minidom::Element,
|
minidom::Element,
|
||||||
parsers::message::{Body, Message, MessagePayload, MessageType},
|
parsers::message::{Message, MessagePayload, MessageType},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Agent;
|
use crate::Agent;
|
||||||
|
|
@ -66,7 +66,7 @@ pub async fn send_raw_message<'a>(agent: &mut Agent, settings: RawMessageSetting
|
||||||
stanza.type_ = message_type;
|
stanza.type_ = message_type;
|
||||||
stanza
|
stanza
|
||||||
.bodies
|
.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();
|
agent.client.send_stanza(stanza.into()).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue