// 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 xso::{AsXml, FromXml}; use crate::data_forms::DataForm; use crate::date::DateTime; 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}; generate_id!( /// An identifier matching a result message to the query requesting it. QueryId ); /// Starts a query to the archive. #[derive(FromXml, AsXml, Debug)] #[xml(namespace = ns::MAM, name = "query")] pub struct Query { /// An optional identifier for matching forwarded messages to this /// query. #[xml(attribute(default))] pub queryid: Option, /// Must be set to Some when querying a PubSub node’s archive. #[xml(attribute(default))] pub node: Option, /// Used for filtering the results. #[xml(child(default))] pub form: Option, /// Used for paging through results. #[xml(child(default))] pub set: Option, /// Used for reversing the order of the results. #[xml(flag(name = "flip-page"))] pub flip_page: bool, } impl IqGetPayload for Query {} impl IqSetPayload for Query {} impl IqResultPayload for Query {} /// The wrapper around forwarded stanzas. #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = ns::MAM, name = "result")] pub struct Result_ { /// The stanza-id under which the archive stored this stanza. #[xml(attribute)] pub id: String, /// The same queryid as the one requested in the /// [query](struct.Query.html). #[xml(attribute(default))] pub queryid: Option, /// The actual stanza being forwarded. #[xml(child)] pub forwarded: Forwarded, } impl MessagePayload for Result_ {} /// Notes the end of a page in a query. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "fin")] pub struct Fin { /// True when the end of a MAM query has been reached. #[xml(attribute(default))] pub complete: bool, /// 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 #[xml(child)] pub set: SetResult, } impl IqResultPayload for Fin {} /// Metadata of the first message in the archive. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "start")] pub struct Start { /// The id of the first message in the archive. #[xml(attribute)] pub id: String, /// Time at which that message was sent. #[xml(attribute)] pub timestamp: DateTime, } /// Metadata of the last message in the archive. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "end")] pub struct End { /// The id of the last message in the archive. #[xml(attribute)] pub id: String, /// Time at which that message was sent. #[xml(attribute)] pub timestamp: DateTime, } /// Request an archive for its metadata. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "metadata")] pub struct MetadataQuery; impl IqGetPayload for MetadataQuery {} /// Response from the archive, containing the start and end metadata if it isn’t empty. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "metadata")] pub struct MetadataResponse { /// Metadata about the first message in the archive. #[xml(child(default))] pub start: Option, /// Metadata about the last message in the archive. #[xml(child(default))] pub end: Option, } impl IqResultPayload for MetadataResponse {} #[cfg(test)] mod tests { use super::*; use minidom::Element; use xso::error::{Error, FromElementError}; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { assert_size!(QueryId, 12); assert_size!(Query, 120); assert_size!(Result_, 164); assert_size!(Fin, 44); assert_size!(Start, 28); assert_size!(End, 28); assert_size!(MetadataQuery, 0); assert_size!(MetadataResponse, 56); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { assert_size!(QueryId, 24); assert_size!(Query, 240); assert_size!(Result_, 312); assert_size!(Fin, 88); assert_size!(Start, 40); assert_size!(End, 40); assert_size!(MetadataQuery, 0); assert_size!(MetadataResponse, 80); } #[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_metadata() { let elem: Element = r"".parse().unwrap(); MetadataQuery::try_from(elem).unwrap(); let elem: Element = r" " .parse() .unwrap(); let metadata = MetadataResponse::try_from(elem).unwrap(); let start = metadata.start.unwrap(); let end = metadata.end.unwrap(); assert_eq!(start.id, "YWxwaGEg"); assert_eq!(start.timestamp.0.timestamp(), 1219439344); assert_eq!(end.id, "b21lZ2Eg"); assert_eq!(end.timestamp.0.timestamp(), 1587393261); } #[test] fn test_invalid_child() { let elem: Element = "" .parse() .unwrap(); let error = Query::try_from(elem).unwrap_err(); let message = match error { FromElementError::Invalid(Error::Other(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: false, }; let serialized: Element = fin.into(); assert_eq!(serialized, reference); } }