idle: Add the chrono dependency to actually parse dates.

Emmanuel Gil Peyrot created

Change summary

Cargo.toml   |  1 
src/error.rs |  8 ++++++
src/idle.rs  | 69 +++++++++++++++++++++++++++++++++++++++++++++++++----
src/lib.rs   |  1 
4 files changed, 73 insertions(+), 6 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -18,3 +18,4 @@ sha-1 = "0.3.0"
 sha2 = "0.5.0"
 sha3 = "0.5.0"
 blake2 = "0.5.0"
+chrono = "0.3.1"

src/error.rs 🔗

@@ -12,6 +12,7 @@ use std::string;
 use base64;
 use minidom;
 use jid;
+use chrono;
 
 #[derive(Debug)]
 pub enum Error {
@@ -22,6 +23,7 @@ pub enum Error {
     ParseIntError(num::ParseIntError),
     ParseStringError(string::ParseError),
     JidParseError(jid::JidParseError),
+    ChronoParseError(chrono::ParseError),
 }
 
 impl From<io::Error> for Error {
@@ -59,3 +61,9 @@ impl From<jid::JidParseError> for Error {
         Error::JidParseError(err)
     }
 }
+
+impl From<chrono::ParseError> for Error {
+    fn from(err: chrono::ParseError) -> Error {
+        Error::ChronoParseError(err)
+    }
+}

src/idle.rs 🔗

@@ -7,16 +7,15 @@
 use std::convert::TryFrom;
 
 use minidom::Element;
+use chrono::prelude::*;
 
 use error::Error;
 
 use ns;
 
-type Date = String;
-
 #[derive(Debug, Clone)]
 pub struct Idle {
-    pub since: Date,
+    pub since: DateTime<FixedOffset>,
 }
 
 impl TryFrom<Element> for Idle {
@@ -29,7 +28,7 @@ impl TryFrom<Element> for Idle {
         for _ in elem.children() {
             return Err(Error::ParseError("Unknown child in idle element."));
         }
-        let since = get_attr!(elem, "since", required);
+        let since = get_attr!(elem, "since", required, since, DateTime::parse_from_rfc3339(since)?);
         Ok(Idle { since: since })
     }
 }
@@ -38,7 +37,7 @@ impl Into<Element> for Idle {
     fn into(self) -> Element {
         Element::builder("idle")
                 .ns(ns::IDLE)
-                .attr("since", self.since.clone())
+                .attr("since", self.since.to_rfc3339())
                 .build()
     }
 }
@@ -46,6 +45,7 @@ impl Into<Element> for Idle {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use std::error::Error as StdError;
 
     #[test]
     fn test_simple() {
@@ -75,10 +75,67 @@ mod tests {
         assert_eq!(message, "Required attribute 'since' missing.");
     }
 
+    #[test]
+    fn test_invalid_date() {
+        // There is no thirteenth month.
+        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-13-01T12:23:34Z'/>".parse().unwrap();
+        let error = Idle::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ChronoParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message.description(), "input is out of range");
+
+        // Timezone ≥24:00 aren’t allowed.
+        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-27T12:11:02+25:00'/>".parse().unwrap();
+        let error = Idle::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ChronoParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message.description(), "input is out of range");
+
+        // Timezone without the : separator aren’t allowed.
+        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-27T12:11:02+0100'/>".parse().unwrap();
+        let error = Idle::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ChronoParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message.description(), "input contains invalid characters");
+
+        // No seconds, error message could be improved.
+        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-27T12:11+01:00'/>".parse().unwrap();
+        let error = Idle::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ChronoParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message.description(), "input contains invalid characters");
+
+        // TODO: maybe we’ll want to support this one, as per XEP-0082 §4.
+        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='20170527T12:11:02+01:00'/>".parse().unwrap();
+        let error = Idle::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ChronoParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message.description(), "input contains invalid characters");
+
+        // No timezone.
+        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-27T12:11:02'/>".parse().unwrap();
+        let error = Idle::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ChronoParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message.description(), "premature end of input");
+    }
+
     #[test]
     fn test_serialise() {
         let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-21T20:19:55+01:00'/>".parse().unwrap();
-        let idle = Idle { since: Date::from("2017-05-21T20:19:55+01:00") };
+        let idle = Idle { since: DateTime::parse_from_rfc3339("2017-05-21T20:19:55+01:00").unwrap() };
         let elem2 = idle.into();
         assert_eq!(elem, elem2);
     }

src/lib.rs 🔗

@@ -22,6 +22,7 @@ extern crate sha_1;
 extern crate sha2;
 extern crate sha3;
 extern crate blake2;
+extern crate chrono;
 
 macro_rules! get_attr {
     ($elem:ident, $attr:tt, $type:tt) => (