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}