From 4e246b2142cae371ca4d972f94472f88f9c6b1ce Mon Sep 17 00:00:00 2001 From: Alan Du Date: Thu, 1 Jun 2017 20:35:39 +0100 Subject: [PATCH 1/4] Truncate fractional seconds to picoseconds Close https://github.com/alexcrichton/toml-rs/issues/186 --- src/datetime.rs | 5 ++++- tests/valid.rs | 3 +++ tests/valid/datetime-truncate.json | 6 ++++++ tests/valid/datetime-truncate.toml | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/valid/datetime-truncate.json create mode 100644 tests/valid/datetime-truncate.toml diff --git a/src/datetime.rs b/src/datetime.rs index f39d9fa..810a4d7 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -218,7 +218,10 @@ impl FromStr for Datetime { return Err(DatetimeParseError { _private: () }) } chars = whole[end..].chars(); - match format!("0.{}", &whole[..end]).parse() { + + // truncate to picoseconds precision + let last = if end > 12 { 12 } else { end }; + match format!("0.{}", &whole[..last]).parse() { Ok(f) => f, Err(_) => return Err(DatetimeParseError { _private: () }), } diff --git a/tests/valid.rs b/tests/valid.rs index 676fc67..b3cc096 100644 --- a/tests/valid.rs +++ b/tests/valid.rs @@ -190,6 +190,9 @@ test!(example_bom, include_str!("valid/example-bom.toml"), include_str!("valid/example.json")); +test!(datetime_truncate, + include_str!("valid/datetime-truncate.toml"), + include_str!("valid/datetime-truncate.json")); test!(table_array_nest_no_keys, include_str!("valid/table-array-nest-no-keys.toml"), include_str!("valid/table-array-nest-no-keys.json")); diff --git a/tests/valid/datetime-truncate.json b/tests/valid/datetime-truncate.json new file mode 100644 index 0000000..34a432f --- /dev/null +++ b/tests/valid/datetime-truncate.json @@ -0,0 +1,6 @@ +{ + "bestdayever": { + "type": "datetime", + "value": "1987-07-05T17:45:00.123456789012Z" + } +} diff --git a/tests/valid/datetime-truncate.toml b/tests/valid/datetime-truncate.toml new file mode 100644 index 0000000..05de841 --- /dev/null +++ b/tests/valid/datetime-truncate.toml @@ -0,0 +1 @@ +bestdayever = 1987-07-05T17:45:00.123456789012345Z From bad367cab08f1bb519742c6196aa38fe464631aa Mon Sep 17 00:00:00 2001 From: Alan Du Date: Thu, 1 Jun 2017 20:54:17 +0100 Subject: [PATCH 2/4] Allow serializing keys with \n in them Use special quoted form Closes https://github.com/alexcrichton/toml-rs/issues/185 --- src/ser.rs | 8 -------- tests/valid.rs | 3 +++ tests/valid/key-quote-newline.json | 3 +++ tests/valid/key-quote-newline.toml | 1 + 4 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 tests/valid/key-quote-newline.json create mode 100644 tests/valid/key-quote-newline.toml diff --git a/src/ser.rs b/src/ser.rs index 8f6b366..9fa0678 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -107,9 +107,6 @@ pub enum Error { /// attempted where the key of a map was not a string. KeyNotString, - /// Keys in maps are not allowed to have newlines. - KeyNewline, - /// Arrays in TOML must have a homogenous type, but a heterogeneous array /// was emitted. ArrayMixedType, @@ -624,9 +621,6 @@ impl<'a, 'b> ser::SerializeMap for SerializeTable<'a, 'b> { SerializeTable::Table { ref mut key, .. } => { key.truncate(0); *key = input.serialize(StringExtractor)?; - if key.contains('\n') { - return Err(Error::KeyNewline) - } } } Ok(()) @@ -1047,7 +1041,6 @@ impl fmt::Display for Error { match *self { Error::UnsupportedType => "unsupported Rust type".fmt(f), Error::KeyNotString => "map key was not a string".fmt(f), - Error::KeyNewline => "map keys cannot contain newlines".fmt(f), Error::ArrayMixedType => "arrays cannot have mixed types".fmt(f), Error::ValueAfterTable => "values must be emitted before tables".fmt(f), Error::DateInvalid => "a serialized date was invalid".fmt(f), @@ -1064,7 +1057,6 @@ impl error::Error for Error { match *self { Error::UnsupportedType => "unsupported Rust type", Error::KeyNotString => "map key was not a string", - Error::KeyNewline => "map keys cannot contain newlines", Error::ArrayMixedType => "arrays cannot have mixed types", Error::ValueAfterTable => "values must be emitted before tables", Error::DateInvalid => "a serialized date was invalid", diff --git a/tests/valid.rs b/tests/valid.rs index b3cc096..e7577ad 100644 --- a/tests/valid.rs +++ b/tests/valid.rs @@ -193,6 +193,9 @@ test!(example_bom, test!(datetime_truncate, include_str!("valid/datetime-truncate.toml"), include_str!("valid/datetime-truncate.json")); +test!(key_quote_newline, + include_str!("valid/key-quote-newline.toml"), + include_str!("valid/key-quote-newline.json")); test!(table_array_nest_no_keys, include_str!("valid/table-array-nest-no-keys.toml"), include_str!("valid/table-array-nest-no-keys.json")); diff --git a/tests/valid/key-quote-newline.json b/tests/valid/key-quote-newline.json new file mode 100644 index 0000000..12473e4 --- /dev/null +++ b/tests/valid/key-quote-newline.json @@ -0,0 +1,3 @@ +{ + "\n": {"type": "integer", "value": "1"} +} diff --git a/tests/valid/key-quote-newline.toml b/tests/valid/key-quote-newline.toml new file mode 100644 index 0000000..a2639bf --- /dev/null +++ b/tests/valid/key-quote-newline.toml @@ -0,0 +1 @@ +"\n" = 1 From ce7b96c47bce16d8cd824f72290cc8c823c24660 Mon Sep 17 00:00:00 2001 From: Alan Du Date: Thu, 1 Jun 2017 22:47:24 +0100 Subject: [PATCH 3/4] Add toml::ser::Error::KeyNewline back in Avoid a breaking change --- src/ser.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ser.rs b/src/ser.rs index 9fa0678..77db867 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -107,6 +107,10 @@ pub enum Error { /// attempted where the key of a map was not a string. KeyNotString, + /// An error that we never omit but keep for backwards compatibility + #[doc(hidden)] + KeyNewline, + /// Arrays in TOML must have a homogenous type, but a heterogeneous array /// was emitted. ArrayMixedType, @@ -1047,6 +1051,7 @@ impl fmt::Display for Error { Error::NumberInvalid => "a serialized number was invalid".fmt(f), Error::UnsupportedNone => "unsupported None value".fmt(f), Error::Custom(ref s) => s.fmt(f), + Error::KeyNewline => unreachable!(), Error::__Nonexhaustive => panic!(), } } @@ -1063,6 +1068,7 @@ impl error::Error for Error { Error::NumberInvalid => "a serialized number was invalid", Error::UnsupportedNone => "unsupported None value", Error::Custom(_) => "custom error", + Error::KeyNewline => unreachable!(), Error::__Nonexhaustive => panic!(), } } From 5d36195f2d8af6b9a09d4c9f291458d8ea82f2a1 Mon Sep 17 00:00:00 2001 From: Alan Du Date: Thu, 1 Jun 2017 23:22:35 +0100 Subject: [PATCH 4/4] Store fractional seconds as a u32 instead of a f64 Drops precision down to the nanoseconds --- src/datetime.rs | 47 +++++++++++++++--------------- tests/parser.rs | 2 +- tests/valid/datetime-truncate.json | 2 +- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/datetime.rs b/src/datetime.rs index 810a4d7..83b5c0b 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -55,7 +55,7 @@ struct Time { hour: u8, minute: u8, second: u8, - secfract: f64, + nanosecond: u32, } #[derive(PartialEq, Clone)] @@ -97,9 +97,9 @@ impl fmt::Display for Date { 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 self.secfract != 0.0 { - let s = format!("{}", self.secfract); - write!(f, "{}", s.trim_left_matches("0"))?; + if self.nanosecond != 0 { + let s = format!("{:09}", self.nanosecond); + write!(f, ".{}", s.trim_right_matches('0'))?; } Ok(()) } @@ -199,41 +199,37 @@ impl FromStr for Datetime { let s1 = digit(&mut chars)?; let s2 = digit(&mut chars)?; - let secfract = if chars.clone().next() == Some('.') { + let mut nanosecond = 0; + 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' => {} + for (i, byte) in whole.bytes().enumerate() { + match byte { + b'0' ... b'9' => { + if i < 9 { + let p = 10_u32.pow(8 - i as u32); + nanosecond += p * (byte - b'0') as u32; + } + } _ => { end = i; - break + break; } } - first = false; } - if first { + if end == 0 { return Err(DatetimeParseError { _private: () }) } chars = whole[end..].chars(); - - // truncate to picoseconds precision - let last = if end > 12 { 12 } else { end }; - match format!("0.{}", &whole[..last]).parse() { - Ok(f) => f, - Err(_) => return Err(DatetimeParseError { _private: () }), - } - } else { - 0.0 - }; + } let time = Time { hour: h1 * 10 + h2, minute: m1 * 10 + m2, second: s1 * 10 + s2, - secfract: secfract, + nanosecond: nanosecond, }; if time.hour > 24 { @@ -242,7 +238,10 @@ impl FromStr for Datetime { if time.minute > 59 { return Err(DatetimeParseError { _private: () }) } - if time.second > 60 { + if time.second > 59 { + return Err(DatetimeParseError { _private: () }) + } + if time.nanosecond > 999_999_999 { return Err(DatetimeParseError { _private: () }) } diff --git a/tests/parser.rs b/tests/parser.rs index 2db2cfb..2282416 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -463,7 +463,7 @@ fn datetimes() { t!("2016-09-09T09:09:09Z"); t!("2016-09-09T09:09:09.1Z"); t!("2016-09-09T09:09:09.2+10:00"); - t!("2016-09-09T09:09:09.0123456789-02:00"); + t!("2016-09-09T09:09:09.123456789-02:00"); bad!("foo = 2016-09-09T09:09:09.Z", "failed to parse date"); bad!("foo = 2016-9-09T09:09:09Z", "failed to parse date"); bad!("foo = 2016-09-09T09:09:09+2:00", "failed to parse date"); diff --git a/tests/valid/datetime-truncate.json b/tests/valid/datetime-truncate.json index 34a432f..8c512e1 100644 --- a/tests/valid/datetime-truncate.json +++ b/tests/valid/datetime-truncate.json @@ -1,6 +1,6 @@ { "bestdayever": { "type": "datetime", - "value": "1987-07-05T17:45:00.123456789012Z" + "value": "1987-07-05T17:45:00.123456789Z" } }