diff --git a/parsers/ChangeLog b/parsers/ChangeLog index de221154..ce2d8409 100644 --- a/parsers/ChangeLog +++ b/parsers/ChangeLog @@ -25,6 +25,9 @@ XXXX-YY-ZZ RELEASER change it again once any other specification allows e.g. presences or iqs. - Module `jingle_thumnails` renamed to `jingle_thumbnails`. + - The bookmarks2 Conference `extensions` child is now an + `Option` instead of a `Vec`, to distinguish + between it being absent or empty (!472). * New parsers/serialisers: - Stream Features (RFC 6120) (!400) - Extensible SASL Profile (XEP-0388) diff --git a/parsers/src/bookmarks.rs b/parsers/src/bookmarks.rs index c79af6b3..fdcde6c5 100644 --- a/parsers/src/bookmarks.rs +++ b/parsers/src/bookmarks.rs @@ -20,7 +20,7 @@ use xso::{AsXml, FromXml}; use jid::BareJid; -pub use crate::bookmarks2::Autojoin; +pub use crate::bookmarks2::{self, Autojoin}; use crate::ns; /// A conference bookmark. @@ -51,15 +51,15 @@ pub struct Conference { impl Conference { /// Turns a XEP-0048 Conference element into a XEP-0402 "Bookmarks2" Conference element, in a /// tuple with the room JID. - pub fn into_bookmarks2(self) -> (BareJid, crate::bookmarks2::Conference) { + pub fn into_bookmarks2(self) -> (BareJid, bookmarks2::Conference) { ( self.jid, - crate::bookmarks2::Conference { + bookmarks2::Conference { autojoin: self.autojoin, name: self.name, nick: self.nick, password: self.password, - extensions: vec![], + extensions: None, }, ) } diff --git a/parsers/src/bookmarks2.rs b/parsers/src/bookmarks2.rs index f55cecd8..32a49529 100644 --- a/parsers/src/bookmarks2.rs +++ b/parsers/src/bookmarks2.rs @@ -14,9 +14,10 @@ //! //! This module exposes the [`Autojoin`][crate::bookmarks2::Autojoin] boolean flag, the [`Conference`][crate::bookmarks2::Conference] chatroom element, and the [BOOKMARKS2][crate::ns::BOOKMARKS2] XML namespace. +use xso::{AsXml, FromXml}; + use crate::ns; use minidom::Element; -use xso::error::{Error, FromElementError}; generate_attribute!( /// Whether a conference bookmark should be joined automatically. @@ -25,23 +26,38 @@ generate_attribute!( bool ); +/// Potential extensions in a conference. +#[derive(FromXml, AsXml, Debug, Clone, Default)] +#[xml(namespace = ns::BOOKMARKS2, name = "extensions")] +pub struct Extensions { + /// Extension elements. + #[xml(element(n = ..))] + pub payloads: Vec, +} + /// A conference bookmark. -#[derive(Debug, Clone, Default)] +#[derive(FromXml, AsXml, Debug, Clone, Default)] +#[xml(namespace = ns::BOOKMARKS2, name = "conference")] pub struct Conference { /// Whether a conference bookmark should be joined automatically. + #[xml(attribute(default))] pub autojoin: Autojoin, /// A user-defined name for this conference. + #[xml(attribute(default))] pub name: Option, /// The nick the user will use to join this conference. + #[xml(extract(default, fields(text(type_ = String))))] pub nick: Option, /// The password required to join this conference. + #[xml(extract(default, fields(text(type_ = String))))] pub password: Option, - /// Extensions elements. - pub extensions: Vec, + /// Extension elements. + #[xml(child(default))] + pub extensions: Option, } impl Conference { @@ -51,80 +67,6 @@ impl Conference { } } -impl TryFrom for Conference { - type Error = FromElementError; - - fn try_from(root: Element) -> Result { - check_self!(root, "conference", BOOKMARKS2, "Conference"); - check_no_unknown_attributes!(root, "Conference", ["autojoin", "name"]); - - let mut conference = Conference { - autojoin: get_attr!(root, "autojoin", Default), - name: get_attr!(root, "name", Option), - nick: None, - password: None, - extensions: Vec::new(), - }; - - for child in root.children() { - if child.is("nick", ns::BOOKMARKS2) { - if conference.nick.is_some() { - return Err(Error::Other("Conference must not have more than one nick.").into()); - } - check_no_children!(child, "nick"); - check_no_attributes!(child, "nick"); - conference.nick = Some(child.text()); - } else if child.is("password", ns::BOOKMARKS2) { - if conference.password.is_some() { - return Err( - Error::Other("Conference must not have more than one password.").into(), - ); - } - check_no_children!(child, "password"); - check_no_attributes!(child, "password"); - conference.password = Some(child.text()); - } else if child.is("extensions", ns::BOOKMARKS2) { - if !conference.extensions.is_empty() { - return Err(Error::Other( - "Conference must not have more than one extensions element.", - ) - .into()); - } - conference.extensions.extend(child.children().cloned()); - } else { - return Err(Error::Other("Unknown element in bookmarks2 conference").into()); - } - } - - Ok(conference) - } -} - -impl From for Element { - fn from(conference: Conference) -> Element { - Element::builder("conference", ns::BOOKMARKS2) - .attr("autojoin", conference.autojoin) - .attr("name", conference.name) - .append_all( - conference - .nick - .map(|nick| Element::builder("nick", ns::BOOKMARKS2).append(nick)), - ) - .append_all( - conference - .password - .map(|password| Element::builder("password", ns::BOOKMARKS2).append(password)), - ) - .append_all(match conference.extensions { - empty if empty.is_empty() => None, - extensions => { - Some(Element::builder("extensions", ns::BOOKMARKS2).append_all(extensions)) - } - }) - .build() - } -} - #[cfg(test)] mod tests { use super::*; @@ -166,8 +108,9 @@ mod tests { assert_eq!(conference.name, Some(String::from("Test MUC"))); assert_eq!(conference.clone().nick.unwrap(), "Coucou"); assert_eq!(conference.clone().password.unwrap(), "secret"); - assert_eq!(conference.clone().extensions.len(), 1); - assert!(conference.clone().extensions[0].is("test", "urn:xmpp:unknown")); + let payloads = conference.clone().extensions.unwrap().payloads; + assert_eq!(payloads.len(), 1); + assert!(payloads[0].is("test", "urn:xmpp:unknown")); } #[test]