toml-rs/src/datetime.rs
Alex Crichton e256931e9b Rewrite crate with serde support from ground up
This commit completely rewrites this crate from the ground up,
supporting serde at the lowest levels as I believe serde support was
intended to do. This is a major change from the previous versions of
this crate, with a summary of changes being:

* Serialization directly to TOML is now supported without going through
  a `Value` first.

* Deserialization directly from TOML is now supported without going
  through a `Value`. Note that due to the TOML format some values still
  are buffered in intermediate memory, but overall this should be at a
  minimum now.

* The API of `Value` was overhauled to match the API of
  `serde_json::Value`. The changes here were to:

  * Add `is_*` accessors
  * Add `get` and `get_mut` for one-field lookups.
  * Implement panicking lookups through `Index`

  The old `index` methods are now gone in favor of `get` and `Index`
  implementations.

* A `Datetime` type has been added to represent a TOML datetime in a
  first-class fashion. Currently this type provides no accessors other
  than a `Display` implementation, but the idea is that this will grow
  support over time for decomposing the date.

* Support for the `rustc-serialize` crate has been dropped, that'll stay
  on the 0.2 and 0.1 release trains.

* This crate no longer supports the detection of unused fields, for that though
  you can use the `serde_ignored` crate on crates.io
2017-02-08 21:21:18 -08:00

425 lines
12 KiB
Rust

