From 2d7bf32ad452cea31d9c64e71142cb2a0179e911 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Mon, 22 Apr 2019 11:58:13 +0200 Subject: [PATCH] Add a XEP-0202 implementation. Fixes #7. --- ChangeLog | 5 +++ src/date.rs | 15 +++++++ src/lib.rs | 3 ++ src/ns.rs | 3 ++ src/time.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+) create mode 100644 src/time.rs diff --git a/ChangeLog b/ChangeLog index f012948..8225377 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Version TODO: +TODO Emmanuel Gil Peyrot + * New parsers/serialisers: + - Entity Time (XEP-0202). + Version 0.13.1: 2019-04-12 Emmanuel Gil Peyrot * Bugfixes: diff --git a/src/date.rs b/src/date.rs index 1d3389b..f50a1c4 100644 --- a/src/date.rs +++ b/src/date.rs @@ -15,6 +15,20 @@ use std::str::FromStr; #[derive(Debug, Clone, PartialEq)] pub struct DateTime(ChronoDateTime); +impl DateTime { + pub fn timezone(&self) -> FixedOffset { + self.0.timezone() + } + + pub fn with_timezone(&self, tz: &FixedOffset) -> DateTime { + DateTime(self.0.with_timezone(tz)) + } + + pub fn format(&self, fmt: &str) -> String { + format!("{}", self.0.format(fmt)) + } +} + impl FromStr for DateTime { type Err = Error; @@ -41,6 +55,7 @@ mod tests { use chrono::{Datelike, Timelike}; use std::error::Error as StdError; + // DateTime’s size doesn’t depend on the architecture. #[test] fn test_size() { assert_size!(DateTime, 16); diff --git a/src/lib.rs b/src/lib.rs index 38546ac..ebeddf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,9 @@ pub mod sm; /// XEP-0199: XMPP Ping pub mod ping; +/// XEP-0202: Entity Time +pub mod time; + /// XEP-0203: Delayed Delivery pub mod delay; diff --git a/src/ns.rs b/src/ns.rs index 4f3d2d2..978a852 100644 --- a/src/ns.rs +++ b/src/ns.rs @@ -112,6 +112,9 @@ pub const SM: &str = "urn:xmpp:sm:3"; /// XEP-0199: XMPP Ping pub const PING: &str = "urn:xmpp:ping"; +/// XEP-0202: Entity Time +pub const TIME: &str = "urn:xmpp:time"; + /// XEP-0203: Delayed Delivery pub const DELAY: &str = "urn:xmpp:delay"; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..c107393 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,112 @@ +// Copyright (c) 2019 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 chrono::FixedOffset; +use crate::date::DateTime; +use crate::iq::{IqGetPayload, IqResultPayload}; +use crate::ns; +use crate::util::error::Error; +use minidom::Element; +use std::convert::TryFrom; +use std::str::FromStr; + +generate_empty_element!( + TimeQuery, "time", TIME +); + +impl IqGetPayload for TimeQuery {} + +#[derive(Debug, Clone)] +pub struct TimeResult(pub DateTime); + +impl IqResultPayload for TimeResult {} + +impl TryFrom for TimeResult { + type Error = Error; + + fn try_from(elem: Element) -> Result { + check_self!(elem, "time", TIME); + check_no_attributes!(elem, "time"); + + let mut tzo = None; + let mut utc = None; + + for child in elem.children() { + if child.is("tzo", ns::TIME) { + if tzo.is_some() { + return Err(Error::ParseError("More than one tzo element in time.")); + } + check_no_children!(child, "tzo"); + check_no_attributes!(child, "tzo"); + // TODO: Add a FromStr implementation to FixedOffset to avoid this hack. + let fake_date = String::from("2019-04-22T11:38:00") + &child.text(); + let date_time = DateTime::from_str(&fake_date)?; + tzo = Some(date_time.timezone()); + } else if child.is("utc", ns::TIME) { + if utc.is_some() { + return Err(Error::ParseError("More than one utc element in time.")); + } + check_no_children!(child, "utc"); + check_no_attributes!(child, "utc"); + let date_time = DateTime::from_str(&child.text())?; + if date_time.timezone() != FixedOffset::east(0) { + return Err(Error::ParseError("Non-UTC timezone for utc element.")); + } + utc = Some(date_time); + } else { + return Err(Error::ParseError( + "Unknown child in time element.", + )); + } + } + + let tzo = tzo.ok_or(Error::ParseError("Missing tzo child in time element."))?; + let utc = utc.ok_or(Error::ParseError("Missing utc child in time element."))?; + let date = utc.with_timezone(&tzo); + + Ok(TimeResult(date)) + } +} + +impl From for Element { + fn from(time: TimeResult) -> Element { + Element::builder("time") + .ns(ns::TIME) + .append(Element::builder("tzo") + .append(format!("{}", time.0.timezone())) + .build()) + .append(Element::builder("utc") + .append(time.0.with_timezone(&FixedOffset::east(0)).format("%FT%TZ")) + .build()) + .build() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // DateTime’s size doesn’t depend on the architecture. + #[test] + fn test_size() { + assert_size!(TimeQuery, 0); + assert_size!(TimeResult, 16); + } + + #[test] + fn parse_response() { + let elem: Element = + "" + .parse() + .unwrap(); + let elem1 = elem.clone(); + let time = TimeResult::try_from(elem).unwrap(); + assert_eq!(time.0.timezone(), FixedOffset::west(6 * 3600)); + assert_eq!(time.0, DateTime::from_str("2006-12-19T12:58:35-05:00").unwrap()); + let elem2 = Element::from(time); + assert_eq!(elem1, elem2); + } +}