From c3a29ecea57ca164a223b858893481e59d2f5c4b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 13 Sep 2021 11:24:13 -0500 Subject: [PATCH] perf: Delay generate repr (#202) When parsing, we create the `Value`, which will cause a default repr to be generated. We then overwrite it with the actual repr. Similarly, when indexing, we generate a repr for the `Key` but usually throw that key away. This makes it so we only generate a repr when needed, and allows the user to force a default repr. The downside is that anything with a default repr will be re-generated multiple times if the user renders the document multiple times. This is probably rare. As we expand our formatting support though, we should consider a "fill in details where non-exist", so that all goes away. --- src/easy/value.rs | 11 +-- src/encode.rs | 221 +++++++++++++++++++++++++++++++++++++++++++- src/index.rs | 11 +-- src/key.rs | 34 ++++--- src/lib.rs | 13 +++ src/parser/key.rs | 2 +- src/parser/table.rs | 10 +- src/parser/value.rs | 11 +-- src/repr.rs | 45 ++++++--- src/value.rs | 194 ++------------------------------------ tests/test_valid.rs | 4 +- 11 files changed, 308 insertions(+), 248 deletions(-) diff --git a/src/easy/value.rs b/src/easy/value.rs index 1de11fb5..5a62ce8b 100644 --- a/src/easy/value.rs +++ b/src/easy/value.rs @@ -314,22 +314,13 @@ impl_into_value!(Table: Table); /// /// This trait is sealed and not intended for implementation outside of the /// `toml` crate. -pub trait Index: Sealed { +pub trait Index: crate::private::Sealed { #[doc(hidden)] fn index<'a>(&self, val: &'a Value) -> Option<&'a Value>; #[doc(hidden)] fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value>; } -/// An implementation detail that should not be implemented, this will change in -/// the future and break code otherwise. -#[doc(hidden)] -pub trait Sealed {} -impl Sealed for usize {} -impl Sealed for str {} -impl Sealed for String {} -impl<'a, T: Sealed + ?Sized> Sealed for &'a T {} - impl Index for usize { fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> { match *val { diff --git a/src/encode.rs b/src/encode.rs index 271d9257..c2c61b21 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -2,10 +2,11 @@ use std::fmt::{Display, Formatter, Result, Write}; use itertools::Itertools; +use crate::datetime::*; use crate::document::Document; use crate::inline_table::DEFAULT_INLINE_KEY_DECOR; use crate::key::Key; -use crate::repr::{DecorDisplay, Formatted, Repr}; +use crate::repr::{DecorDisplay, Formatted, Repr, ValueRepr}; use crate::table::{DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_TABLE_DECOR}; use crate::value::DEFAULT_VALUE_DECOR; use crate::{Array, InlineTable, Item, Table, Value}; @@ -28,20 +29,232 @@ impl Display for Repr { } } -impl Display for Formatted { +impl Display for Formatted +where + T: ValueRepr, +{ fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let repr = self.to_repr(); write!( f, "{}", - self.decor().display(self.repr(), DEFAULT_VALUE_DECOR) + self.decor().display(repr.as_ref(), DEFAULT_VALUE_DECOR) ) } } +impl ValueRepr for String { + fn to_repr(&self) -> Repr { + to_string_repr(self, None, None) + } +} + +pub(crate) fn to_string_repr( + value: &str, + style: Option, + literal: Option, +) -> Repr { + let (style, literal) = match (style, literal) { + (Some(style), Some(literal)) => (style, literal), + (_, Some(literal)) => (infer_style(value).0, literal), + (Some(style), _) => (style, infer_style(value).1), + (_, _) => infer_style(value), + }; + + let mut output = String::with_capacity(value.len() * 2); + if literal { + output.push_str(style.literal_start()); + output.push_str(value); + output.push_str(style.literal_end()); + } else { + output.push_str(style.standard_start()); + for ch in value.chars() { + match ch { + '\u{8}' => output.push_str("\\b"), + '\u{9}' => output.push_str("\\t"), + '\u{a}' => match style { + StringStyle::NewlineTripple => output.push('\n'), + StringStyle::OnelineSingle => output.push_str("\\n"), + _ => unreachable!(), + }, + '\u{c}' => output.push_str("\\f"), + '\u{d}' => output.push_str("\\r"), + '\u{22}' => output.push_str("\\\""), + '\u{5c}' => output.push_str("\\\\"), + c if c <= '\u{1f}' || c == '\u{7f}' => { + write!(output, "\\u{:04X}", ch as u32).unwrap(); + } + ch => output.push(ch), + } + } + output.push_str(style.standard_end()); + } + + Repr::new_unchecked(output) +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum StringStyle { + NewlineTripple, + OnelineTripple, + OnelineSingle, +} + +impl StringStyle { + fn literal_start(self) -> &'static str { + match self { + Self::NewlineTripple => "'''\n", + Self::OnelineTripple => "'''", + Self::OnelineSingle => "'", + } + } + fn literal_end(self) -> &'static str { + match self { + Self::NewlineTripple => "'''", + Self::OnelineTripple => "'''", + Self::OnelineSingle => "'", + } + } + + fn standard_start(self) -> &'static str { + match self { + Self::NewlineTripple => "\"\"\"\n", + // note: OnelineTripple can happen if do_pretty wants to do + // '''it's one line''' + // but literal == false + Self::OnelineTripple | Self::OnelineSingle => "\"", + } + } + + fn standard_end(self) -> &'static str { + match self { + Self::NewlineTripple => "\"\"\"", + // note: OnelineTripple can happen if do_pretty wants to do + // '''it's one line''' + // but literal == false + Self::OnelineTripple | Self::OnelineSingle => "\"", + } + } +} + +fn infer_style(value: &str) -> (StringStyle, bool) { + // For doing pretty prints we store in a new String + // because there are too many cases where pretty cannot + // work. We need to determine: + // - if we are a "multi-line" pretty (if there are \n) + // - if ['''] appears if multi or ['] if single + // - if there are any invalid control characters + // + // Doing it any other way would require multiple passes + // to determine if a pretty string works or not. + let mut out = String::with_capacity(value.len() * 2); + let mut ty = StringStyle::OnelineSingle; + // found consecutive single quotes + let mut max_found_singles = 0; + let mut found_singles = 0; + let mut prefer_literal = false; + let mut can_be_pretty = true; + + for ch in value.chars() { + if can_be_pretty { + if ch == '\'' { + found_singles += 1; + if found_singles >= 3 { + can_be_pretty = false; + } + } else { + if found_singles > max_found_singles { + max_found_singles = found_singles; + } + found_singles = 0 + } + match ch { + '\t' => {} + '\\' => { + prefer_literal = true; + } + '\n' => ty = StringStyle::NewlineTripple, + // Escape codes are needed if any ascii control + // characters are present, including \b \f \r. + c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false, + _ => {} + } + out.push(ch); + } else { + // the string cannot be represented as pretty, + // still check if it should be multiline + if ch == '\n' { + ty = StringStyle::NewlineTripple; + } + } + } + if found_singles > 0 && value.ends_with('\'') { + // We cannot escape the ending quote so we must use """ + can_be_pretty = false; + } + if !prefer_literal { + can_be_pretty = false; + } + if !can_be_pretty { + debug_assert!(ty != StringStyle::OnelineTripple); + return (ty, false); + } + if found_singles > max_found_singles { + max_found_singles = found_singles; + } + debug_assert!(max_found_singles < 3); + if ty == StringStyle::OnelineSingle && max_found_singles >= 1 { + // no newlines, but must use ''' because it has ' in it + ty = StringStyle::OnelineTripple; + } + (ty, true) +} + +impl ValueRepr for i64 { + fn to_repr(&self) -> Repr { + Repr::new_unchecked(self.to_string()) + } +} + +impl ValueRepr for f64 { + fn to_repr(&self) -> Repr { + to_f64_repr(*self) + } +} + +fn to_f64_repr(f: f64) -> Repr { + let repr = match (f.is_sign_negative(), f.is_nan(), f == 0.0) { + (true, true, _) => "-nan".to_owned(), + (false, true, _) => "nan".to_owned(), + (true, false, true) => "-0.0".to_owned(), + (false, false, true) => "0.0".to_owned(), + (_, false, false) => { + if f % 1.0 == 0.0 { + format!("{}.0", f) + } else { + format!("{}", f) + } + } + }; + Repr::new_unchecked(repr) +} + +impl ValueRepr for bool { + fn to_repr(&self) -> Repr { + Repr::new_unchecked(self.to_string()) + } +} + +impl ValueRepr for Datetime { + fn to_repr(&self) -> Repr { + Repr::new_unchecked(self.to_string()) + } +} + impl Display for Key { fn fmt(&self, f: &mut Formatter<'_>) -> Result { // HACK: For now, leaving off decor since we don't know the defaults to use in this context - self.repr().fmt(f) + self.to_repr().as_ref().fmt(f) } } diff --git a/src/index.rs b/src/index.rs index 133d6230..83508d30 100644 --- a/src/index.rs +++ b/src/index.rs @@ -8,7 +8,7 @@ use crate::{value, InlineTable, Item, Table, Value}; // copied from // https://github.com/serde-rs/json/blob/master/src/value/index.rs -pub trait Index: private::Sealed { +pub trait Index: crate::private::Sealed { /// Return `Option::None` if the key is not already in the array or table. #[doc(hidden)] fn index<'v>(&self, v: &'v Item) -> Option<&'v Item>; @@ -151,12 +151,3 @@ impl<'s> ops::IndexMut<&'s str> for Document { self.root.index_mut(key) } } - -// Prevent users from implementing the Index trait. -mod private { - pub trait Sealed {} - impl Sealed for usize {} - impl Sealed for str {} - impl Sealed for String {} - impl<'a, T: ?Sized> Sealed for &'a T where T: Sealed {} -} diff --git a/src/key.rs b/src/key.rs index b14e1bbf..57f9fa10 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,11 +1,12 @@ +use std::borrow::Cow; use std::str::FromStr; use combine::stream::position::Stream; +use crate::encode::{to_string_repr, StringStyle}; use crate::parser; use crate::parser::is_unquoted_char; use crate::repr::{Decor, InternalString, Repr}; -use crate::value::{to_string_repr, StringStyle}; /// Key as part of a Key/Value Pair or a table header. /// @@ -32,26 +33,25 @@ use crate::value::{to_string_repr, StringStyle}; #[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)] pub struct Key { key: InternalString, - pub(crate) repr: Repr, + pub(crate) repr: Option, pub(crate) decor: Decor, } impl Key { /// Create a new table key pub fn new(key: impl AsRef) -> Self { - let key = key.as_ref(); - let repr = to_key_repr(key); - Self::new_unchecked(repr, key.to_owned()) - } - - pub(crate) fn new_unchecked(repr: Repr, key: InternalString) -> Self { Self { - key, - repr, + key: key.as_ref().into(), + repr: None, decor: Default::default(), } } + pub(crate) fn with_repr_unchecked(mut self, repr: Repr) -> Self { + self.repr = Some(repr); + self + } + /// While creating the `Key`, add `Decor` to it pub fn with_decor(mut self, decor: Decor) -> Self { self.decor = decor; @@ -64,8 +64,11 @@ impl Key { } /// Returns the key raw representation. - pub fn repr(&self) -> &Repr { - &self.repr + pub fn to_repr(&self) -> Cow { + self.repr + .as_ref() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(to_key_repr(&self.key))) } /// Returns the surrounding whitespace @@ -78,6 +81,11 @@ impl Key { &self.decor } + /// Auto formats the key. + pub fn fmt(&mut self) { + self.repr = Some(to_key_repr(&self.key)); + } + fn try_parse(s: &str) -> Result { use combine::EasyParser; let result = parser::key_parser().easy_parse(Stream::new(s)); @@ -85,7 +93,7 @@ impl Key { Ok((_, ref rest)) if !rest.input.is_empty() => { Err(parser::TomlError::from_unparsed(rest.positioner, s)) } - Ok(((raw, key), _)) => Ok(Key::new_unchecked(Repr::new_unchecked(raw), key)), + Ok(((raw, key), _)) => Ok(Key::new(key).with_repr_unchecked(Repr::new_unchecked(raw))), Err(e) => Err(parser::TomlError::new(e, s)), } } diff --git a/src/lib.rs b/src/lib.rs index 6496d8ac..27ec5f47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,3 +96,16 @@ pub use crate::table::{ Entry, IntoIter, Iter, IterMut, OccupiedEntry, Table, TableLike, VacantEntry, }; pub use crate::value::Value; + +// Prevent users from some traits. +pub(crate) mod private { + pub trait Sealed {} + impl Sealed for usize {} + impl Sealed for str {} + impl Sealed for String {} + impl Sealed for i64 {} + impl Sealed for f64 {} + impl Sealed for bool {} + impl Sealed for crate::Datetime {} + impl<'a, T: ?Sized> Sealed for &'a T where T: Sealed {} +} diff --git a/src/parser/key.rs b/src/parser/key.rs index 0019b619..8ea45258 100644 --- a/src/parser/key.rs +++ b/src/parser/key.rs @@ -17,7 +17,7 @@ parse!(key() -> Vec1, { simple_key(), ws(), )).map(|(pre, (raw, key), suffix)| { - Key::new_unchecked(Repr::new_unchecked(raw), key).with_decor(Decor::new(pre, suffix)) + Key::new(key).with_repr_unchecked(Repr::new_unchecked(raw)).with_decor(Decor::new(pre, suffix)) }), char(DOT_SEP) ).map(|k| Vec1::try_from_vec(k).expect("parser should guarantee this")) diff --git a/src/parser/table.rs b/src/parser/table.rs index c25bd4e5..76a2925a 100644 --- a/src/parser/table.rs +++ b/src/parser/table.rs @@ -10,6 +10,7 @@ use combine::parser::char::char; use combine::parser::range::range; use combine::stream::RangeStream; use combine::*; +use itertools::Itertools; use std::cell::RefCell; use std::mem; // https://github.com/rust-lang/rust/issues/41358 @@ -71,10 +72,13 @@ parser! { pub(crate) fn duplicate_key(path: &[Key], i: usize) -> CustomError { assert!(i < path.len()); - let header: Vec<&str> = path[..i].iter().map(|k| k.repr().as_raw()).collect(); + let header = path[..i] + .iter() + .map(|k| k.to_repr().as_ref().as_raw().to_owned()) + .join("."); CustomError::DuplicateKey { - key: path[i].repr().as_raw().into(), - table: format!("[{}]", header.join(".")), + key: path[i].to_repr().as_ref().as_raw().into(), + table: format!("[{}]", header), } } diff --git a/src/parser/value.rs b/src/parser/value.rs index 291e2fb2..ad35818b 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -17,7 +17,6 @@ parse!(value() -> v::Value, { .map(|s| v::Value::String(Formatted::new( s, - Repr::new_unchecked(""), )) ), boolean() @@ -38,19 +37,19 @@ parse!(value() -> v::Value, { fn apply_raw(mut val: Value, raw: &str) -> Value { match val { Value::String(ref mut f) => { - f.repr = Repr::new_unchecked(raw); + f.set_repr_unchecked(Repr::new_unchecked(raw)); } Value::Integer(ref mut f) => { - f.repr = Repr::new_unchecked(raw); + f.set_repr_unchecked(Repr::new_unchecked(raw)); } Value::Float(ref mut f) => { - f.repr = Repr::new_unchecked(raw); + f.set_repr_unchecked(Repr::new_unchecked(raw)); } Value::Boolean(ref mut f) => { - f.repr = Repr::new_unchecked(raw); + f.set_repr_unchecked(Repr::new_unchecked(raw)); } Value::Datetime(ref mut f) => { - f.repr = Repr::new_unchecked(raw); + f.set_repr_unchecked(Repr::new_unchecked(raw)); } Value::Array(_) | Value::InlineTable(_) => {} }; diff --git a/src/repr.rs b/src/repr.rs index 91293678..961147a2 100644 --- a/src/repr.rs +++ b/src/repr.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + pub(crate) type InternalString = String; /// A value together with its `to_string` representation, @@ -5,11 +7,27 @@ pub(crate) type InternalString = String; #[derive(Eq, PartialEq, Clone, Debug, Hash)] pub struct Formatted { value: T, - pub(crate) repr: Repr, + repr: Option, decor: Decor, } -impl Formatted { +impl Formatted +where + T: ValueRepr, +{ + /// Default-formatted value + pub fn new(value: T) -> Self { + Self { + value, + repr: None, + decor: Default::default(), + } + } + + pub(crate) fn set_repr_unchecked(&mut self, repr: Repr) { + self.repr = Some(repr); + } + /// The wrapped value pub fn value(&self) -> &T { &self.value @@ -20,9 +38,12 @@ impl Formatted { self.value } - /// The TOML representation of the value - pub fn repr(&self) -> &Repr { - &self.repr + /// Returns the key raw representation. + pub fn to_repr(&self) -> Cow { + self.repr + .as_ref() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(self.value.to_repr())) } /// Returns the surrounding whitespace @@ -35,15 +56,17 @@ impl Formatted { &self.decor } - pub(crate) fn new(v: T, repr: Repr) -> Self { - Self { - value: v, - repr, - decor: Default::default(), - } + /// Auto formats the value. + pub fn fmt(&mut self) { + self.repr = Some(self.value.to_repr()); } } +pub trait ValueRepr: crate::private::Sealed { + /// The TOML representation of the value + fn to_repr(&self) -> Repr; +} + /// TOML-encoded value #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Hash)] pub struct Repr { diff --git a/src/value.rs b/src/value.rs index 5bd40104..c33c0bdb 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,3 @@ -use std::fmt::Write; use std::iter::FromIterator; use std::str::FromStr; @@ -7,7 +6,7 @@ use combine::stream::position::Stream; use crate::datetime::*; use crate::key::Key; use crate::parser; -use crate::repr::{Decor, Formatted, InternalString, Repr}; +use crate::repr::{Decor, Formatted, InternalString}; use crate::{Array, InlineTable}; /// Representation of a TOML Value (as part of a Key/Value Pair). @@ -237,9 +236,8 @@ impl<'b> From<&'b Value> for Value { impl<'b> From<&'b str> for Value { fn from(s: &'b str) -> Self { - let repr = to_string_repr(s, None, None); let value = s.to_owned(); - Value::String(Formatted::new(value, repr)) + Value::String(Formatted::new(value)) } } @@ -255,207 +253,27 @@ impl From for Value { } } -pub(crate) fn to_string_repr( - value: &str, - style: Option, - literal: Option, -) -> Repr { - let (style, literal) = match (style, literal) { - (Some(style), Some(literal)) => (style, literal), - (_, Some(literal)) => (infer_style(value).0, literal), - (Some(style), _) => (style, infer_style(value).1), - (_, _) => infer_style(value), - }; - - let mut output = String::with_capacity(value.len() * 2); - if literal { - output.push_str(style.literal_start()); - output.push_str(value); - output.push_str(style.literal_end()); - } else { - output.push_str(style.standard_start()); - for ch in value.chars() { - match ch { - '\u{8}' => output.push_str("\\b"), - '\u{9}' => output.push_str("\\t"), - '\u{a}' => match style { - StringStyle::NewlineTripple => output.push('\n'), - StringStyle::OnelineSingle => output.push_str("\\n"), - _ => unreachable!(), - }, - '\u{c}' => output.push_str("\\f"), - '\u{d}' => output.push_str("\\r"), - '\u{22}' => output.push_str("\\\""), - '\u{5c}' => output.push_str("\\\\"), - c if c <= '\u{1f}' || c == '\u{7f}' => { - write!(output, "\\u{:04X}", ch as u32).unwrap(); - } - ch => output.push(ch), - } - } - output.push_str(style.standard_end()); - } - - Repr::new_unchecked(output) -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum StringStyle { - NewlineTripple, - OnelineTripple, - OnelineSingle, -} - -impl StringStyle { - fn literal_start(self) -> &'static str { - match self { - Self::NewlineTripple => "'''\n", - Self::OnelineTripple => "'''", - Self::OnelineSingle => "'", - } - } - fn literal_end(self) -> &'static str { - match self { - Self::NewlineTripple => "'''", - Self::OnelineTripple => "'''", - Self::OnelineSingle => "'", - } - } - - fn standard_start(self) -> &'static str { - match self { - Self::NewlineTripple => "\"\"\"\n", - // note: OnelineTripple can happen if do_pretty wants to do - // '''it's one line''' - // but literal == false - Self::OnelineTripple | Self::OnelineSingle => "\"", - } - } - - fn standard_end(self) -> &'static str { - match self { - Self::NewlineTripple => "\"\"\"", - // note: OnelineTripple can happen if do_pretty wants to do - // '''it's one line''' - // but literal == false - Self::OnelineTripple | Self::OnelineSingle => "\"", - } - } -} - -fn infer_style(value: &str) -> (StringStyle, bool) { - // For doing pretty prints we store in a new String - // because there are too many cases where pretty cannot - // work. We need to determine: - // - if we are a "multi-line" pretty (if there are \n) - // - if ['''] appears if multi or ['] if single - // - if there are any invalid control characters - // - // Doing it any other way would require multiple passes - // to determine if a pretty string works or not. - let mut out = String::with_capacity(value.len() * 2); - let mut ty = StringStyle::OnelineSingle; - // found consecutive single quotes - let mut max_found_singles = 0; - let mut found_singles = 0; - let mut prefer_literal = false; - let mut can_be_pretty = true; - - for ch in value.chars() { - if can_be_pretty { - if ch == '\'' { - found_singles += 1; - if found_singles >= 3 { - can_be_pretty = false; - } - } else { - if found_singles > max_found_singles { - max_found_singles = found_singles; - } - found_singles = 0 - } - match ch { - '\t' => {} - '\\' => { - prefer_literal = true; - } - '\n' => ty = StringStyle::NewlineTripple, - // Escape codes are needed if any ascii control - // characters are present, including \b \f \r. - c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false, - _ => {} - } - out.push(ch); - } else { - // the string cannot be represented as pretty, - // still check if it should be multiline - if ch == '\n' { - ty = StringStyle::NewlineTripple; - } - } - } - if found_singles > 0 && value.ends_with('\'') { - // We cannot escape the ending quote so we must use """ - can_be_pretty = false; - } - if !prefer_literal { - can_be_pretty = false; - } - if !can_be_pretty { - debug_assert!(ty != StringStyle::OnelineTripple); - return (ty, false); - } - if found_singles > max_found_singles { - max_found_singles = found_singles; - } - debug_assert!(max_found_singles < 3); - if ty == StringStyle::OnelineSingle && max_found_singles >= 1 { - // no newlines, but must use ''' because it has ' in it - ty = StringStyle::OnelineTripple; - } - (ty, true) -} - impl From for Value { fn from(i: i64) -> Self { - Value::Integer(Formatted::new(i, Repr::new_unchecked(i.to_string()))) + Value::Integer(Formatted::new(i)) } } impl From for Value { fn from(f: f64) -> Self { - let repr = match (f.is_sign_negative(), f.is_nan(), f == 0.0) { - (true, true, _) => "-nan".to_owned(), - (false, true, _) => "nan".to_owned(), - (true, false, true) => "-0.0".to_owned(), - (false, false, true) => "0.0".to_owned(), - (_, false, false) => { - if f % 1.0 == 0.0 { - format!("{}.0", f) - } else { - format!("{}", f) - } - } - }; - let repr = Repr::new_unchecked(repr); - - Value::Float(Formatted::new(f, repr)) + Value::Float(Formatted::new(f)) } } impl From for Value { fn from(b: bool) -> Self { - Value::Boolean(Formatted::new( - b, - Repr::new_unchecked(if b { "true" } else { "false" }), - )) + Value::Boolean(Formatted::new(b)) } } impl From for Value { fn from(d: Datetime) -> Self { - let s = d.to_string(); - Value::Datetime(Formatted::new(d, Repr::new_unchecked(s))) + Value::Datetime(Formatted::new(d)) } } diff --git a/tests/test_valid.rs b/tests/test_valid.rs index f07b5fd0..bd6e3bd4 100644 --- a/tests/test_valid.rs +++ b/tests/test_valid.rs @@ -16,9 +16,9 @@ fn pair_to_json((key, value): (&str, Item)) -> (String, Json) { Value::String(ref s) => typed_json("string", Json::String(s.value().clone())), Value::Integer(ref i) => typed_json("integer", Json::String(format!("{}", i.value()))), Value::Float(ref f) => typed_json("float", Json::String(format!("{}", f.value()))), - Value::Boolean(ref b) => typed_json("bool", Json::String(b.repr().as_raw().into())), + Value::Boolean(ref b) => typed_json("bool", Json::String(b.to_repr().as_raw().into())), Value::Datetime(ref d) => { - typed_json("datetime", Json::String(d.repr().as_raw().into())) + typed_json("datetime", Json::String(d.to_repr().as_raw().into())) } Value::Array(ref a) => { let json = Json::Array(a.iter().map(value_to_json).collect::>());