From f66d8bcf33530c858a502bfa170f2383a8cbc204 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 29 Jan 2017 16:53:20 -0800 Subject: 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 --- .travis.yml | 6 +- Cargo.toml | 10 +- examples/decode.rs | 18 +- examples/toml2json.rs | 52 +- serde-tests/Cargo.toml | 20 - serde-tests/build.rs | 13 - serde-tests/lib.rs | 1 - serde-tests/test.rs | 1 - serde-tests/test.rs.in | 482 -------- src/datetime.rs | 424 ++++++++ src/de.rs | 1195 ++++++++++++++++++++ src/decoder/mod.rs | 240 ---- src/decoder/rustc_serialize.rs | 371 ------- src/decoder/serde.rs | 773 ------------- src/display.rs | 212 ---- src/encoder/mod.rs | 222 ---- src/encoder/rustc_serialize.rs | 748 ------------- src/encoder/serde.rs | 339 ------ src/lib.rs | 640 +++-------- src/parser.rs | 1627 ---------------------------- src/ser.rs | 1006 +++++++++++++++++ src/tokens.rs | 620 +++++++++++ src/value.rs | 894 +++++++++++++++ src/value/display.rs | 134 +++ tests/datetime.rs | 58 + tests/display.rs | 97 ++ tests/formatting.rs | 21 +- tests/invalid-misc.rs | 12 + tests/invalid.rs | 16 +- tests/invalid/datetime-malformed-no-z.toml | 1 - tests/parser.rs | 495 +++++++++ tests/serde.rs | 496 +++++++++ tests/valid.rs | 68 +- 33 files changed, 5663 insertions(+), 5649 deletions(-) delete mode 100644 serde-tests/Cargo.toml delete mode 100644 serde-tests/build.rs delete mode 100644 serde-tests/lib.rs delete mode 100644 serde-tests/test.rs delete mode 100644 serde-tests/test.rs.in create mode 100644 src/datetime.rs create mode 100644 src/de.rs delete mode 100644 src/decoder/mod.rs delete mode 100644 src/decoder/rustc_serialize.rs delete mode 100644 src/decoder/serde.rs delete mode 100644 src/display.rs delete mode 100644 src/encoder/mod.rs delete mode 100644 src/encoder/rustc_serialize.rs delete mode 100644 src/encoder/serde.rs delete mode 100644 src/parser.rs create mode 100644 src/ser.rs create mode 100644 src/tokens.rs create mode 100644 src/value.rs create mode 100644 src/value/display.rs create mode 100644 tests/datetime.rs create mode 100644 tests/display.rs create mode 100644 tests/invalid-misc.rs delete mode 100644 tests/invalid/datetime-malformed-no-z.toml create mode 100644 tests/parser.rs create mode 100644 tests/serde.rs diff --git a/.travis.yml b/.travis.yml index dc725d4..a7c4b12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,11 +7,7 @@ sudo: false before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH script: - - cargo build --verbose - - cargo build --verbose --no-default-features - - cargo build --verbose --features serde --no-default-features - - cargo test --verbose --features serde - - cargo test --verbose --manifest-path serde-tests/Cargo.toml + - cargo test - rustdoc --test README.md -L target - cargo doc --no-deps after_success: diff --git a/Cargo.toml b/Cargo.toml index 0e47242..31c60e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,4 @@ [package] - name = "toml" version = "0.2.1" authors = ["Alex Crichton "] @@ -16,11 +15,8 @@ facilitate deserializing and serializing Rust structures. """ [dependencies] -rustc-serialize = { optional = true, version = "0.3.0" } -serde = { optional = true, version = "0.8" } - -[features] -default = ["rustc-serialize"] +serde = "0.9.6" [dev-dependencies] -rustc-serialize = "0.3" +serde_derive = "0.9" +serde_json = "0.9" diff --git a/examples/decode.rs b/examples/decode.rs index 9124596..8390e15 100644 --- a/examples/decode.rs +++ b/examples/decode.rs @@ -1,4 +1,4 @@ -//! An example showing off the usage of `RustcDecodable` to automatically decode +//! An example showing off the usage of `Deserialize` to automatically decode //! TOML into a Rust `struct` //! //! Note that this works similarly with `serde` as well. @@ -6,11 +6,12 @@ #![deny(warnings)] extern crate toml; -extern crate rustc_serialize; +#[macro_use] +extern crate serde_derive; /// This is what we're going to decode into. Each field is optional, meaning /// that it doesn't have to be present in TOML. -#[derive(Debug, RustcDecodable)] +#[derive(Debug, Deserialize)] struct Config { global_string: Option, global_integer: Option, @@ -22,13 +23,13 @@ struct Config { /// table. /// /// Again, each field is optional, meaning they don't have to be present. -#[derive(Debug, RustcDecodable)] +#[derive(Debug, Deserialize)] struct ServerConfig { ip: Option, port: Option, } -#[derive(Debug, RustcDecodable)] +#[derive(Debug, Deserialize)] struct PeerConfig { ip: Option, port: Option, @@ -51,11 +52,6 @@ fn main() { ip = "127.0.0.1" "#; - // Use the `decode_str` convenience here to decode a TOML string directly - // into the `Config` struct. - // - // Note that the errors reported here won't necessarily be the best, but you - // can get higher fidelity errors working with `toml::Parser` directly. - let decoded: Config = toml::decode_str(toml_str).unwrap(); + let decoded: Config = toml::from_str(toml_str).unwrap(); println!("{:#?}", decoded); } diff --git a/examples/toml2json.rs b/examples/toml2json.rs index 0d40680..1ed441a 100644 --- a/examples/toml2json.rs +++ b/examples/toml2json.rs @@ -1,57 +1,51 @@ #![deny(warnings)] extern crate toml; -extern crate rustc_serialize; +extern crate serde_json; use std::fs::File; use std::env; use std::io; use std::io::prelude::*; -use toml::Value; -use rustc_serialize::json::Json; +use toml::Value as Toml; +use serde_json::Value as Json; fn main() { let mut args = env::args(); let mut input = String::new(); - let filename = if args.len() > 1 { + if args.len() > 1 { let name = args.nth(1).unwrap(); File::open(&name).and_then(|mut f| { f.read_to_string(&mut input) }).unwrap(); - name } else { io::stdin().read_to_string(&mut input).unwrap(); - "".to_string() - }; + } - let mut parser = toml::Parser::new(&input); - let toml = match parser.parse() { - Some(toml) => toml, - None => { - for err in &parser.errors { - let (loline, locol) = parser.to_linecol(err.lo); - let (hiline, hicol) = parser.to_linecol(err.hi); - println!("{}:{}:{}-{}:{} error: {}", - filename, loline, locol, hiline, hicol, err.desc); - } - return + match input.parse() { + Ok(toml) => { + let json = convert(toml); + println!("{}", serde_json::to_string_pretty(&json).unwrap()); } - }; - let json = convert(Value::Table(toml)); - println!("{}", json.pretty()); + Err(error) => println!("failed to parse TOML: {}", error), + } } -fn convert(toml: Value) -> Json { +fn convert(toml: Toml) -> Json { match toml { - Value::String(s) => Json::String(s), - Value::Integer(i) => Json::I64(i), - Value::Float(f) => Json::F64(f), - Value::Boolean(b) => Json::Boolean(b), - Value::Array(arr) => Json::Array(arr.into_iter().map(convert).collect()), - Value::Table(table) => Json::Object(table.into_iter().map(|(k, v)| { + Toml::String(s) => Json::String(s), + Toml::Integer(i) => Json::Number(i.into()), + Toml::Float(f) => { + let n = serde_json::Number::from_f64(f) + .expect("float infinite and nan not allowed"); + Json::Number(n) + } + Toml::Boolean(b) => Json::Bool(b), + Toml::Array(arr) => Json::Array(arr.into_iter().map(convert).collect()), + Toml::Table(table) => Json::Object(table.into_iter().map(|(k, v)| { (k, convert(v)) }).collect()), - Value::Datetime(dt) => Json::String(dt), + Toml::Datetime(dt) => Json::String(dt.to_string()), } } diff --git a/serde-tests/Cargo.toml b/serde-tests/Cargo.toml deleted file mode 100644 index a41747f..0000000 --- a/serde-tests/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "serde-tests" -version = "0.1.0" -authors = ["Alex Crichton "] -build = "build.rs" - -[dependencies] -serde = "0.8" -toml = { path = "..", features = ["serde"] } - -[build-dependencies] -serde_codegen = "0.8" - -[lib] -name = "serde_tests" -path = "lib.rs" - -[[test]] -name = "serde" -path = "test.rs" diff --git a/serde-tests/build.rs b/serde-tests/build.rs deleted file mode 100644 index 1cc5062..0000000 --- a/serde-tests/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -extern crate serde_codegen; - -use std::env; -use std::path::Path; - -fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - - let src = Path::new("test.rs.in"); - let dst = Path::new(&out_dir).join("test.rs"); - - serde_codegen::expand(&src, &dst).unwrap(); -} diff --git a/serde-tests/lib.rs b/serde-tests/lib.rs deleted file mode 100644 index 65e2cc3..0000000 --- a/serde-tests/lib.rs +++ /dev/null @@ -1 +0,0 @@ -// intentionally blank diff --git a/serde-tests/test.rs b/serde-tests/test.rs deleted file mode 100644 index e8de9fa..0000000 --- a/serde-tests/test.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/test.rs")); diff --git a/serde-tests/test.rs.in b/serde-tests/test.rs.in deleted file mode 100644 index 15bb164..0000000 --- a/serde-tests/test.rs.in +++ /dev/null @@ -1,482 +0,0 @@ -extern crate serde; -extern crate toml; - -use std::collections::{BTreeMap, HashSet}; -use serde::{Deserialize, Serialize, Deserializer}; - -use toml::{Encoder, Decoder, DecodeError}; -use toml::Value; -use toml::Value::{Table, Integer, Array, Float}; - -macro_rules! t { - ($e:expr) => (match $e { - Ok(t) => t, - Err(e) => panic!("{} failed with {}", stringify!($e), e), - }) -} - -macro_rules! encode( ($t:expr) => ({ - let mut e = Encoder::new(); - t!($t.serialize(&mut e)); - e.toml -}) ); - -macro_rules! decode( ($t:expr) => ({ - let mut d = Decoder::new($t); - t!(Deserialize::deserialize(&mut d)) -}) ); - -macro_rules! map( ($($k:ident, $v:expr),*) => ({ - let mut _m = BTreeMap::new(); - $(_m.insert(stringify!($k).to_string(), $v);)* - _m -}) ); - -#[test] -fn smoke() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: isize } - - let v = Foo { a: 2 }; - assert_eq!(encode!(v), map! { a, Integer(2) }); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn smoke_hyphen() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a_b: isize } - - let v = Foo { a_b: 2 }; - assert_eq!(encode!(v), map! { a_b, Integer(2) }); - assert_eq!(v, decode!(Table(encode!(v)))); - - let mut m = BTreeMap::new(); - m.insert("a-b".to_string(), Integer(2)); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn nested() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: isize, b: Bar } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar { a: String } - - let v = Foo { a: 2, b: Bar { a: "test".to_string() } }; - assert_eq!(encode!(v), - map! { - a, Integer(2), - b, Table(map! { - a, Value::String("test".to_string()) - }) - }); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn application_decode_error() { - #[derive(PartialEq, Debug)] - struct Range10(usize); - impl Deserialize for Range10 { - fn deserialize(d: &mut D) -> Result { - let x: usize = try!(Deserialize::deserialize(d)); - if x > 10 { - Err(serde::de::Error::custom("more than 10")) - } else { - Ok(Range10(x)) - } - } - } - let mut d_good = Decoder::new(Integer(5)); - let mut d_bad1 = Decoder::new(Value::String("not an isize".to_string())); - let mut d_bad2 = Decoder::new(Integer(11)); - - assert_eq!(Ok(Range10(5)), Deserialize::deserialize(&mut d_good)); - - let err1: Result = Deserialize::deserialize(&mut d_bad1); - assert!(err1.is_err()); - let err2: Result = Deserialize::deserialize(&mut d_bad2); - assert!(err2.is_err()); -} - -#[test] -fn array() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Vec } - - let v = Foo { a: vec![1, 2, 3, 4] }; - assert_eq!(encode!(v), - map! { - a, Array(vec![ - Integer(1), - Integer(2), - Integer(3), - Integer(4) - ]) - }); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn tuple() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: (isize, isize, isize, isize) } - - let v = Foo { a: (1, 2, 3, 4) }; - assert_eq!(encode!(v), - map! { - a, Array(vec![ - Integer(1), - Integer(2), - Integer(3), - Integer(4) - ]) - }); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn inner_structs_with_options() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { - a: Option>, - b: Bar, - } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar { - a: String, - b: f64, - } - - let v = Foo { - a: Some(Box::new(Foo { - a: None, - b: Bar { a: "foo".to_string(), b: 4.5 }, - })), - b: Bar { a: "bar".to_string(), b: 1.0 }, - }; - assert_eq!(encode!(v), - map! { - a, Table(map! { - b, Table(map! { - a, Value::String("foo".to_string()), - b, Float(4.5) - }) - }), - b, Table(map! { - a, Value::String("bar".to_string()), - b, Float(1.0) - }) - }); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn hashmap() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { - map: BTreeMap, - set: HashSet, - } - - let v = Foo { - map: { - let mut m = BTreeMap::new(); - m.insert("foo".to_string(), 10); - m.insert("bar".to_string(), 4); - m - }, - set: { - let mut s = HashSet::new(); - s.insert('a'); - s - }, - }; - assert_eq!(encode!(v), - map! { - map, Table(map! { - foo, Integer(10), - bar, Integer(4) - }), - set, Array(vec![Value::String("a".to_string())]) - } - ); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn tuple_struct() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo(isize, String, f64); - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar { - whee: Foo, - } - - let v = Bar { - whee: Foo(1, "foo".to_string(), 4.5) - }; - assert_eq!( - encode!(v), - map! { - whee, Value::Array(vec![ - Integer(1), - Value::String("foo".to_string()), - Float(4.5), - ]) - } - ); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn table_array() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Vec, } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar { a: isize } - - let v = Foo { a: vec![Bar { a: 1 }, Bar { a: 2 }] }; - assert_eq!( - encode!(v), - map! { - a, Array(vec![ - Table(map!{ a, Integer(1) }), - Table(map!{ a, Integer(2) }), - ]) - } - ); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn type_errors() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { bar: isize } - - let mut d = Decoder::new(Table(map! { - bar, Float(1.0) - })); - let a: Result = Deserialize::deserialize(&mut d); - // serde uses FromPrimitive, that's why this works - match a { - Ok(..) => panic!("should not have decoded"), - Err(e) => { - assert_eq!(format!("{}", e), - "expected a value of type `integer`, but \ - found a value of type `float` for the key `bar`"); - } - } -} - -#[test] -fn missing_errors() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { bar: isize } - - let mut d = Decoder::new(Table(map! { - })); - let a: Result = Deserialize::deserialize(&mut d); - match a { - Ok(..) => panic!("should not have decoded"), - Err(e) => { - assert_eq!(format!("{}", e), - "expected a value for the key `bar`"); - } - } -} - -#[test] -fn parse_enum() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: E } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - enum E { - Bar(isize), - Baz(f64), - Last(Foo2), - } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo2 { - test: String, - } - - let v = Foo { a: E::Bar(10) }; - // technically serde is correct here. a single element tuple still is a - // tuple and therefor a sequence - assert_eq!( - encode!(v), - map! { a, Integer(10) } - ); - assert_eq!(v, decode!(Table(encode!(v)))); - - let v = Foo { a: E::Baz(10.2) }; - assert_eq!( - encode!(v), - map! { a, Float(10.2) } - ); - assert_eq!(v, decode!(Table(encode!(v)))); - - let v = Foo { a: E::Last(Foo2 { test: "test".to_string() }) }; - assert_eq!( - encode!(v), - map! { a, Table(map! { test, Value::String("test".to_string()) }) } - ); - assert_eq!(v, decode!(Table(encode!(v)))); -} - -#[test] -fn unused_fields() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: isize } - - let v = Foo { a: 2 }; - let mut d = Decoder::new(Table(map! { - a, Integer(2), - b, Integer(5) - })); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); - - assert_eq!(d.toml, Some(Table(map! { - b, Integer(5) - }))); -} - -#[test] -fn unused_fields2() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Bar } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar { a: isize } - - let v = Foo { a: Bar { a: 2 } }; - let mut d = Decoder::new(Table(map! { - a, Table(map! { - a, Integer(2), - b, Integer(5) - }) - })); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); - - assert_eq!(d.toml, Some(Table(map! { - a, Table(map! { - b, Integer(5) - }) - }))); -} - -#[test] -fn unused_fields3() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Bar } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar { a: isize } - - let v = Foo { a: Bar { a: 2 } }; - let mut d = Decoder::new(Table(map! { - a, Table(map! { - a, Integer(2) - }) - })); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); - - assert_eq!(d.toml, None); -} - -#[test] -fn unused_fields4() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: BTreeMap } - - let v = Foo { a: map! { a, "foo".to_string() } }; - let mut d = Decoder::new(Table(map! { - a, Table(map! { - a, Value::String("foo".to_string()) - }) - })); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); - - assert_eq!(d.toml, None); -} - -#[test] -fn unused_fields5() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Vec } - - let v = Foo { a: vec!["a".to_string()] }; - let mut d = Decoder::new(Table(map! { - a, Array(vec![Value::String("a".to_string())]) - })); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); - - assert_eq!(d.toml, None); -} - -#[test] -fn unused_fields6() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Option> } - - let v = Foo { a: Some(vec![]) }; - let mut d = Decoder::new(Table(map! { - a, Array(vec![]) - })); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); - - assert_eq!(d.toml, None); -} - -#[test] -fn unused_fields7() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Vec } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar { a: isize } - - let v = Foo { a: vec![Bar { a: 1 }] }; - let mut d = Decoder::new(Table(map! { - a, Array(vec![Table(map! { - a, Integer(1), - b, Integer(2) - })]) - })); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); - - assert_eq!(d.toml, Some(Table(map! { - a, Array(vec![Table(map! { - b, Integer(2) - })]) - }))); -} - -#[test] -fn empty_arrays() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Vec } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar; - - let v = Foo { a: vec![] }; - let mut d = Decoder::new(Table(map! {})); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); -} - -#[test] -fn empty_arrays2() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Foo { a: Option> } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Bar; - - let v = Foo { a: None }; - let mut d = Decoder::new(Table(map! {})); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); - - let v = Foo { a: Some(vec![]) }; - let mut d = Decoder::new(Table(map! { - a, Array(vec![]) - })); - assert_eq!(v, t!(Deserialize::deserialize(&mut d))); -} diff --git a/src/datetime.rs b/src/datetime.rs new file mode 100644 index 0000000..7a618dc --- /dev/null +++ b/src/datetime.rs @@ -0,0 +1,424 @@ +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, + time: Option