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_all(set.max.map(|max| {
76 Element::builder("max")
77 .ns(ns::RSM)
78 .append(format!("{}", max))
79 }))
80 .append_all(
81 set.after
82 .map(|after| Element::builder("after").ns(ns::RSM).append(after))
83 )
84 .append_all(set.before.map(|before| {
85 Element::builder("before")
86 .ns(ns::RSM)
87 .append(before)
88 }))
89 .append_all(set.index.map(|index| {
90 Element::builder("index")
91 .ns(ns::RSM)
92 .append(format!("{}", index))
93 }))
94 .build()
95 }
96}
97
98/// Describes the paging result of a [query](struct.SetQuery.html).
99#[derive(Debug, Clone)]
100pub struct SetResult {
101 /// The UID of the first item of the page.
102 pub first: Option<String>,
103
104 /// The position of the [first item](#structfield.first) in the full set
105 /// (which may be approximate).
106 pub first_index: Option<usize>,
107
108 /// The UID of the last item of the page.
109 pub last: Option<String>,
110
111 /// How many items there are in the full set (which may be approximate).
112 pub count: Option<usize>,
113}
114
115impl TryFrom<Element> for SetResult {
116 type Error = Error;
117
118 fn try_from(elem: Element) -> Result<SetResult, Error> {
119 check_self!(elem, "set", RSM, "RSM set");
120 let mut set = SetResult {
121 first: None,
122 first_index: None,
123 last: None,
124 count: None,
125 };
126 for child in elem.children() {
127 if child.is("first", ns::RSM) {
128 if set.first.is_some() {
129 return Err(Error::ParseError("Set can’t have more than one first."));
130 }
131 set.first_index = get_attr!(child, "index", Option);
132 set.first = Some(child.text());
133 } else if child.is("last", ns::RSM) {
134 if set.last.is_some() {
135 return Err(Error::ParseError("Set can’t have more than one last."));
136 }
137 set.last = Some(child.text());
138 } else if child.is("count", ns::RSM) {
139 if set.count.is_some() {
140 return Err(Error::ParseError("Set can’t have more than one count."));
141 }
142 set.count = Some(child.text().parse()?);
143 } else {
144 return Err(Error::ParseError("Unknown child in set element."));
145 }
146 }
147 Ok(set)
148 }
149}
150
151impl From<SetResult> for Element {
152 fn from(set: SetResult) -> Element {
153 let first = set.first.clone().map(|first| {
154 Element::builder("first")
155 .ns(ns::RSM)
156 .attr("index", set.first_index)
157 .append(first)
158 });
159 Element::builder("set")
160 .ns(ns::RSM)
161 .append_all(first)
162 .append_all(
163 set.last
164 .map(|last| Element::builder("last").ns(ns::RSM).append(last)),
165 )
166 .append_all(set.count.map(|count| {
167 Element::builder("count")
168 .ns(ns::RSM)
169 .append(format!("{}", count))
170 }))
171 .build()
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::util::compare_elements::NamespaceAwareCompare;
179
180 #[cfg(target_pointer_width = "32")]
181 #[test]
182 fn test_size() {
183 assert_size!(SetQuery, 40);
184 assert_size!(SetResult, 40);
185 }
186
187 #[cfg(target_pointer_width = "64")]
188 #[test]
189 fn test_size() {
190 assert_size!(SetQuery, 80);
191 assert_size!(SetResult, 80);
192 }
193
194 #[test]
195 fn test_simple() {
196 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'/>"
197 .parse()
198 .unwrap();
199 let set = SetQuery::try_from(elem).unwrap();
200 assert_eq!(set.max, None);
201 assert_eq!(set.after, None);
202 assert_eq!(set.before, None);
203 assert_eq!(set.index, None);
204
205 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'/>"
206 .parse()
207 .unwrap();
208 let set = SetResult::try_from(elem).unwrap();
209 match set.first {
210 Some(_) => panic!(),
211 None => (),
212 }
213 assert_eq!(set.last, None);
214 assert_eq!(set.count, None);
215 }
216
217 #[test]
218 fn test_unknown() {
219 let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
220 .parse()
221 .unwrap();
222 let error = SetQuery::try_from(elem).unwrap_err();
223 let message = match error {
224 Error::ParseError(string) => string,
225 _ => panic!(),
226 };
227 assert_eq!(message, "This is not a RSM set element.");
228
229 let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
230 .parse()
231 .unwrap();
232 let error = SetResult::try_from(elem).unwrap_err();
233 let message = match error {
234 Error::ParseError(string) => string,
235 _ => panic!(),
236 };
237 assert_eq!(message, "This is not a RSM set element.");
238 }
239
240 #[test]
241 fn test_invalid_child() {
242 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'><coucou/></set>"
243 .parse()
244 .unwrap();
245 let error = SetQuery::try_from(elem).unwrap_err();
246 let message = match error {
247 Error::ParseError(string) => string,
248 _ => panic!(),
249 };
250 assert_eq!(message, "Unknown child in set element.");
251
252 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'><coucou/></set>"
253 .parse()
254 .unwrap();
255 let error = SetResult::try_from(elem).unwrap_err();
256 let message = match error {
257 Error::ParseError(string) => string,
258 _ => panic!(),
259 };
260 assert_eq!(message, "Unknown child in set element.");
261 }
262
263 #[test]
264 fn test_serialise() {
265 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'/>"
266 .parse()
267 .unwrap();
268 let rsm = SetQuery {
269 max: None,
270 after: None,
271 before: None,
272 index: None,
273 };
274 let elem2 = rsm.into();
275 assert_eq!(elem, elem2);
276
277 let elem: Element = "<set xmlns='http://jabber.org/protocol/rsm'/>"
278 .parse()
279 .unwrap();
280 let rsm = SetResult {
281 first: None,
282 first_index: None,
283 last: None,
284 count: None,
285 };
286 let elem2 = rsm.into();
287 assert_eq!(elem, elem2);
288 }
289
290 #[test]
291 fn test_first_index() {
292 let elem: Element =
293 "<set xmlns='http://jabber.org/protocol/rsm'><first index='4'>coucou</first></set>"
294 .parse()
295 .unwrap();
296 let elem1 = elem.clone();
297 let set = SetResult::try_from(elem).unwrap();
298 assert_eq!(set.first, Some(String::from("coucou")));
299 assert_eq!(set.first_index, Some(4));
300
301 let set2 = SetResult {
302 first: Some(String::from("coucou")),
303 first_index: Some(4),
304 last: None,
305 count: None,
306 };
307 let elem2 = set2.into();
308 assert!(elem1.compare_to(&elem2));
309 }
310}