xmpp-rs-mirror/parsers/src/date.rs
Jonas Schäfer 6ef8dbefa3 parsers: use Error type from xso
This is a large change and as such, it needs good motivation. Let me
remind you of the ultimate goal: we want a derive macro which allows us
to FromXml/IntoXml, and that derive macro should be usable from
`xmpp_parsers` and other crates.

For that, any code generated by the derive macro mustn't depend on any
code in the `xmpp_parsers` crate, because you cannot name the crate you
are in portably (`xmpp_parsers::..` wouldn't resolve within
`xmpp_parsers`, and `crate::..` would point at other crates if the macro
was used in other crates).

We also want to interoperate with code already implementing
`TryFrom<Element>` and `Into<Element>` on structs. This ultimately
requires that we have an error type which is shared by the two
implementations and that error type must be declared in the `xso` crate
to be usable by the macros.

Thus, we port the error type over to use the type declared in `xso`.

This changes the structure of the error type greatly; I do not think
that `xso` should have to know about all the different types we are
parsing there and they don't deserve special treatment. Wrapping them in
a `Box<dyn ..>` seems more appropriate.
2024-06-23 09:40:52 +02:00

112 lines
3.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// 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::{DateTime as ChronoDateTime, FixedOffset};
use minidom::{IntoAttributeValue, Node};
use std::str::FromStr;
/// Implements the DateTime profile of XEP-0082, which represents a
/// non-recurring moment in time, with an accuracy of seconds or fraction of
/// seconds, and includes a timezone.
#[derive(Debug, Clone, PartialEq)]
pub struct DateTime(pub ChronoDateTime<FixedOffset>);
impl DateTime {
/// Retrieves the associated timezone.
pub fn timezone(&self) -> FixedOffset {
self.0.timezone()
}
/// Returns a new `DateTime` with a different timezone.
pub fn with_timezone(&self, tz: FixedOffset) -> DateTime {
DateTime(self.0.with_timezone(&tz))
}
/// Formats this `DateTime` with the specified format string.
pub fn format(&self, fmt: &str) -> String {
format!("{}", self.0.format(fmt))
}
}
impl FromStr for DateTime {
type Err = chrono::ParseError;
fn from_str(s: &str) -> Result<DateTime, Self::Err> {
Ok(DateTime(ChronoDateTime::parse_from_rfc3339(s)?))
}
}
impl IntoAttributeValue for DateTime {
fn into_attribute_value(self) -> Option<String> {
Some(self.0.to_rfc3339())
}
}
impl From<DateTime> for Node {
fn from(date: DateTime) -> Node {
Node::Text(date.0.to_rfc3339())
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Datelike, Timelike};
// DateTimes size doesnt depend on the architecture.
#[test]
fn test_size() {
assert_size!(DateTime, 16);
}
#[test]
fn test_simple() {
let date: DateTime = "2002-09-10T23:08:25Z".parse().unwrap();
assert_eq!(date.0.year(), 2002);
assert_eq!(date.0.month(), 9);
assert_eq!(date.0.day(), 10);
assert_eq!(date.0.hour(), 23);
assert_eq!(date.0.minute(), 08);
assert_eq!(date.0.second(), 25);
assert_eq!(date.0.nanosecond(), 0);
assert_eq!(date.0.timezone(), FixedOffset::east_opt(0).unwrap());
}
#[test]
fn test_invalid_date() {
// There is no thirteenth month.
let error = DateTime::from_str("2017-13-01T12:23:34Z").unwrap_err();
assert_eq!(error.to_string(), "input is out of range");
// Timezone ≥24:00 arent allowed.
let error = DateTime::from_str("2017-05-27T12:11:02+25:00").unwrap_err();
assert_eq!(error.to_string(), "input is out of range");
// Timezone without the : separator arent allowed.
let error = DateTime::from_str("2017-05-27T12:11:02+0100").unwrap_err();
assert_eq!(error.to_string(), "input contains invalid characters");
// No seconds, error message could be improved.
let error = DateTime::from_str("2017-05-27T12:11+01:00").unwrap_err();
assert_eq!(error.to_string(), "input contains invalid characters");
// TODO: maybe well want to support this one, as per XEP-0082 §4.
let error = DateTime::from_str("20170527T12:11:02+01:00").unwrap_err();
assert_eq!(error.to_string(), "input contains invalid characters");
// No timezone.
let error = DateTime::from_str("2017-05-27T12:11:02").unwrap_err();
assert_eq!(error.to_string(), "premature end of input");
}
#[test]
fn test_serialise() {
let date =
DateTime(ChronoDateTime::parse_from_rfc3339("2017-05-21T20:19:55+01:00").unwrap());
let attr = date.into_attribute_value();
assert_eq!(attr, Some(String::from("2017-05-21T20:19:55+01:00")));
}
}