idle.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use try_from::TryFrom;
  8
  9use minidom::Element;
 10use chrono::{DateTime, FixedOffset};
 11
 12use error::Error;
 13
 14use ns;
 15
 16#[derive(Debug, Clone)]
 17pub struct Idle {
 18    pub since: DateTime<FixedOffset>,
 19}
 20
 21impl TryFrom<Element> for Idle {
 22    type Err = Error;
 23
 24    fn try_from(elem: Element) -> Result<Idle, Error> {
 25        if !elem.is("idle", ns::IDLE) {
 26            return Err(Error::ParseError("This is not an idle element."));
 27        }
 28        for _ in elem.children() {
 29            return Err(Error::ParseError("Unknown child in idle element."));
 30        }
 31        let since = get_attr!(elem, "since", required, since, DateTime::parse_from_rfc3339(since)?);
 32        Ok(Idle { since: since })
 33    }
 34}
 35
 36impl From<Idle> for Element {
 37    fn from(idle: Idle) -> Element {
 38        Element::builder("idle")
 39                .ns(ns::IDLE)
 40                .attr("since", idle.since.to_rfc3339())
 41                .build()
 42    }
 43}
 44
 45#[cfg(test)]
 46mod tests {
 47    use super::*;
 48    use std::error::Error as StdError;
 49
 50    #[test]
 51    fn test_simple() {
 52        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-21T20:19:55+01:00'/>".parse().unwrap();
 53        Idle::try_from(elem).unwrap();
 54    }
 55
 56    #[test]
 57    fn test_invalid_child() {
 58        let elem: Element = "<idle xmlns='urn:xmpp:idle:1'><coucou/></idle>".parse().unwrap();
 59        let error = Idle::try_from(elem).unwrap_err();
 60        let message = match error {
 61            Error::ParseError(string) => string,
 62            _ => panic!(),
 63        };
 64        assert_eq!(message, "Unknown child in idle element.");
 65    }
 66
 67    #[test]
 68    fn test_invalid_id() {
 69        let elem: Element = "<idle xmlns='urn:xmpp:idle:1'/>".parse().unwrap();
 70        let error = Idle::try_from(elem).unwrap_err();
 71        let message = match error {
 72            Error::ParseError(string) => string,
 73            _ => panic!(),
 74        };
 75        assert_eq!(message, "Required attribute 'since' missing.");
 76    }
 77
 78    #[test]
 79    fn test_invalid_date() {
 80        // There is no thirteenth month.
 81        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-13-01T12:23:34Z'/>".parse().unwrap();
 82        let error = Idle::try_from(elem).unwrap_err();
 83        let message = match error {
 84            Error::ChronoParseError(string) => string,
 85            _ => panic!(),
 86        };
 87        assert_eq!(message.description(), "input is out of range");
 88
 89        // Timezone ≥24:00 aren’t allowed.
 90        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-27T12:11:02+25:00'/>".parse().unwrap();
 91        let error = Idle::try_from(elem).unwrap_err();
 92        let message = match error {
 93            Error::ChronoParseError(string) => string,
 94            _ => panic!(),
 95        };
 96        assert_eq!(message.description(), "input is out of range");
 97
 98        // Timezone without the : separator aren’t allowed.
 99        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-27T12:11:02+0100'/>".parse().unwrap();
100        let error = Idle::try_from(elem).unwrap_err();
101        let message = match error {
102            Error::ChronoParseError(string) => string,
103            _ => panic!(),
104        };
105        assert_eq!(message.description(), "input contains invalid characters");
106
107        // No seconds, error message could be improved.
108        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-27T12:11+01:00'/>".parse().unwrap();
109        let error = Idle::try_from(elem).unwrap_err();
110        let message = match error {
111            Error::ChronoParseError(string) => string,
112            _ => panic!(),
113        };
114        assert_eq!(message.description(), "input contains invalid characters");
115
116        // TODO: maybe we’ll want to support this one, as per XEP-0082 §4.
117        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='20170527T12:11:02+01:00'/>".parse().unwrap();
118        let error = Idle::try_from(elem).unwrap_err();
119        let message = match error {
120            Error::ChronoParseError(string) => string,
121            _ => panic!(),
122        };
123        assert_eq!(message.description(), "input contains invalid characters");
124
125        // No timezone.
126        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-27T12:11:02'/>".parse().unwrap();
127        let error = Idle::try_from(elem).unwrap_err();
128        let message = match error {
129            Error::ChronoParseError(string) => string,
130            _ => panic!(),
131        };
132        assert_eq!(message.description(), "premature end of input");
133    }
134
135    #[test]
136    fn test_serialise() {
137        let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-21T20:19:55+01:00'/>".parse().unwrap();
138        let idle = Idle { since: DateTime::parse_from_rfc3339("2017-05-21T20:19:55+01:00").unwrap() };
139        let elem2 = idle.into();
140        assert_eq!(elem, elem2);
141    }
142}