1// Copyright (c) 2020 Paul Fariello <paul@fariello.eu>
2// Copyright (c) 2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8use crate::data_forms::DataForm;
9use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
10use crate::ns;
11use crate::pubsub::{AffiliationAttribute, NodeName, Subscription};
12use crate::util::error::Error;
13use crate::Element;
14use jid::Jid;
15use std::convert::TryFrom;
16
17generate_element!(
18 /// A list of affiliations you have on a service, or on a node.
19 Affiliations, "affiliations", PUBSUB_OWNER,
20 attributes: [
21 /// The node name this request pertains to.
22 node: Required<NodeName> = "node",
23 ],
24 children: [
25 /// The actual list of affiliation elements.
26 affiliations: Vec<Affiliation> = ("affiliation", PUBSUB_OWNER) => Affiliation
27 ]
28);
29
30generate_element!(
31 /// An affiliation element.
32 Affiliation, "affiliation", PUBSUB_OWNER,
33 attributes: [
34 /// The node this affiliation pertains to.
35 jid: Required<Jid> = "jid",
36
37 /// The affiliation you currently have on this node.
38 affiliation: Required<AffiliationAttribute> = "affiliation",
39 ]
40);
41
42generate_element!(
43 /// Request to configure a node.
44 Configure, "configure", PUBSUB_OWNER,
45 attributes: [
46 /// The node to be configured.
47 node: Option<NodeName> = "node",
48 ],
49 children: [
50 /// The form to configure it.
51 form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
52 ]
53);
54
55generate_element!(
56 /// Request to change default configuration.
57 Default, "default", PUBSUB_OWNER,
58 children: [
59 /// The form to configure it.
60 form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
61 ]
62);
63
64generate_element!(
65 /// Request to delete a node.
66 Delete, "delete", PUBSUB_OWNER,
67 attributes: [
68 /// The node to be configured.
69 node: Required<NodeName> = "node",
70 ],
71 children: [
72 /// Redirection to replace the deleted node.
73 redirect: Option<Redirect> = ("redirect", PUBSUB_OWNER) => Redirect
74 ]
75);
76
77generate_element!(
78 /// A redirect element.
79 Redirect, "redirect", PUBSUB_OWNER,
80 attributes: [
81 /// The node this node will be redirected to.
82 uri: Required<String> = "uri",
83 ]
84);
85
86generate_element!(
87 /// Request to delete a node.
88 Purge, "purge", PUBSUB_OWNER,
89 attributes: [
90 /// The node to be configured.
91 node: Required<NodeName> = "node",
92 ]
93);
94
95generate_element!(
96 /// A request for current subscriptions.
97 Subscriptions, "subscriptions", PUBSUB_OWNER,
98 attributes: [
99 /// The node to query.
100 node: Required<NodeName> = "node",
101 ],
102 children: [
103 /// The list of subscription elements returned.
104 subscriptions: Vec<SubscriptionElem> = ("subscription", PUBSUB_OWNER) => SubscriptionElem
105 ]
106);
107
108generate_element!(
109 /// A subscription element, describing the state of a subscription.
110 SubscriptionElem, "subscription", PUBSUB_OWNER,
111 attributes: [
112 /// The JID affected by this subscription.
113 jid: Required<Jid> = "jid",
114
115 /// The state of the subscription.
116 subscription: Required<Subscription> = "subscription",
117
118 /// Subscription unique id.
119 subid: Option<String> = "subid",
120 ]
121);
122
123/// Main payload used to communicate with a PubSubOwner service.
124///
125/// `<pubsub xmlns="http://jabber.org/protocol/pubsub#owner"/>`
126#[derive(Debug, Clone)]
127pub enum PubSubOwner {
128 /// Manage the affiliations of a node.
129 Affiliations(Affiliations),
130 /// Request to configure a node, with optional suggested name and suggested configuration.
131 Configure(Configure),
132 /// Request the default node configuration.
133 Default(Default),
134 /// Delete a node.
135 Delete(Delete),
136 /// Purge all items from node.
137 Purge(Purge),
138 /// Request subscriptions of a node.
139 Subscriptions(Subscriptions),
140}
141
142impl IqGetPayload for PubSubOwner {}
143impl IqSetPayload for PubSubOwner {}
144impl IqResultPayload for PubSubOwner {}
145
146impl TryFrom<Element> for PubSubOwner {
147 type Error = Error;
148
149 fn try_from(elem: Element) -> Result<PubSubOwner, Error> {
150 check_self!(elem, "pubsub", PUBSUB_OWNER);
151 check_no_attributes!(elem, "pubsub");
152
153 let mut payload = None;
154 for child in elem.children() {
155 if child.is("configure", ns::PUBSUB_OWNER) {
156 if payload.is_some() {
157 return Err(Error::ParseError(
158 "Payload is already defined in pubsub owner element.",
159 ));
160 }
161 let configure = Configure::try_from(child.clone())?;
162 payload = Some(PubSubOwner::Configure(configure));
163 } else {
164 return Err(Error::ParseError("Unknown child in pubsub element."));
165 }
166 }
167 payload.ok_or(Error::ParseError("No payload in pubsub element."))
168 }
169}
170
171impl From<PubSubOwner> for Element {
172 fn from(pubsub: PubSubOwner) -> Element {
173 Element::builder("pubsub", ns::PUBSUB_OWNER)
174 .append_all(match pubsub {
175 PubSubOwner::Affiliations(affiliations) => vec![Element::from(affiliations)],
176 PubSubOwner::Configure(configure) => vec![Element::from(configure)],
177 PubSubOwner::Default(default) => vec![Element::from(default)],
178 PubSubOwner::Delete(delete) => vec![Element::from(delete)],
179 PubSubOwner::Purge(purge) => vec![Element::from(purge)],
180 PubSubOwner::Subscriptions(subscriptions) => vec![Element::from(subscriptions)],
181 })
182 .build()
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crate::data_forms::{DataForm, DataFormType, Field, FieldType};
190 use jid::BareJid;
191 use std::str::FromStr;
192
193 #[test]
194 fn affiliations() {
195 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><affiliations node='foo'><affiliation jid='hamlet@denmark.lit' affiliation='owner'/><affiliation jid='polonius@denmark.lit' affiliation='outcast'/></affiliations></pubsub>"
196 .parse()
197 .unwrap();
198 let elem1 = elem.clone();
199
200 let pubsub = PubSubOwner::Affiliations(Affiliations {
201 node: NodeName(String::from("foo")),
202 affiliations: vec![
203 Affiliation {
204 jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()),
205 affiliation: AffiliationAttribute::Owner,
206 },
207 Affiliation {
208 jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()),
209 affiliation: AffiliationAttribute::Outcast,
210 },
211 ],
212 });
213
214 let elem2 = Element::from(pubsub);
215 assert_eq!(elem1, elem2);
216 }
217
218 #[test]
219 fn configure() {
220 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><configure node='foo'><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var='pubsub#access_model' type='list-single'><value>whitelist</value></field></x></configure></pubsub>"
221 .parse()
222 .unwrap();
223 let elem1 = elem.clone();
224
225 let pubsub = PubSubOwner::Configure(Configure {
226 node: Some(NodeName(String::from("foo"))),
227 form: Some(DataForm {
228 type_: DataFormType::Submit,
229 form_type: Some(String::from(ns::PUBSUB_CONFIGURE)),
230 title: None,
231 instructions: None,
232 fields: vec![Field {
233 var: String::from("pubsub#access_model"),
234 type_: FieldType::ListSingle,
235 label: None,
236 required: false,
237 options: vec![],
238 values: vec![String::from("whitelist")],
239 media: vec![],
240 }],
241 }),
242 });
243
244 let elem2 = Element::from(pubsub);
245 assert_eq!(elem1, elem2);
246 }
247
248 #[test]
249 fn test_serialize_configure() {
250 let reference: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><configure node='foo'><x xmlns='jabber:x:data' type='submit'/></configure></pubsub>"
251 .parse()
252 .unwrap();
253
254 let elem: Element = "<x xmlns='jabber:x:data' type='submit'/>".parse().unwrap();
255
256 let form = DataForm::try_from(elem).unwrap();
257
258 let configure = PubSubOwner::Configure(Configure {
259 node: Some(NodeName(String::from("foo"))),
260 form: Some(form),
261 });
262 let serialized: Element = configure.into();
263 assert_eq!(serialized, reference);
264 }
265
266 #[test]
267 fn default() {
268 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><default><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var='pubsub#access_model' type='list-single'><value>whitelist</value></field></x></default></pubsub>"
269 .parse()
270 .unwrap();
271 let elem1 = elem.clone();
272
273 let pubsub = PubSubOwner::Default(Default {
274 form: Some(DataForm {
275 type_: DataFormType::Submit,
276 form_type: Some(String::from(ns::PUBSUB_CONFIGURE)),
277 title: None,
278 instructions: None,
279 fields: vec![Field {
280 var: String::from("pubsub#access_model"),
281 type_: FieldType::ListSingle,
282 label: None,
283 required: false,
284 options: vec![],
285 values: vec![String::from("whitelist")],
286 media: vec![],
287 }],
288 }),
289 });
290
291 let elem2 = Element::from(pubsub);
292 assert_eq!(elem1, elem2);
293 }
294
295 #[test]
296 fn delete() {
297 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><delete node='foo'><redirect uri='xmpp:hamlet@denmark.lit?;node=blog'/></delete></pubsub>"
298 .parse()
299 .unwrap();
300 let elem1 = elem.clone();
301
302 let pubsub = PubSubOwner::Delete(Delete {
303 node: NodeName(String::from("foo")),
304 redirect: Some(Redirect {
305 uri: String::from("xmpp:hamlet@denmark.lit?;node=blog"),
306 }),
307 });
308
309 let elem2 = Element::from(pubsub);
310 assert_eq!(elem1, elem2);
311 }
312
313 #[test]
314 fn purge() {
315 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><purge node='foo'></purge></pubsub>"
316 .parse()
317 .unwrap();
318 let elem1 = elem.clone();
319
320 let pubsub = PubSubOwner::Purge(Purge {
321 node: NodeName(String::from("foo")),
322 });
323
324 let elem2 = Element::from(pubsub);
325 assert_eq!(elem1, elem2);
326 }
327
328 #[test]
329 fn subscriptions() {
330 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><subscriptions node='foo'><subscription jid='hamlet@denmark.lit' subscription='subscribed'/><subscription jid='polonius@denmark.lit' subscription='unconfigured'/><subscription jid='bernardo@denmark.lit' subscription='subscribed' subid='123-abc'/><subscription jid='bernardo@denmark.lit' subscription='subscribed' subid='004-yyy'/></subscriptions></pubsub>"
331 .parse()
332 .unwrap();
333 let elem1 = elem.clone();
334
335 let pubsub = PubSubOwner::Subscriptions(Subscriptions {
336 node: NodeName(String::from("foo")),
337 subscriptions: vec![
338 SubscriptionElem {
339 jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()),
340 subscription: Subscription::Subscribed,
341 subid: None,
342 },
343 SubscriptionElem {
344 jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()),
345 subscription: Subscription::Unconfigured,
346 subid: None,
347 },
348 SubscriptionElem {
349 jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()),
350 subscription: Subscription::Subscribed,
351 subid: Some(String::from("123-abc")),
352 },
353 SubscriptionElem {
354 jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()),
355 subscription: Subscription::Subscribed,
356 subid: Some(String::from("004-yyy")),
357 },
358 ],
359 });
360
361 let elem2 = Element::from(pubsub);
362 assert_eq!(elem1, elem2);
363 }
364}