1// Copyright (c) 2017 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/.
6
7use crate::util::error::Error;
8use crate::ns;
9use minidom::Element;
10use std::convert::TryFrom;
11
12/// Requests paging through a potentially big set of items (represented by an
13/// UID).
14#[derive(Debug, Clone)]
15pub struct SetQuery {
16 /// Limit the number of items, or use the recipient’s defaults if None.
17 pub max: Option<usize>,
18
19 /// The UID after which to give results, or if None it is the element
20 /// “before” the first item, effectively an index of negative one.
21 pub after: Option<String>,
22
23 /// The UID before which to give results, or if None it starts with the
24 /// last page of the full set.
25 pub before: Option<String>,
26
27 /// Numerical index of the page (deprecated).
28 pub index: Option<usize>,
29}
30
31impl TryFrom<Element> for SetQuery {
32 type Error = Error;
33
34 fn try_from(elem: Element) -> Result<SetQuery, Error> {
35 check_self!(elem, "set", RSM, "RSM set");
36 let mut set = SetQuery {
37 max: None,
38 after: None,
39 before: None,
40 index: None,
41 };
42 for child in elem.children() {
43 if child.is("max", ns::RSM) {
44 if set.max.is_some() {
45 return Err(Error::ParseError("Set can’t have more than one max."));
46 }
47 set.max = Some(child.text().parse()?);
48 } else if child.is("after", ns::RSM) {
49 if set.after.is_some() {
50 return Err(Error::ParseError("Set can’t have more than one after."));
51 }
52 set.after = Some(child.text());
53 } else if child.is("before", ns::RSM) {
54 if set.before.is_some() {
55 return Err(Error::ParseError("Set can’t have more than one before."));
56 }
57 set.before = Some(child.text());
58 } else if child.is("index", ns::RSM) {
59 if set.index.is_some() {
60 return Err(Error::ParseError("Set can’t have more than one index."));
61 }
62 set.index = Some(child.text().parse()?);
63 } else {
64 return Err(Error::ParseError("Unknown child in set element."));
65 }
66 }
67 Ok(set)
68 }
69}
70
71impl From<SetQuery> for Element {
72 fn from(set: SetQuery) -> Element {
73 Element::builder("set")
74 .ns(ns::RSM)
75 .append(set.max.map(|max| {
76 Element::builder("max")
77 .ns(ns::RSM)
78 .append(format!("{}", max))
79 .build()
80 }))
81 .append(
82 set.after
83 .map(|after| Element::builder("after").ns(ns::RSM).append(after).build()),
84 )
85 .append(set.before.map(|before| {
86 Element::builder("before")
87 .ns(ns::RSM)
88 .append(before)
89 .build()
90 }))
91 .append(set.index.map(|index| {
92 Element::builder("index")
93 .ns(ns::RSM)
94 .append(format!("{}", index))
95 .build()
96 }))
97 .build()
98 }
99}
100
101/// Describes the paging result of a [query](struct.SetQuery.html).
102#[derive(Debug, Clone)]
103pub struct SetResult {
104 /// The UID of the first item of the page.
105 pub first: Option<String>,
106
107 /// The position of the [first item](#structfield.first) in the full set
108 /// (which may be approximate).
109 pub first_index: Option<usize>,
110
111 /// The UID of the last item of the page.
112 pub last: Option<String>,
113
114 /// How many items there are in the full set (which may be approximate).
115 pub count: Option<usize>,
116}
117
118impl TryFrom<Element> for SetResult {
119 type Error = Error;
120
121 fn try_from(elem: Element) -> Result<SetResult, Error> {
122 check_self!(elem, "set", RSM, "RSM set");
123 let mut set = SetResult {
124 first: None,
125 first_index: None,
126 last: None,
127 count: None,
128 };
129 for child in elem.children() {
130 if child.is("first", ns::RSM) {
131 if set.first.is_some() {
132 return Err(Error::ParseError("Set can’t have more than one first."));
133 }
134 set.first_index = get_attr!(child, "index", Option);
135 set.first = Some(child.text());
136 } else if child.is("last", ns::RSM) {
137 if set.last.is_some() {
138 return Err(Error::ParseError("Set can’t have more than one last."));
139 }
140 set.last = Some(child.text());
141 } else if child.is("count", ns::RSM) {
142 if set.count.is_some() {
143 return Err(Error::ParseError("Set can’t have more than one count."));
144 }
145 set.count = Some(child.text().parse()?);
146 } else {
147 return Err(Error::ParseError("Unknown child in set element."));
148 }
149 }
150 Ok(set)
151 }
152}
153
154impl From<SetResult> for Element {
155 fn from(set: SetResult) -> Element {
156 let first = set.first.clone().map(|first| {
157 Element::builder("first")
158 .ns(ns::RSM)
159 .attr("index", set.first_index)
160 .append(first)
161 .build()
162 });
163 Element::builder("set")
164 .ns(ns::RSM)
165 .append(first)
166 .append(
167 set.last
168 .map(|last| Element::builder("last").ns(ns::RSM).append(last).build()),
169 )
170 .append(set.count.map(|count| {
171 Element::builder("count")
172 .ns(ns::RSM)
173 .append(format!("{}", count))
174 .build()
175 }))
176 .build()
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::util::compare_elements::NamespaceAwareCompare;
184
185 #[cfg(target_pointer_width = "32")]
186 #[test]
187 fn test_size() {
188 assert_size!(SetQuery, 40);
189 assert_size!(SetResult, 40);
190 }
191
192 #[cfg(target_pointer_width = "64")]
193 #[test]
194 fn test_size() {
195 assert_size!(SetQuery, 80);
196 assert_size!(SetResult, 80);
197 }
198
199 #[test]
200 fn test_simple() {
201 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'/>"
202 .parse()
203 .unwrap();
204 let set = SetQuery::try_from(elem).unwrap();
205 assert_eq!(set.max, None);
206 assert_eq!(set.after, None);
207 assert_eq!(set.before, None);
208 assert_eq!(set.index, None);
209
210 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'/>"
211 .parse()
212 .unwrap();
213 let set = SetResult::try_from(elem).unwrap();
214 match set.first {
215 Some(_) => panic!(),
216 None => (),
217 }
218 assert_eq!(set.last, None);
219 assert_eq!(set.count, None);
220 }
221
222 #[test]
223 fn test_unknown() {
224 let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
225 .parse()
226 .unwrap();
227 let error = SetQuery::try_from(elem).unwrap_err();
228 let message = match error {
229 Error::ParseError(string) => string,
230 _ => panic!(),
231 };
232 assert_eq!(message, "This is not a RSM set element.");
233
234 let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
235 .parse()
236 .unwrap();
237 let error = SetResult::try_from(elem).unwrap_err();
238 let message = match error {
239 Error::ParseError(string) => string,
240 _ => panic!(),
241 };
242 assert_eq!(message, "This is not a RSM set element.");
243 }
244
245 #[test]
246 fn test_invalid_child() {
247 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'><coucou/></set>"
248 .parse()
249 .unwrap();
250 let error = SetQuery::try_from(elem).unwrap_err();
251 let message = match error {
252 Error::ParseError(string) => string,
253 _ => panic!(),
254 };
255 assert_eq!(message, "Unknown child in set element.");
256
257 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'><coucou/></set>"
258 .parse()
259 .unwrap();
260 let error = SetResult::try_from(elem).unwrap_err();
261 let message = match error {
262 Error::ParseError(string) => string,
263 _ => panic!(),
264 };
265 assert_eq!(message, "Unknown child in set element.");
266 }
267
268 #[test]
269 fn test_serialise() {
270 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'/>"
271 .parse()
272 .unwrap();
273 let rsm = SetQuery {
274 max: None,
275 after: None,
276 before: None,
277 index: None,
278 };
279 let elem2 = rsm.into();
280 assert_eq!(elem, elem2);
281
282 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'/>"
283 .parse()
284 .unwrap();
285 let rsm = SetResult {
286 first: None,
287 first_index: None,
288 last: None,
289 count: None,
290 };
291 let elem2 = rsm.into();
292 assert_eq!(elem, elem2);
293 }
294
295 #[test]
296 fn test_first_index() {
297 let elem: Element =
298 "<set xmlns='http://jabber.org/protocol/rsm'><first index='4'>coucou</first></set>"
299 .parse()
300 .unwrap();
301 let elem1 = elem.clone();
302 let set = SetResult::try_from(elem).unwrap();
303 assert_eq!(set.first, Some(String::from("coucou")));
304 assert_eq!(set.first_index, Some(4));
305
306 let set2 = SetResult {
307 first: Some(String::from("coucou")),
308 first_index: Some(4),
309 last: None,
310 count: None,
311 };
312 let elem2 = set2.into();
313 assert!(elem1.compare_to(&elem2));
314 }
315}