// Copyright (c) 2017 Emmanuel Gil Peyrot // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::ns; use minidom::Element; use xso::{ error::{Error, FromElementError, FromEventsError}, exports::rxml, minidom_compat, AsXml, FromXml, }; /// Requests paging through a potentially big set of items (represented by an /// UID). #[derive(Debug, Clone, PartialEq)] pub struct SetQuery { /// Limit the number of items, or use the recipient’s defaults if None. pub max: Option, /// The UID after which to give results, or if None it is the element /// “before” the first item, effectively an index of negative one. pub after: Option, /// The UID before which to give results, or if None it starts with the /// last page of the full set. pub before: Option, /// Numerical index of the page (deprecated). pub index: Option, } impl TryFrom for SetQuery { type Error = FromElementError; fn try_from(elem: Element) -> Result { check_self!(elem, "set", RSM, "RSM set"); let mut set = SetQuery { max: None, after: None, before: None, index: None, }; for child in elem.children() { if child.is("max", ns::RSM) { if set.max.is_some() { return Err(Error::Other("Set can’t have more than one max.").into()); } set.max = Some(child.text().parse().map_err(Error::text_parse_error)?); } else if child.is("after", ns::RSM) { if set.after.is_some() { return Err(Error::Other("Set can’t have more than one after.").into()); } set.after = Some(child.text()); } else if child.is("before", ns::RSM) { if set.before.is_some() { return Err(Error::Other("Set can’t have more than one before.").into()); } set.before = Some(child.text()); } else if child.is("index", ns::RSM) { if set.index.is_some() { return Err(Error::Other("Set can’t have more than one index.").into()); } set.index = Some(child.text().parse().map_err(Error::text_parse_error)?); } else { return Err(Error::Other("Unknown child in set element.").into()); } } Ok(set) } } impl FromXml for SetQuery { type Builder = minidom_compat::FromEventsViaElement; fn from_events( qname: rxml::QName, attrs: rxml::AttrMap, ) -> Result { if qname.0 != crate::ns::RSM || qname.1 != "set" { return Err(FromEventsError::Mismatch { name: qname, attrs }); } Self::Builder::new(qname, attrs) } } impl From for Element { fn from(set: SetQuery) -> Element { Element::builder("set", ns::RSM) .append_all( set.max .map(|max| Element::builder("max", ns::RSM).append(format!("{}", max))), ) .append_all( set.after .map(|after| Element::builder("after", ns::RSM).append(after)), ) .append_all(set.before.map(|before| { let mut builder = Element::builder("before", ns::RSM); if !before.is_empty() { builder = builder.append(before); } builder })) .append_all( set.index .map(|index| Element::builder("index", ns::RSM).append(format!("{}", index))), ) .build() } } impl AsXml for SetQuery { type ItemIter<'x> = minidom_compat::AsItemsViaElement<'x>; fn as_xml_iter(&self) -> Result, Error> { minidom_compat::AsItemsViaElement::new(self.clone()) } } /// Describes the paging result of a [query](struct.SetQuery.html). #[derive(Debug, Clone, PartialEq)] pub struct SetResult { /// The UID of the first item of the page. pub first: Option, /// The position of the [first item](#structfield.first) in the full set /// (which may be approximate). pub first_index: Option, /// The UID of the last item of the page. pub last: Option, /// How many items there are in the full set (which may be approximate). pub count: Option, } impl TryFrom for SetResult { type Error = FromElementError; fn try_from(elem: Element) -> Result { check_self!(elem, "set", RSM, "RSM set"); let mut set = SetResult { first: None, first_index: None, last: None, count: None, }; for child in elem.children() { if child.is("first", ns::RSM) { if set.first.is_some() { return Err(Error::Other("Set can’t have more than one first.").into()); } set.first_index = get_attr!(child, "index", Option); set.first = Some(child.text()); } else if child.is("last", ns::RSM) { if set.last.is_some() { return Err(Error::Other("Set can’t have more than one last.").into()); } set.last = Some(child.text()); } else if child.is("count", ns::RSM) { if set.count.is_some() { return Err(Error::Other("Set can’t have more than one count.").into()); } set.count = Some(child.text().parse().map_err(Error::text_parse_error)?); } else { return Err(Error::Other("Unknown child in set element.").into()); } } Ok(set) } } impl FromXml for SetResult { type Builder = minidom_compat::FromEventsViaElement; fn from_events( qname: rxml::QName, attrs: rxml::AttrMap, ) -> Result { if qname.0 != crate::ns::RSM || qname.1 != "set" { return Err(FromEventsError::Mismatch { name: qname, attrs }); } Self::Builder::new(qname, attrs) } } impl From for Element { fn from(set: SetResult) -> Element { let first = set.first.clone().map(|first| { Element::builder("first", ns::RSM) .attr("index", set.first_index) .append(first) }); Element::builder("set", ns::RSM) .append_all(first) .append_all( set.last .map(|last| Element::builder("last", ns::RSM).append(last)), ) .append_all( set.count .map(|count| Element::builder("count", ns::RSM).append(format!("{}", count))), ) .build() } } impl AsXml for SetResult { type ItemIter<'x> = minidom_compat::AsItemsViaElement<'x>; fn as_xml_iter(&self) -> Result, Error> { minidom_compat::AsItemsViaElement::new(self.clone()) } } #[cfg(test)] mod tests { use super::*; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { assert_size!(SetQuery, 40); assert_size!(SetResult, 40); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { assert_size!(SetQuery, 80); assert_size!(SetResult, 80); } #[test] fn test_simple() { let elem: Element = "" .parse() .unwrap(); let set = SetQuery::try_from(elem).unwrap(); assert_eq!(set.max, None); assert_eq!(set.after, None); assert_eq!(set.before, None); assert_eq!(set.index, None); let elem: Element = "" .parse() .unwrap(); let set = SetResult::try_from(elem).unwrap(); match set.first { Some(_) => panic!(), None => (), } assert_eq!(set.last, None); assert_eq!(set.count, None); } #[test] fn test_unknown() { let elem: Element = "" .parse() .unwrap(); let error = SetQuery::try_from(elem.clone()).unwrap_err(); let returned_elem = match error { FromElementError::Mismatch(elem) => elem, _ => panic!(), }; assert_eq!(elem, returned_elem); let elem: Element = "" .parse() .unwrap(); let error = SetResult::try_from(elem.clone()).unwrap_err(); let returned_elem = match error { FromElementError::Mismatch(elem) => elem, _ => panic!(), }; assert_eq!(elem, returned_elem); } #[test] fn test_invalid_child() { let elem: Element = "" .parse() .unwrap(); let error = SetQuery::try_from(elem).unwrap_err(); let message = match error { FromElementError::Invalid(Error::Other(string)) => string, _ => panic!(), }; assert_eq!(message, "Unknown child in set element."); let elem: Element = "" .parse() .unwrap(); let error = SetResult::try_from(elem).unwrap_err(); let message = match error { FromElementError::Invalid(Error::Other(string)) => string, _ => panic!(), }; assert_eq!(message, "Unknown child in set element."); } #[test] fn test_serialise() { let elem: Element = "" .parse() .unwrap(); let rsm = SetQuery { max: None, after: None, before: None, index: None, }; let elem2 = rsm.into(); assert_eq!(elem, elem2); let elem: Element = "" .parse() .unwrap(); let rsm = SetResult { first: None, first_index: None, last: None, count: None, }; let elem2 = rsm.into(); assert_eq!(elem, elem2); } #[test] fn test_serialise_empty_before() { let elem: Element = "" .parse() .unwrap(); let rsm = SetQuery { max: None, after: None, before: Some("".into()), index: None, }; let elem2 = rsm.into(); assert_eq!(elem, elem2); } #[test] fn test_first_index() { let elem: Element = "coucou" .parse() .unwrap(); let elem1 = elem.clone(); let set = SetResult::try_from(elem).unwrap(); assert_eq!(set.first, Some(String::from("coucou"))); assert_eq!(set.first_index, Some(4)); let set2 = SetResult { first: Some(String::from("coucou")), first_index: Some(4), last: None, count: None, }; let elem2 = set2.into(); assert_eq!(elem1, elem2); } }