use std::fmt;
use std::str::{self, FromStr};
use std::error;
use serde::{de, ser};
/// A parsed TOML datetime value
///
/// This structure is intended to represent the datetime primitive type that can
/// be encoded into TOML documents. This type is a parsed version that contains
/// all metadata internally.
///
/// Currently this type is intentionally conservative and only supports
/// `to_string` as an accessor. Over time though it's intended that it'll grow
/// more support!
///
/// Note that if you're using `Deserialize` to deserialize a TOML document, you
/// can use this as a placeholder for where you're expecting a datetime to be
/// specified.
///
/// Also note though that while this type implements `Serialize` and
/// `Deserialize` it's only recommended to use this type with the TOML format,
/// otherwise encoded in other formats it may look a little odd.
#[derive(PartialEq, Clone)]
pub struct Datetime {
date: Option<Date>,
time: Option<Time>,
offset: Option<Offset>,
}
/// Error returned from parsing a `Datetime` in the `FromStr` implementation.
#[derive(Debug, Clone)]
pub struct DatetimeParseError {
_private: (),
}
// Currently serde itself doesn't have a datetime type, so we map our `Datetime`
// to a special valid in the serde data model. Namely one with thiese special
// fields/struct names.
//
// In general the TOML encoder/decoder will catch this and not literally emit
// these strings but rather emit datetimes as they're intended.
pub const SERDE_STRUCT_FIELD_NAME: &'static str = "$__toml_private_datetime";
pub const SERDE_STRUCT_NAME: &'static str = "$__toml_private_Datetime";
#[derive(PartialEq, Clone)]
struct Date {
year: u16,
month: u8,
day: u8,
}
#[derive(PartialEq, Clone)]
struct Time {
hour: u8,
minute: u8,
second: u8,
secfract: Option<f64>,
}
#[derive(PartialEq, Clone)]
enum Offset {
Z,
Custom { hours: i8, minutes: u8 },
}
impl fmt::Debug for Datetime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for Datetime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref date) = self.date {
write!(f, "{}", date)?;
}
if let Some(ref time) = self.time {
if self.date.is_some() {
write!(f, "T")?;
}
write!(f, "{}", time)?;
}
if let Some(ref offset) = self.offset {
write!(f, "{}", offset)?;
}
Ok(())
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
if let Some(i) = self.secfract {
let s = format!("{}", i);
write!(f, "{}", s.trim_left_matches("0"))?;
}
Ok(())
}
}
impl fmt::Display for Offset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Offset::Z => write!(f, "Z"),
Offset::Custom { hours, minutes } => {
write!(f, "{:+03}:{:02}", hours, minutes)
}
}
}
}
impl FromStr for Datetime {
type Err = DatetimeParseError;
fn from_str(date: &str) -> Result<Datetime, DatetimeParseError> {
// Accepted formats:
//
// 0000-00-00T00:00:00.00Z
// 0000-00-00T00:00:00.00
// 0000-00-00
// 00:00:00.00
if date.len() < 3 {
return Err(DatetimeParseError { _private: () })
}
let mut offset_allowed = true;
let mut chars = date.chars();
// First up, parse the full date if we can
let full_date = if chars.clone().nth(2) == Some(':') {
offset_allowed = false;
None
} else {
let y1 = digit(&mut chars)? as u16;
let y2 = digit(&mut chars)? as u16;
let y3 = digit(&mut chars)? as u16;
let y4 = digit(&mut chars)? as u16;
match chars.next() {
Some('-') => {}
_ => return Err(DatetimeParseError { _private: () }),
}
let m1 = digit(&mut chars)?;
let m2 = digit(&mut chars)?;
match chars.next() {
Some('-') => {}
_ => return Err(DatetimeParseError { _private: () }),
}
let d1 = digit(&mut chars)?;
let d2 = digit(&mut chars)?;
let date = Date {
year: y1 * 1000 + y2 * 100 + y3 * 10 + y4,
month: m1 * 10 + m2,
day: d1 * 10 + d2,
};
if date.month < 1 || date.month > 12 {
return Err(DatetimeParseError { _private: () })
}
if date.day < 1 || date.day > 31 {
return Err(DatetimeParseError { _private: () })
}
Some(date)
};
// Next parse the "partial-time" if available
let partial_time = if full_date.is_some() &&
chars.clone().next() == Some('T') {
chars.next();
true
} else if full_date.is_none() {
true
} else {
false
};
let time = if partial_time {
let h1 = digit(&mut chars)?;
let h2 = digit(&mut chars)?;
match chars.next() {
Some(':') => {}
_ => return Err(DatetimeParseError { _private: () }),
}
let m1 = digit(&mut chars)?;
let m2 = digit(&mut chars)?;
match chars.next() {
Some(':') => {}
_ => return Err(DatetimeParseError { _private: () }),
}
let s1 = digit(&mut chars)?;
let s2 = digit(&mut chars)?;
let secfract = if chars.clone().next() == Some('.') {
chars.next();
let mut first = true;
let whole = chars.as_str();
let mut end = whole.len();
for (i, c) in whole.char_indices() {
match c {
'0' ... '9' => {}
_ => {
end = i;
break
}
}
first = false;
}
if first {
return Err(DatetimeParseError { _private: () })
}
chars = whole[end..].chars();
match format!("0.{}", &whole[..end]).parse() {
Ok(f) => Some(f),
Err(_) => return Err(DatetimeParseError { _private: () }),
}
} else {
None
};
let time = Time {
hour: h1 * 10 + h2,
minute: m1 * 10 + m2,
second: s1 * 10 + s2,
secfract: secfract,
};
if time.hour > 24 {
return Err(DatetimeParseError { _private: () })
}
if time.minute > 59 {
return Err(DatetimeParseError { _private: () })
}
if time.second > 60 {
return Err(DatetimeParseError { _private: () })
}
Some(time)
} else {
offset_allowed = false;
None
};
// And finally, parse the offset
let offset = if offset_allowed {
let next = chars.clone().next();
if next == Some('Z') {
chars.next();
Some(Offset::Z)
} else if next.is_none() {
None
} else {
let sign = match next {
Some('+') => 1,
Some('-') => -1,
_ => return Err(DatetimeParseError { _private: () }),
};
chars.next();
let h1 = digit(&mut chars)? as i8;
let h2 = digit(&mut chars)? as i8;
match chars.next() {
Some(':') => {}
_ => return Err(DatetimeParseError { _private: () }),
}
let m1 = digit(&mut chars)?;
let m2 = digit(&mut chars)?;
Some(Offset::Custom {
hours: sign * (h1 * 10 + h2),
minutes: m1 * 10 + m2,
})
}
} else {
None
};
// Return an error if we didn't hit eof, otherwise return our parsed
// date
if chars.next().is_some() {
return Err(DatetimeParseError { _private: () })
}
Ok(Datetime {
date: full_date,
time: time,
offset: offset,
})
}
}
fn digit(chars: &mut str::Chars) -> Result<u8, DatetimeParseError> {
match chars.next() {
Some(c) if '0' <= c && c <= '9' => Ok(c as u8 - '0' as u8),
_ => Err(DatetimeParseError { _private: () }),
}
}
impl ser::Serialize for Datetime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: ser::Serializer
{
use serde::ser::SerializeStruct;
let mut s = serializer.serialize_struct(SERDE_STRUCT_NAME, 1)?;
s.serialize_field(SERDE_STRUCT_FIELD_NAME, &self.to_string())?;
s.end()
}
}
impl de::Deserialize for Datetime {
fn deserialize<D>(deserializer: D) -> Result<Datetime, D::Error>
where D: de::Deserializer
{
struct DatetimeVisitor;
impl de::Visitor for DatetimeVisitor {
type Value = Datetime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a TOML datetime")
}
fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error>
where V: de::MapVisitor
{
let value = visitor.visit_key::<DatetimeKey>()?;
if value.is_none() {
return Err(de::Error::custom("datetime key not found"))
}
let v: DatetimeFromString = visitor.visit_value()?;
Ok(v.value)
}
}
static FIELDS: [&'static str; 1] = [SERDE_STRUCT_FIELD_NAME];
deserializer.deserialize_struct(SERDE_STRUCT_NAME,
&FIELDS,
DatetimeVisitor)
}
}
struct DatetimeKey;
impl de::Deserialize for DatetimeKey {
fn deserialize<D>(deserializer: D) -> Result<DatetimeKey, D::Error>
where D: de::Deserializer
{
struct FieldVisitor;
impl de::Visitor for FieldVisitor {
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid datetime field")
}
fn visit_str<E>(self, s: &str) -> Result<(), E>
where E: de::Error
{
if s == SERDE_STRUCT_FIELD_NAME {
Ok(())
} else {
Err(de::Error::custom("expected field with custom name"))
}
}
}
deserializer.deserialize_struct_field(FieldVisitor)?;
Ok(DatetimeKey)
}
}
pub struct DatetimeFromString {
pub value: Datetime,
}
impl de::Deserialize for DatetimeFromString {
fn deserialize<D>(deserializer: D) -> Result<DatetimeFromString, D::Error>
where D: de::Deserializer
{
struct Visitor;
impl de::Visitor for Visitor {
type Value = DatetimeFromString;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string containing a datetime")
}
fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E>
where E: de::Error,
{
match s.parse() {
Ok(date) => Ok(DatetimeFromString { value: date }),
Err(e) => Err(de::Error::custom(e)),
}
}
}
deserializer.deserialize_str(Visitor)
}
}
impl fmt::Display for DatetimeParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
"failed to parse datetime".fmt(f)
}
}
impl error::Error for DatetimeParseError {
fn description(&self) -> &str {
"failed to parse datetime"
}
}