1// Copyright (c) 2019 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/.
6use crate::ns;
7use crate::util::error::Error;
8use crate::Element;
9use std::convert::TryFrom;
10
11generate_attribute!(
12 /// Whether a conference bookmark should be joined automatically.
13 Autojoin,
14 "autojoin",
15 bool
16);
17
18/// A conference bookmark.
19#[derive(Debug, Clone, Default)]
20pub struct Conference {
21 /// Whether a conference bookmark should be joined automatically.
22 pub autojoin: Autojoin,
23
24 /// A user-defined name for this conference.
25 pub name: Option<String>,
26
27 /// The nick the user will use to join this conference.
28 pub nick: Option<String>,
29
30 /// The password required to join this conference.
31 pub password: Option<String>,
32
33 /// Extensions elements.
34 pub extensions: Vec<Element>,
35}
36
37impl Conference {
38 /// Create a new conference.
39 pub fn new() -> Conference {
40 Conference::default()
41 }
42}
43
44impl TryFrom<Element> for Conference {
45 type Error = Error;
46
47 fn try_from(root: Element) -> Result<Conference, Error> {
48 check_self!(root, "conference", BOOKMARKS2, "Conference");
49 check_no_unknown_attributes!(root, "Conference", ["autojoin", "name"]);
50
51 let mut conference = Conference {
52 autojoin: get_attr!(root, "autojoin", Default),
53 name: get_attr!(root, "name", Option),
54 nick: None,
55 password: None,
56 extensions: Vec::new(),
57 };
58
59 for child in root.children().cloned() {
60 if child.is("nick", ns::BOOKMARKS2) {
61 if conference.nick.is_some() {
62 return Err(Error::ParseError(
63 "Conference must not have more than one nick.",
64 ));
65 }
66 check_no_children!(child, "nick");
67 check_no_attributes!(child, "nick");
68 conference.nick = Some(child.text());
69 } else if child.is("password", ns::BOOKMARKS2) {
70 if conference.password.is_some() {
71 return Err(Error::ParseError(
72 "Conference must not have more than one password.",
73 ));
74 }
75 check_no_children!(child, "password");
76 check_no_attributes!(child, "password");
77 conference.password = Some(child.text());
78 } else if child.is("extensions", ns::BOOKMARKS2) {
79 if !conference.extensions.is_empty() {
80 return Err(Error::ParseError(
81 "Conference must not have more than one extensions element.",
82 ));
83 }
84 conference.extensions.extend(child.children().cloned());
85 } else {
86 return Err(Error::ParseError(
87 "Unknown element in bookmarks2 conference",
88 ));
89 }
90 }
91
92 Ok(conference)
93 }
94}
95
96impl From<Conference> for Element {
97 fn from(conference: Conference) -> Element {
98 Element::builder("conference", ns::BOOKMARKS2)
99 .attr("autojoin", conference.autojoin)
100 .attr("name", conference.name)
101 .append_all(
102 conference
103 .nick
104 .map(|nick| Element::builder("nick", ns::BOOKMARKS2).append(nick)),
105 )
106 .append_all(
107 conference
108 .password
109 .map(|password| Element::builder("password", ns::BOOKMARKS2).append(password)),
110 )
111 .append_all(match conference.extensions {
112 empty if empty.is_empty() => None,
113 extensions => {
114 Some(Element::builder("extensions", ns::BOOKMARKS2).append_all(extensions))
115 }
116 })
117 .build()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::pubsub::{pubsub::Item as PubSubItem, PubSubEvent};
125 use crate::Element;
126 use std::convert::TryFrom;
127
128 #[cfg(target_pointer_width = "32")]
129 #[test]
130 fn test_size() {
131 assert_size!(Conference, 52);
132 }
133
134 #[cfg(target_pointer_width = "64")]
135 #[test]
136 fn test_size() {
137 assert_size!(Conference, 104);
138 }
139
140 #[test]
141 fn simple() {
142 let elem: Element = "<conference xmlns='urn:xmpp:bookmarks:1'/>"
143 .parse()
144 .unwrap();
145 let elem1 = elem.clone();
146 let conference = Conference::try_from(elem).unwrap();
147 assert_eq!(conference.autojoin, Autojoin::False);
148 assert_eq!(conference.name, None);
149 assert_eq!(conference.nick, None);
150 assert_eq!(conference.password, None);
151
152 let elem2 = Element::from(Conference::new());
153 assert_eq!(elem1, elem2);
154 }
155
156 #[test]
157 fn complete() {
158 let elem: Element = "<conference xmlns='urn:xmpp:bookmarks:1' autojoin='true' name='Test MUC'><nick>Coucou</nick><password>secret</password><extensions><test xmlns='urn:xmpp:unknown' /></extensions></conference>".parse().unwrap();
159 let conference = Conference::try_from(elem).unwrap();
160 assert_eq!(conference.autojoin, Autojoin::True);
161 assert_eq!(conference.name, Some(String::from("Test MUC")));
162 assert_eq!(conference.clone().nick.unwrap(), "Coucou");
163 assert_eq!(conference.clone().password.unwrap(), "secret");
164 assert_eq!(conference.clone().extensions.len(), 1);
165 assert!(conference.clone().extensions[0].is("test", "urn:xmpp:unknown"));
166 }
167
168 #[test]
169 fn wrapped() {
170 let elem: Element = "<item xmlns='http://jabber.org/protocol/pubsub' id='test-muc@muc.localhost'><conference xmlns='urn:xmpp:bookmarks:1' autojoin='true' name='Test MUC'><nick>Coucou</nick><password>secret</password></conference></item>".parse().unwrap();
171 let item = PubSubItem::try_from(elem).unwrap();
172 let payload = item.payload.clone().unwrap();
173 println!("FOO: payload: {:?}", payload);
174 // let conference = Conference::try_from(payload).unwrap();
175 let conference = Conference::try_from(payload).unwrap();
176 println!("FOO: conference: {:?}", conference);
177 assert_eq!(conference.autojoin, Autojoin::True);
178 assert_eq!(conference.name, Some(String::from("Test MUC")));
179 assert_eq!(conference.clone().nick.unwrap(), "Coucou");
180 assert_eq!(conference.clone().password.unwrap(), "secret");
181
182 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='urn:xmpp:bookmarks:1'><item xmlns='http://jabber.org/protocol/pubsub#event' id='test-muc@muc.localhost'><conference xmlns='urn:xmpp:bookmarks:1' autojoin='true' name='Test MUC'><nick>Coucou</nick><password>secret</password></conference></item></items></event>".parse().unwrap();
183 let mut items = match PubSubEvent::try_from(elem) {
184 Ok(PubSubEvent::PublishedItems { node, items }) => {
185 assert_eq!(&node.0, ns::BOOKMARKS2);
186 items
187 }
188 _ => panic!(),
189 };
190 assert_eq!(items.len(), 1);
191 let item = items.pop().unwrap();
192 let payload = item.payload.clone().unwrap();
193 let conference = Conference::try_from(payload).unwrap();
194 assert_eq!(conference.autojoin, Autojoin::True);
195 assert_eq!(conference.name, Some(String::from("Test MUC")));
196 assert_eq!(conference.clone().nick.unwrap(), "Coucou");
197 assert_eq!(conference.clone().password.unwrap(), "secret");
198 }
199}