// Copyright (c) 2017-2021 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::data_forms::DataForm; use crate::forwarding::Forwarded; use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; use crate::message::MessagePayload; use crate::ns; use crate::pubsub::NodeName; use crate::rsm::{SetQuery, SetResult}; use crate::util::error::Error; use crate::Element; use minidom::Node; generate_id!( /// An identifier matching a result message to the query requesting it. QueryId ); /// Starts a query to the archive. #[derive(Debug)] pub struct Query { /// An optional identifier for matching forwarded messages to this /// query. pub queryid: Option, /// Must be set to Some when querying a PubSub node’s archive. pub node: Option, /// Used for filtering the results. pub form: Option, /// Used for paging through results. pub set: Option, /// Used for reversing the order of the results. pub flip_page: bool, } impl IqGetPayload for Query {} impl IqSetPayload for Query {} impl IqResultPayload for Query {} impl TryFrom for Query { type Error = Error; fn try_from(elem: Element) -> Result { check_self!(elem, "query", MAM); check_no_unknown_attributes!(elem, "query", ["queryid", "node"]); let mut form = None; let mut set = None; let mut flip_page = None; for child in elem.children() { if child.is("x", ns::DATA_FORMS) { if form.is_some() { return Err(Error::ParseError( "Element query must not have more than one x child.", )); } form = Some(DataForm::try_from(child.clone())?); continue; } if child.is("set", ns::RSM) { if set.is_some() { return Err(Error::ParseError( "Element query must not have more than one set child.", )); } set = Some(SetQuery::try_from(child.clone())?); continue; } if child.is("flip-page", ns::MAM) { if flip_page.is_some() { return Err(Error::ParseError( "Element query must not have more than one flip-page child.", )); } flip_page = Some(true); continue; } return Err(Error::ParseError("Unknown child in query element.")); } Ok(Query { queryid: match elem.attr("queryid") { Some(value) => Some(value.parse()?), None => None, }, node: match elem.attr("node") { Some(value) => Some(value.parse()?), None => None, }, form, set, flip_page: flip_page.is_some(), }) } } impl From for Element { fn from(elem: Query) -> Element { let mut builder = Element::builder("query", ns::MAM); builder = builder.attr("queryid", elem.queryid); builder = builder.attr("node", elem.node); builder = builder.append_all(elem.form.map(|elem| Node::Element(Element::from(elem)))); builder = builder.append_all(elem.set.map(|elem| Node::Element(Element::from(elem)))); if elem.flip_page { let flip_page = Element::builder("flip-page", ns::MAM).build(); builder = builder.append(Node::Element(flip_page)); } builder.build() } } generate_element!( /// The wrapper around forwarded stanzas. Result_, "result", MAM, attributes: [ /// The stanza-id under which the archive stored this stanza. id: Required = "id", /// The same queryid as the one requested in the /// [query](struct.Query.html). queryid: Option = "queryid", ], children: [ /// The actual stanza being forwarded. forwarded: Required = ("forwarded", FORWARD) => Forwarded ] ); impl MessagePayload for Result_ {} generate_attribute!( /// True when the end of a MAM query has been reached. Complete, "complete", bool ); generate_element!( /// Notes the end of a page in a query. Fin, "fin", MAM, attributes: [ /// True when the end of a MAM query has been reached. complete: Default = "complete", ], children: [ /// Describes the current page, it should contain at least [first] /// (with an [index]) and [last], and generally [count]. /// /// [first]: ../rsm/struct.SetResult.html#structfield.first /// [index]: ../rsm/struct.SetResult.html#structfield.first_index /// [last]: ../rsm/struct.SetResult.html#structfield.last /// [count]: ../rsm/struct.SetResult.html#structfield.count set: Required = ("set", RSM) => SetResult ] ); impl IqResultPayload for Fin {} #[cfg(test)] mod tests { use super::*; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { assert_size!(QueryId, 12); assert_size!(Query, 120); assert_size!(Result_, 164); assert_size!(Complete, 1); assert_size!(Fin, 44); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { assert_size!(QueryId, 24); assert_size!(Query, 240); assert_size!(Result_, 312); assert_size!(Complete, 1); assert_size!(Fin, 88); } #[test] fn test_query() { let elem: Element = "".parse().unwrap(); Query::try_from(elem).unwrap(); } #[test] fn test_result() { #[cfg(not(feature = "component"))] let elem: Element = r#" Hail to thee "# .parse() .unwrap(); #[cfg(feature = "component")] let elem: Element = r#" Hail to thee "#.parse().unwrap(); Result_::try_from(elem).unwrap(); } #[test] fn test_fin() { let elem: Element = r#" 28482-98726-73623 09af3-cc343-b409f "# .parse() .unwrap(); Fin::try_from(elem).unwrap(); } #[test] fn test_query_x() { let elem: Element = r#" urn:xmpp:mam:2 juliet@capulet.lit "# .parse() .unwrap(); Query::try_from(elem).unwrap(); } #[test] fn test_query_x_set() { let elem: Element = r#" urn:xmpp:mam:2 2010-08-07T00:00:00Z 10 "# .parse() .unwrap(); Query::try_from(elem).unwrap(); } #[test] fn test_query_x_set_flipped() { let elem: Element = r#" urn:xmpp:mam:2 2010-08-07T00:00:00Z 10 "# .parse() .unwrap(); Query::try_from(elem).unwrap(); } #[test] fn test_invalid_child() { let elem: Element = "" .parse() .unwrap(); let error = Query::try_from(elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown child in query element."); } #[test] fn test_serialise_empty() { let elem: Element = "".parse().unwrap(); let replace = Query { queryid: None, node: None, form: None, set: None, flip_page: false, }; let elem2 = replace.into(); assert_eq!(elem, elem2); } #[test] fn test_serialize_query_with_form() { let reference: Element = "urn:xmpp:mam:2juliet@capulet.lit" .parse() .unwrap(); let elem: Element = "urn:xmpp:mam:2juliet@capulet.lit" .parse() .unwrap(); let form = DataForm::try_from(elem).unwrap(); let query = Query { queryid: None, node: None, set: None, form: Some(form), flip_page: true, }; let serialized: Element = query.into(); assert_eq!(serialized, reference); } #[test] fn test_serialize_result() { let reference: Element = "" .parse() .unwrap(); let elem: Element = "" .parse() .unwrap(); let forwarded = Forwarded::try_from(elem).unwrap(); let result = Result_ { id: String::from("28482-98726-73623"), queryid: Some(QueryId(String::from("f27"))), forwarded, }; let serialized: Element = result.into(); assert_eq!(serialized, reference); } #[test] fn test_serialize_fin() { let reference: Element = "28482-98726-7362309af3-cc343-b409f" .parse() .unwrap(); let elem: Element = "28482-98726-7362309af3-cc343-b409f" .parse() .unwrap(); let set = SetResult::try_from(elem).unwrap(); let fin = Fin { set, complete: Complete::default(), }; let serialized: Element = fin.into(); assert_eq!(serialized, reference); } }