Merge pull request #137 from alexcrichton/serde-upgrade
Rewrite crate with serde support from ground up
This commit is contained in:
commit
f49150f85b
|
@ -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:
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,5 +1,4 @@
|
|||
[package]
|
||||
|
||||
name = "toml"
|
||||
version = "0.2.1"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
|
@ -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"
|
||||
|
|
|
@ -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<String>,
|
||||
global_integer: Option<u64>,
|
||||
|
@ -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<String>,
|
||||
port: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, RustcDecodable)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PeerConfig {
|
||||
ip: Option<String>,
|
||||
port: Option<u64>,
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
"<stdin>".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());
|
||||
}
|
||||
|
||||
fn convert(toml: Value) -> 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)| {
|
||||
(k, convert(v))
|
||||
}).collect()),
|
||||
Value::Datetime(dt) => Json::String(dt),
|
||||
Err(error) => println!("failed to parse TOML: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert(toml: Toml) -> Json {
|
||||
match toml {
|
||||
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()),
|
||||
Toml::Datetime(dt) => Json::String(dt.to_string()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "serde-tests"
|
||||
version = "0.1.0"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
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"
|
|
@ -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();
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
// intentionally blank
|
|
@ -1 +0,0 @@
|
|||
include!(concat!(env!("OUT_DIR"), "/test.rs"));
|
|
@ -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: Deserializer>(d: &mut D) -> Result<Range10, D::Error> {
|
||||
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<Range10, _> = Deserialize::deserialize(&mut d_bad1);
|
||||
assert!(err1.is_err());
|
||||
let err2: Result<Range10, _> = Deserialize::deserialize(&mut d_bad2);
|
||||
assert!(err2.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct Foo { a: Vec<isize> }
|
||||
|
||||
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<Box<Foo>>,
|
||||
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<String, isize>,
|
||||
set: HashSet<char>,
|
||||
}
|
||||
|
||||
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<Bar>, }
|
||||
#[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<Foo, DecodeError> = 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<Foo, DecodeError> = 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<String, String> }
|
||||
|
||||
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<String> }
|
||||
|
||||
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<Vec<String>> }
|
||||
|
||||
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<Bar> }
|
||||
#[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<Bar> }
|
||||
#[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<Vec<Bar>> }
|
||||
#[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)));
|
||||
}
|
424
src/datetime.rs
Normal file
424
src/datetime.rs
Normal file
|
@ -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<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"
|
||||
}
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
use std::error;
|
||||
use std::fmt;
|
||||
|
||||
use std::collections::{btree_map, BTreeMap};
|
||||
use std::iter::Peekable;
|
||||
|
||||
use Value;
|
||||
use self::DecodeErrorKind::*;
|
||||
|
||||
#[cfg(feature = "rustc-serialize")] mod rustc_serialize;
|
||||
#[cfg(feature = "serde")] mod serde;
|
||||
|
||||
/// A structure to transform TOML values into Rust values.
|
||||
///
|
||||
/// This decoder implements the serialization `Decoder` interface, allowing
|
||||
/// `Decodable` types to be generated by this decoder. The input is any
|
||||
/// arbitrary TOML value.
|
||||
pub struct Decoder {
|
||||
/// The TOML value left over after decoding. This can be used to inspect
|
||||
/// whether fields were decoded or not.
|
||||
pub toml: Option<Value>,
|
||||
cur_field: Option<String>,
|
||||
|
||||
// These aren't used if serde is in use
|
||||
#[cfg_attr(feature = "serde", allow(dead_code))]
|
||||
cur_map: Peekable<btree_map::IntoIter<String, Value>>,
|
||||
#[cfg_attr(feature = "serde", allow(dead_code))]
|
||||
leftover_map: ::Table,
|
||||
}
|
||||
|
||||
/// Description for errors which can occur while decoding a type.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct DecodeError {
|
||||
/// Field that this error applies to.
|
||||
pub field: Option<String>,
|
||||
/// The type of error which occurred while decoding,
|
||||
pub kind: DecodeErrorKind,
|
||||
}
|
||||
|
||||
/// Enumeration of possible errors which can occur while decoding a structure.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum DecodeErrorKind {
|
||||
/// An error flagged by the application, e.g. value out of range
|
||||
ApplicationError(String),
|
||||
/// A field was expected, but none was found.
|
||||
ExpectedField(/* type */ Option<&'static str>),
|
||||
/// A field was found, but it was not an expected one.
|
||||
UnknownField,
|
||||
/// A field was found, but it had the wrong type.
|
||||
ExpectedType(/* expected */ &'static str, /* found */ &'static str),
|
||||
/// The nth map key was expected, but none was found.
|
||||
ExpectedMapKey(usize),
|
||||
/// The nth map element was expected, but none was found.
|
||||
ExpectedMapElement(usize),
|
||||
/// An enum decoding was requested, but no variants were supplied
|
||||
NoEnumVariants,
|
||||
/// The unit type was being decoded, but a non-zero length string was found
|
||||
NilTooLong,
|
||||
/// There was an error with the syntactical structure of the TOML.
|
||||
SyntaxError,
|
||||
/// A custom error was generated when decoding.
|
||||
CustomError(String),
|
||||
/// The end of the TOML input was reached too soon
|
||||
EndOfStream,
|
||||
/// Produced by serde ...
|
||||
InvalidType(&'static str),
|
||||
}
|
||||
|
||||
/// Decodes a TOML value into a decodable type.
|
||||
///
|
||||
/// This function will consume the given TOML value and attempt to decode it
|
||||
/// into the type specified. If decoding fails, `None` will be returned. If a
|
||||
/// finer-grained error is desired, then it is recommended to use `Decodable`
|
||||
/// directly.
|
||||
#[cfg(feature = "rustc-serialize")]
|
||||
pub fn decode<T: ::rustc_serialize::Decodable>(toml: Value) -> Option<T> {
|
||||
::rustc_serialize::Decodable::decode(&mut Decoder::new(toml)).ok()
|
||||
}
|
||||
|
||||
/// Decodes a TOML value into a decodable type.
|
||||
///
|
||||
/// This function will consume the given TOML value and attempt to decode it
|
||||
/// into the type specified. If decoding fails, `None` will be returned. If a
|
||||
/// finer-grained error is desired, then it is recommended to use `Decodable`
|
||||
/// directly.
|
||||
#[cfg(all(not(feature = "rustc-serialize"), feature = "serde"))]
|
||||
pub fn decode<T: ::serde::Deserialize>(toml: Value) -> Option<T> {
|
||||
::serde::Deserialize::deserialize(&mut Decoder::new(toml)).ok()
|
||||
}
|
||||
|
||||
/// Decodes a string into a toml-encoded value.
|
||||
///
|
||||
/// This function will parse the given string into a TOML value, and then parse
|
||||
/// the TOML value into the desired type. If any error occurs, `None` is
|
||||
/// returned.
|
||||
///
|
||||
/// If more fine-grained errors are desired, these steps should be driven
|
||||
/// manually.
|
||||
#[cfg(feature = "rustc-serialize")]
|
||||
pub fn decode_str<T: ::rustc_serialize::Decodable>(s: &str) -> Option<T> {
|
||||
::Parser::new(s).parse().and_then(|t| decode(Value::Table(t)))
|
||||
}
|
||||
|
||||
/// Decodes a string into a toml-encoded value.
|
||||
///
|
||||
/// This function will parse the given string into a TOML value, and then parse
|
||||
/// the TOML value into the desired type. If any error occurs, `None` is
|
||||
/// returned.
|
||||
///
|
||||
/// If more fine-grained errors are desired, these steps should be driven
|
||||
/// manually.
|
||||
#[cfg(all(not(feature = "rustc-serialize"), feature = "serde"))]
|
||||
pub fn decode_str<T: ::serde::Deserialize>(s: &str) -> Option<T> {
|
||||
::Parser::new(s).parse().and_then(|t| decode(Value::Table(t)))
|
||||
}
|
||||
|
||||
impl Decoder {
|
||||
/// Creates a new decoder, consuming the TOML value to decode.
|
||||
///
|
||||
/// This decoder can be passed to the `Decodable` methods or driven
|
||||
/// manually.
|
||||
pub fn new(toml: Value) -> Decoder {
|
||||
Decoder::new_empty(Some(toml), None)
|
||||
}
|
||||
|
||||
fn sub_decoder(&self, toml: Option<Value>, field: &str) -> Decoder {
|
||||
let cur_field = if field.is_empty() {
|
||||
self.cur_field.clone()
|
||||
} else {
|
||||
match self.cur_field {
|
||||
None => Some(field.to_string()),
|
||||
Some(ref s) => Some(format!("{}.{}", s, field))
|
||||
}
|
||||
};
|
||||
Decoder::new_empty(toml, cur_field)
|
||||
}
|
||||
|
||||
fn new_empty(toml: Option<Value>, cur_field: Option<String>) -> Decoder {
|
||||
Decoder {
|
||||
toml: toml,
|
||||
cur_field: cur_field,
|
||||
leftover_map: BTreeMap::new(),
|
||||
cur_map: BTreeMap::new().into_iter().peekable(),
|
||||
}
|
||||
}
|
||||
|
||||
fn err(&self, kind: DecodeErrorKind) -> DecodeError {
|
||||
DecodeError {
|
||||
field: self.cur_field.clone(),
|
||||
kind: kind,
|
||||
}
|
||||
}
|
||||
|
||||
fn mismatch(&self, expected: &'static str,
|
||||
found: &Option<Value>) -> DecodeError{
|
||||
match *found {
|
||||
Some(ref val) => self.err(ExpectedType(expected, val.type_str())),
|
||||
None => self.err(ExpectedField(Some(expected))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DecodeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
try!(match self.kind {
|
||||
ApplicationError(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
ExpectedField(expected_type) => {
|
||||
match expected_type {
|
||||
Some("table") => write!(f, "expected a section"),
|
||||
Some(e) => write!(f, "expected a value of type `{}`", e),
|
||||
None => write!(f, "expected a value"),
|
||||
}
|
||||
}
|
||||
UnknownField => write!(f, "unknown field"),
|
||||
ExpectedType(expected, found) => {
|
||||
fn humanize(s: &str) -> String {
|
||||
if s == "section" {
|
||||
"a section".to_string()
|
||||
} else {
|
||||
format!("a value of type `{}`", s)
|
||||
}
|
||||
}
|
||||
write!(f, "expected {}, but found {}",
|
||||
humanize(expected),
|
||||
humanize(found))
|
||||
}
|
||||
ExpectedMapKey(idx) => {
|
||||
write!(f, "expected at least {} keys", idx + 1)
|
||||
}
|
||||
ExpectedMapElement(idx) => {
|
||||
write!(f, "expected at least {} elements", idx + 1)
|
||||
}
|
||||
NoEnumVariants => {
|
||||
write!(f, "expected an enum variant to decode to")
|
||||
}
|
||||
NilTooLong => {
|
||||
write!(f, "expected 0-length string")
|
||||
}
|
||||
SyntaxError => {
|
||||
write!(f, "syntax error")
|
||||
}
|
||||
EndOfStream => {
|
||||
write!(f, "end of stream")
|
||||
}
|
||||
InvalidType(s) => {
|
||||
write!(f, "invalid type: {}", s)
|
||||
}
|
||||
CustomError(ref s) => {
|
||||
write!(f, "custom error: {}", s)
|
||||
}
|
||||
});
|
||||
match self.field {
|
||||
Some(ref s) => {
|
||||
write!(f, " for the key `{}`", s)
|
||||
}
|
||||
None => Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for DecodeError {
|
||||
fn description(&self) -> &str {
|
||||
match self.kind {
|
||||
ApplicationError(ref s) => &**s,
|
||||
ExpectedField(..) => "expected a field",
|
||||
UnknownField => "found an unknown field",
|
||||
ExpectedType(..) => "expected a type",
|
||||
ExpectedMapKey(..) => "expected a map key",
|
||||
ExpectedMapElement(..) => "expected a map element",
|
||||
NoEnumVariants => "no enum variants to decode to",
|
||||
NilTooLong => "nonzero length string representing nil",
|
||||
SyntaxError => "syntax error",
|
||||
EndOfStream => "end of stream",
|
||||
InvalidType(..) => "invalid type",
|
||||
CustomError(..) => "custom error",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,371 +0,0 @@
|
|||
use rustc_serialize;
|
||||
use std::mem;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::{Decoder, DecodeError};
|
||||
use super::DecodeErrorKind::*;
|
||||
use Value;
|
||||
|
||||
impl rustc_serialize::Decoder for Decoder {
|
||||
type Error = DecodeError;
|
||||
fn read_nil(&mut self) -> Result<(), DecodeError> {
|
||||
match self.toml {
|
||||
Some(Value::String(ref s)) if s.is_empty() => {}
|
||||
Some(Value::String(..)) => return Err(self.err(NilTooLong)),
|
||||
ref found => return Err(self.mismatch("string", found)),
|
||||
}
|
||||
self.toml.take();
|
||||
Ok(())
|
||||
}
|
||||
fn read_usize(&mut self) -> Result<usize, DecodeError> {
|
||||
self.read_i64().map(|i| i as usize)
|
||||
}
|
||||
fn read_u64(&mut self) -> Result<u64, DecodeError> {
|
||||
self.read_i64().map(|i| i as u64)
|
||||
}
|
||||
fn read_u32(&mut self) -> Result<u32, DecodeError> {
|
||||
self.read_i64().map(|i| i as u32)
|
||||
}
|
||||
fn read_u16(&mut self) -> Result<u16, DecodeError> {
|
||||
self.read_i64().map(|i| i as u16)
|
||||
}
|
||||
fn read_u8(&mut self) -> Result<u8, DecodeError> {
|
||||
self.read_i64().map(|i| i as u8)
|
||||
}
|
||||
fn read_isize(&mut self) -> Result<isize, DecodeError> {
|
||||
self.read_i64().map(|i| i as isize)
|
||||
}
|
||||
fn read_i64(&mut self) -> Result<i64, DecodeError> {
|
||||
match self.toml {
|
||||
Some(Value::Integer(i)) => { self.toml.take(); Ok(i) }
|
||||
ref found => Err(self.mismatch("integer", found)),
|
||||
}
|
||||
}
|
||||
fn read_i32(&mut self) -> Result<i32, DecodeError> {
|
||||
self.read_i64().map(|i| i as i32)
|
||||
}
|
||||
fn read_i16(&mut self) -> Result<i16, DecodeError> {
|
||||
self.read_i64().map(|i| i as i16)
|
||||
}
|
||||
fn read_i8(&mut self) -> Result<i8, DecodeError> {
|
||||
self.read_i64().map(|i| i as i8)
|
||||
}
|
||||
fn read_bool(&mut self) -> Result<bool, DecodeError> {
|
||||
match self.toml {
|
||||
Some(Value::Boolean(b)) => { self.toml.take(); Ok(b) }
|
||||
ref found => Err(self.mismatch("bool", found)),
|
||||
}
|
||||
}
|
||||
fn read_f64(&mut self) -> Result<f64, DecodeError> {
|
||||
match self.toml {
|
||||
Some(Value::Float(f)) => { self.toml.take(); Ok(f) },
|
||||
ref found => Err(self.mismatch("float", found)),
|
||||
}
|
||||
}
|
||||
fn read_f32(&mut self) -> Result<f32, DecodeError> {
|
||||
self.read_f64().map(|f| f as f32)
|
||||
}
|
||||
fn read_char(&mut self) -> Result<char, DecodeError> {
|
||||
let ch = match self.toml {
|
||||
Some(Value::String(ref s)) if s.chars().count() == 1 =>
|
||||
s.chars().next().unwrap(),
|
||||
ref found => return Err(self.mismatch("string", found)),
|
||||
};
|
||||
self.toml.take();
|
||||
Ok(ch)
|
||||
}
|
||||
fn read_str(&mut self) -> Result<String, DecodeError> {
|
||||
match self.toml.take() {
|
||||
Some(Value::String(s)) => Ok(s),
|
||||
found => {
|
||||
let err = Err(self.mismatch("string", &found));
|
||||
self.toml = found;
|
||||
err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compound types:
|
||||
fn read_enum<T, F>(&mut self, _name: &str, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
|
||||
fn read_enum_variant<T, F>(&mut self, names: &[&str], mut f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnMut(&mut Decoder, usize) -> Result<T, DecodeError>
|
||||
{
|
||||
// When decoding enums, this crate takes the strategy of trying to
|
||||
// decode the current TOML as all of the possible variants, returning
|
||||
// success on the first one that succeeds.
|
||||
//
|
||||
// Note that fidelity of the errors returned here is a little nebulous,
|
||||
// but we try to return the error that had the relevant field as the
|
||||
// longest field. This way we hopefully match an error against what was
|
||||
// most likely being written down without losing too much info.
|
||||
let mut first_error = None::<DecodeError>;
|
||||
for i in 0..names.len() {
|
||||
let mut d = self.sub_decoder(self.toml.clone(), "");
|
||||
match f(&mut d, i) {
|
||||
Ok(t) => {
|
||||
self.toml = d.toml;
|
||||
return Ok(t)
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(ref first) = first_error {
|
||||
let my_len = e.field.as_ref().map(|s| s.len());
|
||||
let first_len = first.field.as_ref().map(|s| s.len());
|
||||
if my_len <= first_len {
|
||||
continue
|
||||
}
|
||||
}
|
||||
first_error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(first_error.unwrap_or_else(|| self.err(NoEnumVariants)))
|
||||
}
|
||||
fn read_enum_variant_arg<T, F>(&mut self, _a_idx: usize, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
|
||||
fn read_enum_struct_variant<T, F>(&mut self, _names: &[&str], _f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnMut(&mut Decoder, usize) -> Result<T, DecodeError>
|
||||
{
|
||||
panic!()
|
||||
}
|
||||
fn read_enum_struct_variant_field<T, F>(&mut self,
|
||||
_f_name: &str,
|
||||
_f_idx: usize,
|
||||
_f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn read_struct<T, F>(&mut self, _s_name: &str, _len: usize, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
match self.toml {
|
||||
Some(Value::Table(..)) => {
|
||||
let ret = try!(f(self));
|
||||
match self.toml {
|
||||
Some(Value::Table(ref t)) if t.is_empty() => {}
|
||||
_ => return Ok(ret)
|
||||
}
|
||||
self.toml.take();
|
||||
Ok(ret)
|
||||
}
|
||||
ref found => Err(self.mismatch("table", found)),
|
||||
}
|
||||
}
|
||||
fn read_struct_field<T, F>(&mut self, f_name: &str, _f_idx: usize, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
let field = f_name.to_string();
|
||||
let toml = match self.toml {
|
||||
Some(Value::Table(ref mut table)) => {
|
||||
table.remove(&field)
|
||||
.or_else(|| table.remove(&f_name.replace("_", "-")))
|
||||
},
|
||||
ref found => return Err(self.mismatch("table", found)),
|
||||
};
|
||||
let mut d = self.sub_decoder(toml, f_name);
|
||||
let ret = try!(f(&mut d));
|
||||
if let Some(value) = d.toml {
|
||||
if let Some(Value::Table(ref mut table)) = self.toml {
|
||||
table.insert(field, value);
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn read_tuple<T, F>(&mut self, tuple_len: usize, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
self.read_seq(move |d, len| {
|
||||
assert!(len == tuple_len,
|
||||
"expected tuple of length `{}`, found tuple \
|
||||
of length `{}`", tuple_len, len);
|
||||
f(d)
|
||||
})
|
||||
}
|
||||
fn read_tuple_arg<T, F>(&mut self, a_idx: usize, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
self.read_seq_elt(a_idx, f)
|
||||
}
|
||||
|
||||
fn read_tuple_struct<T, F>(&mut self, _s_name: &str, _len: usize, _f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
panic!()
|
||||
}
|
||||
fn read_tuple_struct_arg<T, F>(&mut self, _a_idx: usize, _f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
panic!()
|
||||
}
|
||||
|
||||
// Specialized types:
|
||||
fn read_option<T, F>(&mut self, mut f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnMut(&mut Decoder, bool) -> Result<T, DecodeError>
|
||||
{
|
||||
match self.toml {
|
||||
Some(..) => f(self, true),
|
||||
None => f(self, false),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_seq<T, F>(&mut self, f: F) -> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder, usize) -> Result<T, DecodeError>
|
||||
{
|
||||
let len = match self.toml {
|
||||
Some(Value::Array(ref arr)) => arr.len(),
|
||||
None => 0,
|
||||
ref found => return Err(self.mismatch("array", found)),
|
||||
};
|
||||
let ret = try!(f(self, len));
|
||||
match self.toml {
|
||||
Some(Value::Array(ref mut arr)) => {
|
||||
arr.retain(|slot| slot.as_integer() != Some(0));
|
||||
if !arr.is_empty() { return Ok(ret) }
|
||||
}
|
||||
_ => return Ok(ret)
|
||||
}
|
||||
self.toml.take();
|
||||
Ok(ret)
|
||||
}
|
||||
fn read_seq_elt<T, F>(&mut self, idx: usize, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
let toml = match self.toml {
|
||||
Some(Value::Array(ref mut arr)) => {
|
||||
mem::replace(&mut arr[idx], Value::Integer(0))
|
||||
}
|
||||
ref found => return Err(self.mismatch("array", found)),
|
||||
};
|
||||
let mut d = self.sub_decoder(Some(toml), "");
|
||||
let ret = try!(f(&mut d));
|
||||
if let Some(toml) = d.toml {
|
||||
if let Some(Value::Array(ref mut arr)) = self.toml {
|
||||
arr[idx] = toml;
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn read_map<T, F>(&mut self, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder, usize) -> Result<T, DecodeError>
|
||||
{
|
||||
let map = match self.toml.take() {
|
||||
Some(Value::Table(table)) => table,
|
||||
found => {
|
||||
self.toml = found;
|
||||
return Err(self.mismatch("table", &self.toml))
|
||||
}
|
||||
};
|
||||
let amt = map.len();
|
||||
let prev_iter = mem::replace(&mut self.cur_map,
|
||||
map.into_iter().peekable());
|
||||
let prev_map = mem::replace(&mut self.leftover_map, BTreeMap::new());
|
||||
let ret = try!(f(self, amt));
|
||||
let leftover = mem::replace(&mut self.leftover_map, prev_map);
|
||||
self.cur_map = prev_iter;
|
||||
if !leftover.is_empty() {
|
||||
self.toml = Some(Value::Table(leftover));
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
fn read_map_elt_key<T, F>(&mut self, idx: usize, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
let key = match self.cur_map.peek().map(|p| p.0.clone()) {
|
||||
Some(k) => k,
|
||||
None => return Err(self.err(ExpectedMapKey(idx))),
|
||||
};
|
||||
let val = Value::String(key.clone());
|
||||
f(&mut self.sub_decoder(Some(val), &key))
|
||||
}
|
||||
fn read_map_elt_val<T, F>(&mut self, idx: usize, f: F)
|
||||
-> Result<T, DecodeError>
|
||||
where F: FnOnce(&mut Decoder) -> Result<T, DecodeError>
|
||||
{
|
||||
match self.cur_map.next() {
|
||||
Some((key, value)) => {
|
||||
let mut d = self.sub_decoder(Some(value), &key);
|
||||
let ret = f(&mut d);
|
||||
if let Some(toml) = d.toml.take() {
|
||||
self.leftover_map.insert(key, toml);
|
||||
}
|
||||
ret
|
||||
}
|
||||
None => Err(self.err(ExpectedMapElement(idx))),
|
||||
}
|
||||
}
|
||||
|
||||
fn error(&mut self, err: &str) -> DecodeError {
|
||||
DecodeError {
|
||||
field: self.cur_field.clone(),
|
||||
kind: ApplicationError(err.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustc_serialize::Decodable;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use {Parser, Decoder, Value};
|
||||
|
||||
#[test]
|
||||
fn bad_enum_chooses_longest_error() {
|
||||
#[derive(RustcDecodable)]
|
||||
#[allow(dead_code)]
|
||||
struct Foo {
|
||||
wut: HashMap<String, Bar>,
|
||||
}
|
||||
|
||||
#[derive(RustcDecodable)]
|
||||
enum Bar {
|
||||
Simple(String),
|
||||
Detailed(Baz),
|
||||
}
|
||||
|
||||
#[derive(RustcDecodable, Debug)]
|
||||
struct Baz {
|
||||
features: Vec<String>,
|
||||
}
|
||||
|
||||
let s = r#"
|
||||
[wut]
|
||||
a = { features = "" }
|
||||
"#;
|
||||
let v = Parser::new(s).parse().unwrap();
|
||||
let mut d = Decoder::new(Value::Table(v));
|
||||
let err = match Foo::decode(&mut d) {
|
||||
Ok(_) => panic!("expected error"),
|
||||
Err(e) => e,
|
||||
};
|
||||
assert_eq!(err.field.as_ref().unwrap(), "wut.a.features");
|
||||
|
||||
}
|
||||
}
|
|
@ -1,773 +0,0 @@
|
|||
use serde::de;
|
||||
use Value;
|
||||
use super::{Decoder, DecodeError, DecodeErrorKind};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
macro_rules! forward_to_deserialize {
|
||||
($(
|
||||
$name:ident ( $( $arg:ident : $ty:ty ),* );
|
||||
)*) => {
|
||||
$(
|
||||
forward_to_deserialize!{
|
||||
func: $name ( $( $arg: $ty ),* );
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
(func: deserialize_enum ( $( $arg:ident : $ty:ty ),* );) => {
|
||||
fn deserialize_enum<V>(
|
||||
&mut self,
|
||||
$(_: $ty,)*
|
||||
_visitor: V,
|
||||
) -> ::std::result::Result<V::Value, Self::Error>
|
||||
where V: ::serde::de::EnumVisitor
|
||||
{
|
||||
Err(::serde::de::Error::invalid_type(::serde::de::Type::Enum))
|
||||
}
|
||||
};
|
||||
|
||||
(func: $name:ident ( $( $arg:ident : $ty:ty ),* );) => {
|
||||
#[inline]
|
||||
fn $name<V>(
|
||||
&mut self,
|
||||
$(_: $ty,)*
|
||||
visitor: V,
|
||||
) -> ::std::result::Result<V::Value, Self::Error>
|
||||
where V: ::serde::de::Visitor
|
||||
{
|
||||
self.deserialize(visitor)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl de::Deserializer for Decoder {
|
||||
type Error = DecodeError;
|
||||
|
||||
fn deserialize<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
match self.toml.take() {
|
||||
Some(Value::String(s)) => visitor.visit_string(s),
|
||||
Some(Value::Integer(i)) => visitor.visit_i64(i),
|
||||
Some(Value::Float(f)) => visitor.visit_f64(f),
|
||||
Some(Value::Boolean(b)) => visitor.visit_bool(b),
|
||||
Some(Value::Datetime(s)) => visitor.visit_string(s),
|
||||
Some(Value::Array(a)) => {
|
||||
let len = a.len();
|
||||
let iter = a.into_iter();
|
||||
visitor.visit_seq(SeqDeserializer::new(iter, len, &mut self.toml))
|
||||
}
|
||||
Some(Value::Table(t)) => {
|
||||
visitor.visit_map(MapVisitor {
|
||||
iter: t.into_iter(),
|
||||
de: self,
|
||||
key: None,
|
||||
value: None,
|
||||
})
|
||||
}
|
||||
None => Err(self.err(DecodeErrorKind::EndOfStream)),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_bool<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
match self.toml.take() {
|
||||
Some(Value::Boolean(b)) => visitor.visit_bool(b),
|
||||
ref found => Err(self.mismatch("bool", found)),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_i8<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_i16<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_i32<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_i64<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
match self.toml.take() {
|
||||
Some(Value::Integer(f)) => visitor.visit_i64(f),
|
||||
ref found => Err(self.mismatch("integer", found)),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_isize<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_u8<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_u16<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_u32<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_u64<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_usize<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_f32<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_f64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_f64<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
match self.toml.take() {
|
||||
Some(Value::Float(f)) => visitor.visit_f64(f),
|
||||
ref found => Err(self.mismatch("float", found)),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_str<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
match self.toml.take() {
|
||||
Some(Value::String(s)) => visitor.visit_string(s),
|
||||
ref found => Err(self.mismatch("string", found)),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_string<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
self.deserialize_str(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_char<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
match self.toml.take() {
|
||||
Some(Value::String(ref s)) if s.chars().count() == 1 => {
|
||||
visitor.visit_char(s.chars().next().unwrap())
|
||||
}
|
||||
ref found => return Err(self.mismatch("string", found)),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_option<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor
|
||||
{
|
||||
if self.toml.is_none() {
|
||||
visitor.visit_none()
|
||||
} else {
|
||||
visitor.visit_some(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_seq<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
if self.toml.is_none() {
|
||||
let iter = None::<i32>.into_iter();
|
||||
visitor.visit_seq(de::value::SeqDeserializer::new(iter, 0))
|
||||
} else {
|
||||
self.deserialize(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_map<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
match self.toml.take() {
|
||||
Some(Value::Table(t)) => {
|
||||
visitor.visit_map(MapVisitor {
|
||||
iter: t.into_iter(),
|
||||
de: self,
|
||||
key: None,
|
||||
value: None,
|
||||
})
|
||||
}
|
||||
ref found => Err(self.mismatch("table", found)),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(&mut self,
|
||||
_enum: &str,
|
||||
variants: &[&str],
|
||||
mut visitor: V) -> Result<V::Value, DecodeError>
|
||||
where V: de::EnumVisitor,
|
||||
{
|
||||
// When decoding enums, this crate takes the strategy of trying to
|
||||
// decode the current TOML as all of the possible variants, returning
|
||||
// success on the first one that succeeds.
|
||||
//
|
||||
// Note that fidelity of the errors returned here is a little nebulous,
|
||||
// but we try to return the error that had the relevant field as the
|
||||
// longest field. This way we hopefully match an error against what was
|
||||
// most likely being written down without losing too much info.
|
||||
let mut first_error = None::<DecodeError>;
|
||||
|
||||
for variant in 0..variants.len() {
|
||||
let mut de = VariantVisitor {
|
||||
de: self.sub_decoder(self.toml.clone(), ""),
|
||||
variant: variant,
|
||||
};
|
||||
|
||||
match visitor.visit(&mut de) {
|
||||
Ok(value) => {
|
||||
self.toml = de.de.toml;
|
||||
return Ok(value);
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(ref first) = first_error {
|
||||
let my_len = e.field.as_ref().map(|s| s.len());
|
||||
let first_len = first.field.as_ref().map(|s| s.len());
|
||||
if my_len <= first_len {
|
||||
continue
|
||||
}
|
||||
}
|
||||
first_error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(first_error.unwrap_or_else(|| self.err(DecodeErrorKind::NoEnumVariants)))
|
||||
}
|
||||
|
||||
// When #[derive(Deserialize)] encounters an unknown struct field it will
|
||||
// call this method (somehow), and we want to preserve all unknown struct
|
||||
// fields to return them upwards (to warn about unused keys), so we override
|
||||
// that here to not tamper with our own internal state.
|
||||
fn deserialize_ignored_any<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor
|
||||
{
|
||||
use serde::de::value::ValueDeserializer;
|
||||
let mut d = <() as ValueDeserializer<Self::Error>>::into_deserializer(());
|
||||
d.deserialize(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_bytes<V>(&mut self, visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_seq(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_seq_fixed_size<V>(&mut self, _len: usize, visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_seq(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(&mut self, _name: &'static str, visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_seq(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(&mut self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_seq(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(&mut self,
|
||||
_name: &'static str,
|
||||
_fields: &'static [&'static str],
|
||||
visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_map(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(&mut self,
|
||||
_len: usize,
|
||||
visitor: V)
|
||||
-> Result<V::Value, Self::Error>
|
||||
where V: de::Visitor
|
||||
{
|
||||
self.deserialize_seq(visitor)
|
||||
}
|
||||
|
||||
forward_to_deserialize!{
|
||||
deserialize_unit();
|
||||
deserialize_unit_struct(name: &'static str);
|
||||
deserialize_struct_field();
|
||||
}
|
||||
}
|
||||
|
||||
struct VariantVisitor {
|
||||
de: Decoder,
|
||||
variant: usize,
|
||||
}
|
||||
|
||||
impl de::VariantVisitor for VariantVisitor {
|
||||
type Error = DecodeError;
|
||||
|
||||
fn visit_variant<V>(&mut self) -> Result<V, DecodeError>
|
||||
where V: de::Deserialize
|
||||
{
|
||||
use serde::de::value::ValueDeserializer;
|
||||
|
||||
let mut de = self.variant.into_deserializer();
|
||||
|
||||
de::Deserialize::deserialize(&mut de)
|
||||
}
|
||||
|
||||
fn visit_unit(&mut self) -> Result<(), DecodeError> {
|
||||
de::Deserialize::deserialize(&mut self.de)
|
||||
}
|
||||
|
||||
fn visit_newtype<T>(&mut self) -> Result<T, DecodeError>
|
||||
where T: de::Deserialize,
|
||||
{
|
||||
de::Deserialize::deserialize(&mut self.de)
|
||||
}
|
||||
|
||||
fn visit_tuple<V>(&mut self,
|
||||
_len: usize,
|
||||
visitor: V) -> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
de::Deserializer::deserialize(&mut self.de, visitor)
|
||||
}
|
||||
|
||||
fn visit_struct<V>(&mut self,
|
||||
_fields: &'static [&'static str],
|
||||
visitor: V) -> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
de::Deserializer::deserialize(&mut self.de, visitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct SeqDeserializer<'a, I> {
|
||||
iter: I,
|
||||
len: usize,
|
||||
toml: &'a mut Option<Value>,
|
||||
}
|
||||
|
||||
impl<'a, I> SeqDeserializer<'a, I> where I: Iterator<Item=Value> {
|
||||
fn new(iter: I, len: usize, toml: &'a mut Option<Value>) -> Self {
|
||||
SeqDeserializer {
|
||||
iter: iter,
|
||||
len: len,
|
||||
toml: toml,
|
||||
}
|
||||
}
|
||||
|
||||
fn put_value_back(&mut self, v: Value) {
|
||||
*self.toml = self.toml.take().or(Some(Value::Array(Vec::new())));
|
||||
match self.toml.as_mut().unwrap() {
|
||||
&mut Value::Array(ref mut a) => {
|
||||
a.push(v);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I> de::Deserializer for SeqDeserializer<'a, I>
|
||||
where I: Iterator<Item=Value>,
|
||||
{
|
||||
type Error = DecodeError;
|
||||
|
||||
fn deserialize<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
visitor.visit_seq(self)
|
||||
}
|
||||
|
||||
forward_to_deserialize!{
|
||||
deserialize_bool();
|
||||
deserialize_usize();
|
||||
deserialize_u8();
|
||||
deserialize_u16();
|
||||
deserialize_u32();
|
||||
deserialize_u64();
|
||||
deserialize_isize();
|
||||
deserialize_i8();
|
||||
deserialize_i16();
|
||||
deserialize_i32();
|
||||
deserialize_i64();
|
||||
deserialize_f32();
|
||||
deserialize_f64();
|
||||
deserialize_char();
|
||||
deserialize_str();
|
||||
deserialize_string();
|
||||
deserialize_unit();
|
||||
deserialize_option();
|
||||
deserialize_seq();
|
||||
deserialize_seq_fixed_size(len: usize);
|
||||
deserialize_bytes();
|
||||
deserialize_map();
|
||||
deserialize_unit_struct(name: &'static str);
|
||||
deserialize_newtype_struct(name: &'static str);
|
||||
deserialize_tuple_struct(name: &'static str, len: usize);
|
||||
deserialize_struct(name: &'static str, fields: &'static [&'static str]);
|
||||
deserialize_struct_field();
|
||||
deserialize_tuple(len: usize);
|
||||
deserialize_enum(name: &'static str, variants: &'static [&'static str]);
|
||||
deserialize_ignored_any();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I> de::SeqVisitor for SeqDeserializer<'a, I>
|
||||
where I: Iterator<Item=Value>
|
||||
{
|
||||
type Error = DecodeError;
|
||||
|
||||
fn visit<V>(&mut self) -> Result<Option<V>, DecodeError>
|
||||
where V: de::Deserialize
|
||||
{
|
||||
match self.iter.next() {
|
||||
Some(value) => {
|
||||
self.len -= 1;
|
||||
let mut de = Decoder::new(value);
|
||||
let v = try!(de::Deserialize::deserialize(&mut de));
|
||||
if let Some(t) = de.toml {
|
||||
self.put_value_back(t);
|
||||
}
|
||||
Ok(Some(v))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn end(&mut self) -> Result<(), DecodeError> {
|
||||
if self.len == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(de::Error::end_of_stream())
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.len, Some(self.len))
|
||||
}
|
||||
}
|
||||
|
||||
impl de::Error for DecodeError {
|
||||
fn custom<T: Into<String>>(msg: T) -> DecodeError {
|
||||
DecodeError {
|
||||
field: None,
|
||||
kind: DecodeErrorKind::CustomError(msg.into()),
|
||||
}
|
||||
}
|
||||
fn end_of_stream() -> DecodeError {
|
||||
DecodeError { field: None, kind: DecodeErrorKind::EndOfStream }
|
||||
}
|
||||
fn missing_field(name: &'static str) -> DecodeError {
|
||||
DecodeError {
|
||||
field: Some(name.to_string()),
|
||||
kind: DecodeErrorKind::ExpectedField(None),
|
||||
}
|
||||
}
|
||||
fn unknown_field(name: &str) -> DecodeError {
|
||||
DecodeError {
|
||||
field: Some(name.to_string()),
|
||||
kind: DecodeErrorKind::UnknownField,
|
||||
}
|
||||
}
|
||||
fn invalid_type(ty: de::Type) -> Self {
|
||||
DecodeError {
|
||||
field: None,
|
||||
kind: DecodeErrorKind::InvalidType(match ty {
|
||||
de::Type::Bool => "bool",
|
||||
de::Type::Usize |
|
||||
de::Type::U8 |
|
||||
de::Type::U16 |
|
||||
de::Type::U32 |
|
||||
de::Type::U64 |
|
||||
de::Type::Isize |
|
||||
de::Type::I8 |
|
||||
de::Type::I16 |
|
||||
de::Type::I32 |
|
||||
de::Type::I64 => "integer",
|
||||
de::Type::F32 |
|
||||
de::Type::F64 => "float",
|
||||
de::Type::Char |
|
||||
de::Type::Str |
|
||||
de::Type::String => "string",
|
||||
de::Type::Seq => "array",
|
||||
de::Type::Struct |
|
||||
de::Type::Map => "table",
|
||||
de::Type::Unit => "Unit",
|
||||
de::Type::Option => "Option",
|
||||
de::Type::UnitStruct => "UnitStruct",
|
||||
de::Type::NewtypeStruct => "NewtypeStruct",
|
||||
de::Type::TupleStruct => "TupleStruct",
|
||||
de::Type::FieldName => "FieldName",
|
||||
de::Type::Tuple => "Tuple",
|
||||
de::Type::Enum => "Enum",
|
||||
de::Type::VariantName => "VariantName",
|
||||
de::Type::StructVariant => "StructVariant",
|
||||
de::Type::TupleVariant => "TupleVariant",
|
||||
de::Type::UnitVariant => "UnitVariant",
|
||||
de::Type::Bytes => "Bytes",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MapVisitor<'a, I> {
|
||||
iter: I,
|
||||
de: &'a mut Decoder,
|
||||
key: Option<String>,
|
||||
value: Option<Value>,
|
||||
}
|
||||
|
||||
impl<'a, I> MapVisitor<'a, I> {
|
||||
fn put_value_back(&mut self, v: Value) {
|
||||
self.de.toml = self.de.toml.take().or_else(|| {
|
||||
Some(Value::Table(BTreeMap::new()))
|
||||
});
|
||||
|
||||
match self.de.toml.as_mut().unwrap() {
|
||||
&mut Value::Table(ref mut t) => {
|
||||
t.insert(self.key.take().unwrap(), v);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I> de::MapVisitor for MapVisitor<'a, I>
|
||||
where I: Iterator<Item=(String, Value)>
|
||||
{
|
||||
type Error = DecodeError;
|
||||
|
||||
fn visit_key<K>(&mut self) -> Result<Option<K>, DecodeError>
|
||||
where K: de::Deserialize
|
||||
{
|
||||
while let Some((k, v)) = self.iter.next() {
|
||||
let mut dec = self.de.sub_decoder(Some(Value::String(k.clone())), &k);
|
||||
self.key = Some(k);
|
||||
|
||||
match de::Deserialize::deserialize(&mut dec) {
|
||||
Ok(val) => {
|
||||
self.value = Some(v);
|
||||
return Ok(Some(val))
|
||||
}
|
||||
|
||||
// If this was an unknown field, then we put the toml value
|
||||
// back into the map and keep going.
|
||||
Err(DecodeError {kind: DecodeErrorKind::UnknownField, ..}) => {
|
||||
self.put_value_back(v);
|
||||
}
|
||||
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_value<V>(&mut self) -> Result<V, DecodeError>
|
||||
where V: de::Deserialize
|
||||
{
|
||||
match self.value.take() {
|
||||
Some(t) => {
|
||||
let mut dec = {
|
||||
// Borrowing the key here because Rust doesn't have
|
||||
// non-lexical borrows yet.
|
||||
let key = match self.key {
|
||||
Some(ref key) => &**key,
|
||||
None => ""
|
||||
};
|
||||
|
||||
self.de.sub_decoder(Some(t), key)
|
||||
};
|
||||
let v = try!(de::Deserialize::deserialize(&mut dec));
|
||||
if let Some(t) = dec.toml {
|
||||
self.put_value_back(t);
|
||||
}
|
||||
Ok(v)
|
||||
},
|
||||
None => Err(de::Error::end_of_stream())
|
||||
}
|
||||
}
|
||||
|
||||
fn end(&mut self) -> Result<(), DecodeError> {
|
||||
if let Some(v) = self.value.take() {
|
||||
self.put_value_back(v);
|
||||
}
|
||||
while let Some((k, v)) = self.iter.next() {
|
||||
self.key = Some(k);
|
||||
self.put_value_back(v);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn missing_field<V>(&mut self, field_name: &'static str)
|
||||
-> Result<V, DecodeError> where V: de::Deserialize {
|
||||
// See if the type can deserialize from a unit.
|
||||
match de::Deserialize::deserialize(&mut UnitDeserializer) {
|
||||
Err(DecodeError {
|
||||
kind: DecodeErrorKind::InvalidType(..),
|
||||
field,
|
||||
}) => Err(DecodeError {
|
||||
field: field.or(Some(field_name.to_string())),
|
||||
kind: DecodeErrorKind::ExpectedField(None),
|
||||
}),
|
||||
v => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UnitDeserializer;
|
||||
|
||||
impl de::Deserializer for UnitDeserializer {
|
||||
type Error = DecodeError;
|
||||
|
||||
fn deserialize<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_option<V>(&mut self, mut visitor: V)
|
||||
-> Result<V::Value, DecodeError>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
visitor.visit_none()
|
||||
}
|
||||
|
||||
forward_to_deserialize!{
|
||||
deserialize_bool();
|
||||
deserialize_usize();
|
||||
deserialize_u8();
|
||||
deserialize_u16();
|
||||
deserialize_u32();
|
||||
deserialize_u64();
|
||||
deserialize_isize();
|
||||
deserialize_i8();
|
||||
deserialize_i16();
|
||||
deserialize_i32();
|
||||
deserialize_i64();
|
||||
deserialize_f32();
|
||||
deserialize_f64();
|
||||
deserialize_char();
|
||||
deserialize_str();
|
||||
deserialize_string();
|
||||
deserialize_unit();
|
||||
deserialize_seq();
|
||||
deserialize_seq_fixed_size(len: usize);
|
||||
deserialize_bytes();
|
||||
deserialize_map();
|
||||
deserialize_unit_struct(name: &'static str);
|
||||
deserialize_newtype_struct(name: &'static str);
|
||||
deserialize_tuple_struct(name: &'static str, len: usize);
|
||||
deserialize_struct(name: &'static str, fields: &'static [&'static str]);
|
||||
deserialize_struct_field();
|
||||
deserialize_tuple(len: usize);
|
||||
deserialize_enum(name: &'static str, variants: &'static [&'static str]);
|
||||
deserialize_ignored_any();
|
||||
}
|
||||
}
|
||||
|
||||
impl de::Deserialize for Value {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Value, D::Error>
|
||||
where D: de::Deserializer
|
||||
{
|
||||
struct ValueVisitor;
|
||||
|
||||
impl de::Visitor for ValueVisitor {
|
||||
type Value = Value;
|
||||
|
||||
fn visit_bool<E>(&mut self, value: bool) -> Result<Value, E> {
|
||||
Ok(Value::Boolean(value))
|
||||
}
|
||||
|
||||
fn visit_i64<E>(&mut self, value: i64) -> Result<Value, E> {
|
||||
Ok(Value::Integer(value))
|
||||
}
|
||||
|
||||
fn visit_f64<E>(&mut self, value: f64) -> Result<Value, E> {
|
||||
Ok(Value::Float(value))
|
||||
}
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Value, E> {
|
||||
Ok(Value::String(value.into()))
|
||||
}
|
||||
|
||||
fn visit_string<E>(&mut self, value: String) -> Result<Value, E> {
|
||||
Ok(Value::String(value))
|
||||
}
|
||||
|
||||
fn visit_seq<V>(&mut self, visitor: V) -> Result<Value, V::Error>
|
||||
where V: de::SeqVisitor
|
||||
{
|
||||
let values = try!(de::impls::VecVisitor::new().visit_seq(visitor));
|
||||
Ok(Value::Array(values))
|
||||
}
|
||||
|
||||
fn visit_map<V>(&mut self, visitor: V) -> Result<Value, V::Error>
|
||||
where V: de::MapVisitor
|
||||
{
|
||||
let mut v = de::impls::BTreeMapVisitor::new();
|
||||
let values = try!(v.visit_map(visitor));
|
||||
Ok(Value::Table(values))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize(ValueVisitor)
|
||||
}
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
|
||||
use {Value, Table};
|
||||
|
||||
#[cfg(feature = "rustc-serialize")] mod rustc_serialize;
|
||||
#[cfg(feature = "serde")] mod serde;
|
||||
|
||||
/// A structure to transform Rust values into TOML values.
|
||||
///
|
||||
/// This encoder implements the serialization `Encoder` interface, allowing
|
||||
/// `Encodable` rust types to be fed into the encoder. The output of this
|
||||
/// encoder is a TOML `Table` structure. The resulting TOML can be stringified
|
||||
/// if necessary.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// extern crate rustc_serialize;
|
||||
/// extern crate toml;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// use toml::{Encoder, Value};
|
||||
/// use rustc_serialize::Encodable;
|
||||
///
|
||||
/// #[derive(RustcEncodable)]
|
||||
/// struct MyStruct { foo: isize, bar: String }
|
||||
/// let my_struct = MyStruct { foo: 4, bar: "hello!".to_string() };
|
||||
///
|
||||
/// let mut e = Encoder::new();
|
||||
/// my_struct.encode(&mut e).unwrap();
|
||||
///
|
||||
/// assert_eq!(e.toml.get(&"foo".to_string()), Some(&Value::Integer(4)))
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Encoder {
|
||||
/// Output TOML that is emitted. The current version of this encoder forces
|
||||
/// the top-level representation of a structure to be a table.
|
||||
///
|
||||
/// This field can be used to extract the return value after feeding a value
|
||||
/// into this `Encoder`.
|
||||
pub toml: Table,
|
||||
state: State,
|
||||
}
|
||||
|
||||
/// Enumeration of errors which can occur while encoding a rust value into a
|
||||
/// TOML value.
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Indication that a key was needed when a value was emitted, but no key
|
||||
/// was previously emitted.
|
||||
NeedsKey,
|
||||
/// Indication that a key was emitted, but no value was emitted.
|
||||
NoValue,
|
||||
/// Indicates that a map key was attempted to be emitted at an invalid
|
||||
/// location.
|
||||
InvalidMapKeyLocation,
|
||||
/// Indicates that a type other than a string was attempted to be used as a
|
||||
/// map key type.
|
||||
InvalidMapKeyType,
|
||||
/// An error returned whenever a `NaN` value for a float is attempted to be
|
||||
/// encoded
|
||||
NanEncoded,
|
||||
/// An error returned whenever an infinity value for a float is attempted to
|
||||
/// be encoded
|
||||
InfinityEncoded,
|
||||
/// A custom error type was generated
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
/// Internal state of the encoder when encoding transitions
|
||||
#[derive(Debug)]
|
||||
pub struct EncoderState {
|
||||
inner: State,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum State {
|
||||
Start,
|
||||
NextKey(String),
|
||||
NextArray(Vec<Value>),
|
||||
NextMapKey,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> State { State::Start }
|
||||
}
|
||||
|
||||
impl Encoder {
|
||||
/// Constructs a new encoder which will emit to the given output stream.
|
||||
pub fn new() -> Encoder {
|
||||
Encoder { state: State::Start, toml: BTreeMap::new() }
|
||||
}
|
||||
|
||||
fn emit_value(&mut self, v: Value) -> Result<(), Error> {
|
||||
match v {
|
||||
Value::Float(f) => {
|
||||
if f.is_nan() {
|
||||
return Err(Error::NanEncoded)
|
||||
}
|
||||
if f.is_infinite() {
|
||||
return Err(Error::InfinityEncoded)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match mem::replace(&mut self.state, State::Start) {
|
||||
State::NextKey(key) => { self.toml.insert(key, v); Ok(()) }
|
||||
State::NextArray(mut vec) => {
|
||||
// TODO: validate types
|
||||
vec.push(v);
|
||||
self.state = State::NextArray(vec);
|
||||
Ok(())
|
||||
}
|
||||
State::NextMapKey => {
|
||||
match v {
|
||||
Value::String(s) => { self.state = State::NextKey(s); Ok(()) }
|
||||
_ => Err(Error::InvalidMapKeyType)
|
||||
}
|
||||
}
|
||||
_ => Err(Error::NeedsKey)
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_none(&mut self) -> Result<(), Error> {
|
||||
match mem::replace(&mut self.state, State::Start) {
|
||||
State::Start => unreachable!(),
|
||||
State::NextKey(_) => Ok(()),
|
||||
State::NextArray(..) => panic!("how to encode None in an array?"),
|
||||
State::NextMapKey => Err(Error::InvalidMapKeyLocation),
|
||||
}
|
||||
}
|
||||
|
||||
fn seq_begin(&mut self) -> Result<State, Error> {
|
||||
Ok(mem::replace(&mut self.state, State::NextArray(Vec::new())))
|
||||
}
|
||||
|
||||
fn seq_end(&mut self, old: State) -> Result<(), Error> {
|
||||
match mem::replace(&mut self.state, old) {
|
||||
State::NextArray(v) => self.emit_value(Value::Array(v)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn table_key<F>(&mut self, f: F) -> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
match mem::replace(&mut self.state, State::NextMapKey) {
|
||||
State::Start => {}
|
||||
_ => return Err(Error::InvalidMapKeyLocation),
|
||||
}
|
||||
try!(f(self));
|
||||
match self.state {
|
||||
State::NextKey(_) => Ok(()),
|
||||
_ => Err(Error::InvalidMapKeyLocation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes an encodable value into a TOML value.
|
||||
///
|
||||
/// This function expects the type given to represent a TOML table in some form.
|
||||
/// If encoding encounters an error, then this function will fail the task.
|
||||
#[cfg(feature = "rustc-serialize")]
|
||||
pub fn encode<T: ::rustc_serialize::Encodable>(t: &T) -> Value {
|
||||
let mut e = Encoder::new();
|
||||
t.encode(&mut e).unwrap();
|
||||
Value::Table(e.toml)
|
||||
}
|
||||
|
||||
/// Encodes an encodable value into a TOML value.
|
||||
///
|
||||
/// This function expects the type given to represent a TOML table in some form.
|
||||
/// If encoding encounters an error, then this function will fail the task.
|
||||
#[cfg(all(not(feature = "rustc-serialize"), feature = "serde"))]
|
||||
pub fn encode<T: ::serde::Serialize>(t: &T) -> Value {
|
||||
let mut e = Encoder::new();
|
||||
t.serialize(&mut e).unwrap();
|
||||
Value::Table(e.toml)
|
||||
}
|
||||
|
||||
/// Encodes an encodable value into a TOML string.
|
||||
///
|
||||
/// This function expects the type given to represent a TOML table in some form.
|
||||
/// If encoding encounters an error, then this function will fail the task.
|
||||
#[cfg(feature = "rustc-serialize")]
|
||||
pub fn encode_str<T: ::rustc_serialize::Encodable>(t: &T) -> String {
|
||||
encode(t).to_string()
|
||||
}
|
||||
|
||||
/// Encodes an encodable value into a TOML string.
|
||||
///
|
||||
/// This function expects the type given to represent a TOML table in some form.
|
||||
/// If encoding encounters an error, then this function will fail the task.
|
||||
#[cfg(all(not(feature = "rustc-serialize"), feature = "serde"))]
|
||||
pub fn encode_str<T: ::serde::Serialize>(t: &T) -> String {
|
||||
encode(t).to_string()
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::NeedsKey => write!(f, "need a key to encode"),
|
||||
Error::NoValue => write!(f, "no value to emit for a previous key"),
|
||||
Error::InvalidMapKeyLocation => write!(f, "a map cannot be emitted \
|
||||
at this location"),
|
||||
Error::InvalidMapKeyType => write!(f, "only strings can be used as \
|
||||
key types"),
|
||||
Error::NanEncoded => write!(f, "cannot encode NaN"),
|
||||
Error::InfinityEncoded => write!(f, "cannot encode infinity"),
|
||||
Error::Custom(ref s) => write!(f, "custom error: {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str { "TOML encoding error" }
|
||||
}
|
|
@ -1,748 +0,0 @@
|
|||
use std::mem;
|
||||
|
||||
use rustc_serialize;
|
||||
use Value;
|
||||
use super::{Encoder, Error, State};
|
||||
use super::Error::*;
|
||||
|
||||
impl Encoder {
|
||||
fn table<F>(&mut self, f: F) -> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
match mem::replace(&mut self.state, State::Start) {
|
||||
State::NextKey(key) => {
|
||||
let mut nested = Encoder::new();
|
||||
try!(f(&mut nested));
|
||||
self.toml.insert(key, Value::Table(nested.toml));
|
||||
Ok(())
|
||||
}
|
||||
State::NextArray(mut arr) => {
|
||||
let mut nested = Encoder::new();
|
||||
try!(f(&mut nested));
|
||||
arr.push(Value::Table(nested.toml));
|
||||
self.state = State::NextArray(arr);
|
||||
Ok(())
|
||||
}
|
||||
State::Start => f(self),
|
||||
State::NextMapKey => Err(Error::InvalidMapKeyLocation),
|
||||
}
|
||||
}
|
||||
|
||||
fn seq<F>(&mut self, f: F) -> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
let old = try!(self.seq_begin());
|
||||
try!(f(self));
|
||||
self.seq_end(old)
|
||||
}
|
||||
}
|
||||
|
||||
impl rustc_serialize::Encoder for Encoder {
|
||||
type Error = Error;
|
||||
|
||||
fn emit_nil(&mut self) -> Result<(), Error> { Ok(()) }
|
||||
fn emit_usize(&mut self, v: usize) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_u8(&mut self, v: u8) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_u16(&mut self, v: u16) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_u32(&mut self, v: u32) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_u64(&mut self, v: u64) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_isize(&mut self, v: isize) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_i8(&mut self, v: i8) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_i16(&mut self, v: i16) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_i32(&mut self, v: i32) -> Result<(), Error> {
|
||||
self.emit_i64(v as i64)
|
||||
}
|
||||
fn emit_i64(&mut self, v: i64) -> Result<(), Error> {
|
||||
self.emit_value(Value::Integer(v))
|
||||
}
|
||||
fn emit_bool(&mut self, v: bool) -> Result<(), Error> {
|
||||
self.emit_value(Value::Boolean(v))
|
||||
}
|
||||
fn emit_f32(&mut self, v: f32) -> Result<(), Error> { self.emit_f64(v as f64) }
|
||||
fn emit_f64(&mut self, v: f64) -> Result<(), Error> {
|
||||
self.emit_value(Value::Float(v))
|
||||
}
|
||||
fn emit_char(&mut self, v: char) -> Result<(), Error> {
|
||||
self.emit_str(&v.to_string())
|
||||
}
|
||||
fn emit_str(&mut self, v: &str) -> Result<(), Error> {
|
||||
self.emit_value(Value::String(v.to_string()))
|
||||
}
|
||||
fn emit_enum<F>(&mut self, _name: &str, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
fn emit_enum_variant<F>(&mut self, _v_name: &str, _v_id: usize,
|
||||
_len: usize, f: F) -> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
fn emit_enum_variant_arg<F>(&mut self, _a_idx: usize, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
fn emit_enum_struct_variant<F>(&mut self, _v_name: &str, _v_id: usize,
|
||||
_len: usize,
|
||||
_f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
panic!()
|
||||
}
|
||||
fn emit_enum_struct_variant_field<F>(&mut self,
|
||||
_f_name: &str,
|
||||
_f_idx: usize,
|
||||
_f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
panic!()
|
||||
}
|
||||
fn emit_struct<F>(&mut self, _name: &str, _len: usize, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
self.table(f)
|
||||
}
|
||||
fn emit_struct_field<F>(&mut self, f_name: &str, _f_idx: usize, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
let old = mem::replace(&mut self.state,
|
||||
State::NextKey(f_name.to_string()));
|
||||
try!(f(self));
|
||||
if self.state != State::Start {
|
||||
return Err(NoValue)
|
||||
}
|
||||
self.state = old;
|
||||
Ok(())
|
||||
}
|
||||
fn emit_tuple<F>(&mut self, len: usize, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
self.emit_seq(len, f)
|
||||
}
|
||||
fn emit_tuple_arg<F>(&mut self, idx: usize, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
self.emit_seq_elt(idx, f)
|
||||
}
|
||||
fn emit_tuple_struct<F>(&mut self, _name: &str, _len: usize, _f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
fn emit_tuple_struct_arg<F>(&mut self, _f_idx: usize, _f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
fn emit_option<F>(&mut self, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
fn emit_option_none(&mut self) -> Result<(), Error> {
|
||||
self.emit_none()
|
||||
}
|
||||
fn emit_option_some<F>(&mut self, f: F) -> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
fn emit_seq<F>(&mut self, _len: usize, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
self.seq(f)
|
||||
}
|
||||
fn emit_seq_elt<F>(&mut self, _idx: usize, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
fn emit_map<F>(&mut self, len: usize, f: F)
|
||||
-> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
self.emit_struct("foo", len, f)
|
||||
}
|
||||
fn emit_map_elt_key<F>(&mut self, _idx: usize, f: F) -> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
self.table_key(f)
|
||||
}
|
||||
fn emit_map_elt_val<F>(&mut self, _idx: usize, f: F) -> Result<(), Error>
|
||||
where F: FnOnce(&mut Encoder) -> Result<(), Error>
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl rustc_serialize::Encodable for Value {
|
||||
fn encode<E>(&self, e: &mut E) -> Result<(), E::Error>
|
||||
where E: rustc_serialize::Encoder
|
||||
{
|
||||
match *self {
|
||||
Value::String(ref s) => e.emit_str(s),
|
||||
Value::Integer(i) => e.emit_i64(i),
|
||||
Value::Float(f) => e.emit_f64(f),
|
||||
Value::Boolean(b) => e.emit_bool(b),
|
||||
Value::Datetime(ref s) => e.emit_str(s),
|
||||
Value::Array(ref a) => {
|
||||
e.emit_seq(a.len(), |e| {
|
||||
for item in a {
|
||||
try!(item.encode(e));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
Value::Table(ref t) => {
|
||||
e.emit_map(t.len(), |e| {
|
||||
for (i, (key, value)) in t.iter().enumerate() {
|
||||
try!(e.emit_map_elt_key(i, |e| e.emit_str(key)));
|
||||
try!(e.emit_map_elt_val(i, |e| value.encode(e)));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use rustc_serialize::{self, Encodable, Decodable};
|
||||
|
||||
use {Encoder, Decoder, DecodeError};
|
||||
use Value;
|
||||
use Value::{Table, Integer, Array, Float};
|
||||
|
||||
macro_rules! encode( ($t:expr) => ({
|
||||
let mut e = Encoder::new();
|
||||
$t.encode(&mut e).unwrap();
|
||||
e.toml
|
||||
}) );
|
||||
|
||||
macro_rules! decode( ($t:expr) => ({
|
||||
let mut d = Decoder::new($t);
|
||||
Decodable::decode(&mut d).unwrap()
|
||||
}) );
|
||||
|
||||
macro_rules! map( ($($k:ident, $v:expr),*) => ({
|
||||
let mut _m = BTreeMap::new();
|
||||
$(_m.insert(stringify!($k).to_string(), $v);)*
|
||||
_m
|
||||
}) );
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
#[derive(RustcEncodable, RustcDecodable, 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(RustcEncodable, RustcDecodable, 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(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: isize, b: Bar }
|
||||
#[derive(RustcEncodable, RustcDecodable, 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 Decodable for Range10 {
|
||||
fn decode<D: rustc_serialize::Decoder>(d: &mut D) -> Result<Range10, D::Error> {
|
||||
let x: usize = try!(Decodable::decode(d));
|
||||
if x > 10 {
|
||||
Err(d.error("Value out of range!"))
|
||||
} 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)), Decodable::decode(&mut d_good));
|
||||
|
||||
let err1: Result<Range10, _> = Decodable::decode(&mut d_bad1);
|
||||
assert!(err1.is_err());
|
||||
let err2: Result<Range10, _> = Decodable::decode(&mut d_bad2);
|
||||
assert!(err2.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Vec<isize> }
|
||||
|
||||
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(RustcEncodable, RustcDecodable, 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(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo {
|
||||
a: Option<Box<Foo>>,
|
||||
b: Bar,
|
||||
}
|
||||
#[derive(RustcEncodable, RustcDecodable, 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(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo {
|
||||
map: BTreeMap<String, isize>,
|
||||
set: HashSet<char>,
|
||||
}
|
||||
|
||||
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(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo(isize, String, f64);
|
||||
|
||||
let v = Foo(1, "foo".to_string(), 4.5);
|
||||
assert_eq!(
|
||||
encode!(v),
|
||||
map! {
|
||||
_field0, Integer(1),
|
||||
_field1, Value::String("foo".to_string()),
|
||||
_field2, Float(4.5)
|
||||
}
|
||||
);
|
||||
assert_eq!(v, decode!(Table(encode!(v))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_array() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Vec<Bar>, }
|
||||
#[derive(RustcEncodable, RustcDecodable, 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(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { bar: isize }
|
||||
|
||||
let mut d = Decoder::new(Table(map! {
|
||||
bar, Float(1.0)
|
||||
}));
|
||||
let a: Result<Foo, DecodeError> = Decodable::decode(&mut d);
|
||||
match a {
|
||||
Ok(..) => panic!("should not have decoded"),
|
||||
Err(e) => {
|
||||
assert_eq!(e.to_string(),
|
||||
"expected a value of type `integer`, but \
|
||||
found a value of type `float` for the key `bar`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_errors() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { bar: isize }
|
||||
|
||||
let mut d = Decoder::new(Table(map! {
|
||||
}));
|
||||
let a: Result<Foo, DecodeError> = Decodable::decode(&mut d);
|
||||
match a {
|
||||
Ok(..) => panic!("should not have decoded"),
|
||||
Err(e) => {
|
||||
assert_eq!(e.to_string(),
|
||||
"expected a value of type `integer` for the key `bar`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_enum() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: E }
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
enum E {
|
||||
Bar(isize),
|
||||
Baz(f64),
|
||||
Last(Foo2),
|
||||
}
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo2 {
|
||||
test: String,
|
||||
}
|
||||
|
||||
let v = Foo { a: E::Bar(10) };
|
||||
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(RustcEncodable, RustcDecodable, 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, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
assert_eq!(d.toml, Some(Table(map! {
|
||||
b, Integer(5)
|
||||
})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unused_fields2() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Bar }
|
||||
#[derive(RustcEncodable, RustcDecodable, 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, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
assert_eq!(d.toml, Some(Table(map! {
|
||||
a, Table(map! {
|
||||
b, Integer(5)
|
||||
})
|
||||
})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unused_fields3() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Bar }
|
||||
#[derive(RustcEncodable, RustcDecodable, 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, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
assert_eq!(d.toml, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unused_fields4() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: BTreeMap<String, String> }
|
||||
|
||||
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, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
assert_eq!(d.toml, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unused_fields5() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Vec<String> }
|
||||
|
||||
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, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
assert_eq!(d.toml, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unused_fields6() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Option<Vec<String>> }
|
||||
|
||||
let v = Foo { a: Some(vec![]) };
|
||||
let mut d = Decoder::new(Table(map! {
|
||||
a, Array(vec![])
|
||||
}));
|
||||
assert_eq!(v, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
assert_eq!(d.toml, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unused_fields7() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Vec<Bar> }
|
||||
#[derive(RustcEncodable, RustcDecodable, 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, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
assert_eq!(d.toml, Some(Table(map! {
|
||||
a, Array(vec![Table(map! {
|
||||
b, Integer(2)
|
||||
})])
|
||||
})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unused_fields8() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: BTreeMap<String, Bar> }
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Bar { a: isize }
|
||||
|
||||
let v = Foo { a: map! { a, Bar { a: 2 } } };
|
||||
let mut d = Decoder::new(Table(map! {
|
||||
a, Table(map! {
|
||||
a, Table(map! {
|
||||
a, Integer(2),
|
||||
b, Integer(2)
|
||||
})
|
||||
})
|
||||
}));
|
||||
assert_eq!(v, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
assert_eq!(d.toml, Some(Table(map! {
|
||||
a, Table(map! {
|
||||
a, Table(map! {
|
||||
b, Integer(2)
|
||||
})
|
||||
})
|
||||
})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_arrays() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Vec<Bar> }
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Bar;
|
||||
|
||||
let v = Foo { a: vec![] };
|
||||
let mut d = Decoder::new(Table(map! {}));
|
||||
assert_eq!(v, Decodable::decode(&mut d).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_arrays2() {
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Foo { a: Option<Vec<Bar>> }
|
||||
#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
|
||||
struct Bar;
|
||||
|
||||
let v = Foo { a: None };
|
||||
let mut d = Decoder::new(Table(map! {}));
|
||||
assert_eq!(v, Decodable::decode(&mut d).unwrap());
|
||||
|
||||
let v = Foo { a: Some(vec![]) };
|
||||
let mut d = Decoder::new(Table(map! {
|
||||
a, Array(vec![])
|
||||
}));
|
||||
assert_eq!(v, Decodable::decode(&mut d).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
let toml = r#"
|
||||
[test]
|
||||
foo = "bar"
|
||||
|
||||
[[values]]
|
||||
foo = "baz"
|
||||
|
||||
[[values]]
|
||||
foo = "qux"
|
||||
"#;
|
||||
|
||||
let value: Value = toml.parse().unwrap();
|
||||
let val2 = ::encode_str(&value).parse().unwrap();
|
||||
assert_eq!(value, val2);
|
||||
}
|
||||
}
|
|
@ -1,339 +0,0 @@
|
|||
use std::mem;
|
||||
|
||||
use serde::ser;
|
||||
use Value;
|
||||
use super::{Encoder, Error, EncoderState, State};
|
||||
|
||||
impl Encoder {
|
||||
fn table_begin(&mut self) -> Result<Self, Error> {
|
||||
match self.state {
|
||||
State::NextMapKey => Err(Error::InvalidMapKeyLocation),
|
||||
_ => Ok(mem::replace(self, Encoder::new()))
|
||||
}
|
||||
}
|
||||
|
||||
fn table_end(&mut self, mut state: Self) -> Result<(), Error> {
|
||||
match state.state {
|
||||
State::NextKey(key) => {
|
||||
mem::swap(&mut self.toml, &mut state.toml);
|
||||
self.toml.insert(key, Value::Table(state.toml));
|
||||
},
|
||||
State::NextArray(mut arr) => {
|
||||
mem::swap(&mut self.toml, &mut state.toml);
|
||||
arr.push(Value::Table(state.toml));
|
||||
self.state = State::NextArray(arr);
|
||||
},
|
||||
State::Start => {},
|
||||
State::NextMapKey => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Serializer for Encoder {
|
||||
type Error = Error;
|
||||
type MapState = Self;
|
||||
type StructState = Self;
|
||||
type StructVariantState = Self;
|
||||
type SeqState = EncoderState;
|
||||
type TupleState = EncoderState;
|
||||
type TupleStructState = EncoderState;
|
||||
type TupleVariantState = EncoderState;
|
||||
|
||||
fn serialize_bool(&mut self, v: bool) -> Result<(), Error> {
|
||||
self.emit_value(Value::Boolean(v))
|
||||
}
|
||||
|
||||
fn serialize_i64(&mut self, v: i64) -> Result<(), Error> {
|
||||
self.emit_value(Value::Integer(v))
|
||||
}
|
||||
|
||||
// TODO: checked casts
|
||||
|
||||
fn serialize_u64(&mut self, v: u64) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_isize(&mut self, v: isize) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_usize(&mut self, v: usize) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_i8(&mut self, v: i8) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_u8(&mut self, v: u8) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_i16(&mut self, v: i16) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_u16(&mut self, v: u16) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_i32(&mut self, v: i32) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_u32(&mut self, v: u32) -> Result<(), Error> {
|
||||
self.serialize_i64(v as i64)
|
||||
}
|
||||
|
||||
fn serialize_f32(&mut self, v: f32) -> Result<(), Error> {
|
||||
self.serialize_f64(v as f64)
|
||||
}
|
||||
|
||||
fn serialize_f64(&mut self, v: f64) -> Result<(), Error> {
|
||||
self.emit_value(Value::Float(v))
|
||||
}
|
||||
|
||||
fn serialize_str(&mut self, value: &str) -> Result<(), Error> {
|
||||
self.emit_value(Value::String(value.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(&mut self, _name: &'static str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_unit(&mut self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_none(&mut self) -> Result<(), Error> {
|
||||
self.emit_none()
|
||||
}
|
||||
|
||||
fn serialize_char(&mut self, c: char) -> Result<(), Error> {
|
||||
self.serialize_str(&c.to_string())
|
||||
}
|
||||
|
||||
fn serialize_some<V>(&mut self, value: V) -> Result<(), Error>
|
||||
where V: ser::Serialize
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_bytes(&mut self, v: &[u8]) -> Result<(), Error> {
|
||||
let mut state = try!(self.serialize_seq(Some(v.len())));
|
||||
for c in v {
|
||||
try!(self.serialize_seq_elt(&mut state, c));
|
||||
}
|
||||
self.serialize_seq_end(state)
|
||||
}
|
||||
|
||||
fn serialize_seq_fixed_size(&mut self, len: usize)
|
||||
-> Result<EncoderState, Error> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_seq(&mut self, _len: Option<usize>)
|
||||
-> Result<EncoderState, Error> {
|
||||
self.seq_begin().map(|s| EncoderState { inner: s })
|
||||
}
|
||||
|
||||
fn serialize_seq_elt<T>(&mut self,
|
||||
_state: &mut EncoderState,
|
||||
value: T) -> Result<(), Error>
|
||||
where T: ser::Serialize
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_seq_end(&mut self, state: EncoderState) -> Result<(), Error> {
|
||||
self.seq_end(state.inner)
|
||||
}
|
||||
|
||||
fn serialize_tuple(&mut self, len: usize)
|
||||
-> Result<EncoderState, Error> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_tuple_elt<T>(&mut self,
|
||||
state: &mut EncoderState,
|
||||
value: T) -> Result<(), Error>
|
||||
where T: ser::Serialize
|
||||
{
|
||||
self.serialize_seq_elt(state, value)
|
||||
}
|
||||
|
||||
fn serialize_tuple_end(&mut self, state: EncoderState) -> Result<(), Error> {
|
||||
self.serialize_seq_end(state)
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(&mut self,
|
||||
_name: &'static str,
|
||||
len: usize) -> Result<EncoderState, Error> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct_elt<T>(&mut self,
|
||||
state: &mut EncoderState,
|
||||
value: T) -> Result<(), Error>
|
||||
where T: ser::Serialize
|
||||
{
|
||||
self.serialize_seq_elt(state, value)
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct_end(&mut self, state: EncoderState)
|
||||
-> Result<(), Error> {
|
||||
self.serialize_seq_end(state)
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(&mut self,
|
||||
_name: &'static str,
|
||||
_id: usize,
|
||||
_variant: &'static str,
|
||||
len: usize) -> Result<EncoderState, Error> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant_elt<T>(&mut self,
|
||||
state: &mut EncoderState,
|
||||
value: T) -> Result<(), Error>
|
||||
where T: ser::Serialize
|
||||
{
|
||||
self.serialize_seq_elt(state, value)
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant_end(&mut self, state: EncoderState)
|
||||
-> Result<(), Error> {
|
||||
self.serialize_seq_end(state)
|
||||
}
|
||||
|
||||
fn serialize_map(&mut self, _len: Option<usize>) -> Result<Self, Error> {
|
||||
self.table_begin()
|
||||
}
|
||||
|
||||
fn serialize_map_key<K>(&mut self,
|
||||
_state: &mut Encoder,
|
||||
key: K) -> Result<(), Error>
|
||||
where K: ser::Serialize
|
||||
{
|
||||
self.table_key(|me| key.serialize(me))
|
||||
}
|
||||
|
||||
fn serialize_map_value<V>(&mut self,
|
||||
_state: &mut Encoder,
|
||||
value: V) -> Result<(), Error>
|
||||
where V: ser::Serialize
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_map_end(&mut self, state: Self) -> Result<(), Error> {
|
||||
self.table_end(state)
|
||||
}
|
||||
|
||||
fn serialize_struct(&mut self,
|
||||
_name: &'static str,
|
||||
len: usize) -> Result<Self, Error> {
|
||||
self.serialize_map(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_struct_elt<V>(&mut self,
|
||||
state: &mut Encoder,
|
||||
key: &'static str,
|
||||
value: V) -> Result<(), Error>
|
||||
where V: ser::Serialize
|
||||
{
|
||||
try!(self.serialize_map_key(state, key));
|
||||
self.serialize_map_value(state, value)
|
||||
}
|
||||
|
||||
fn serialize_struct_end(&mut self, state: Self) -> Result<(), Error> {
|
||||
self.serialize_map_end(state)
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(&mut self,
|
||||
_name: &'static str,
|
||||
_id: usize,
|
||||
_variant: &'static str,
|
||||
len: usize) -> Result<Self, Error> {
|
||||
self.serialize_map(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_struct_variant_elt<V>(&mut self,
|
||||
state: &mut Encoder,
|
||||
key: &'static str,
|
||||
value: V) -> Result<(), Error>
|
||||
where V: ser::Serialize
|
||||
{
|
||||
try!(self.serialize_map_key(state, key));
|
||||
self.serialize_map_value(state, value)
|
||||
}
|
||||
|
||||
fn serialize_struct_variant_end(&mut self, state: Self) -> Result<(), Error> {
|
||||
self.serialize_map_end(state)
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T>(&mut self,
|
||||
_name: &'static str,
|
||||
value: T) -> Result<(), Self::Error>
|
||||
where T: ser::Serialize,
|
||||
{
|
||||
// Don't serialize the newtype struct in a tuple.
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T>(&mut self,
|
||||
_name: &'static str,
|
||||
_variant_index: usize,
|
||||
_variant: &'static str,
|
||||
value: T) -> Result<(), Self::Error>
|
||||
where T: ser::Serialize,
|
||||
{
|
||||
// Don't serialize the newtype struct variant in a tuple.
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(&mut self,
|
||||
_name: &'static str,
|
||||
_variant_index: usize,
|
||||
_variant: &'static str,
|
||||
) -> Result<(), Self::Error>
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Serialize for Value {
|
||||
fn serialize<E>(&self, e: &mut E) -> Result<(), E::Error>
|
||||
where E: ser::Serializer
|
||||
{
|
||||
match *self {
|
||||
Value::String(ref s) => e.serialize_str(s),
|
||||
Value::Integer(i) => e.serialize_i64(i),
|
||||
Value::Float(f) => e.serialize_f64(f),
|
||||
Value::Boolean(b) => e.serialize_bool(b),
|
||||
Value::Datetime(ref s) => e.serialize_str(s),
|
||||
Value::Array(ref a) => {
|
||||
let mut state = try!(e.serialize_seq(Some(a.len())));
|
||||
for el in a.iter() {
|
||||
try!(e.serialize_seq_elt(&mut state, el));
|
||||
}
|
||||
e.serialize_seq_end(state)
|
||||
}
|
||||
Value::Table(ref t) => {
|
||||
let mut state = try!(e.serialize_map(Some(t.len())));
|
||||
for (k, v) in t.iter() {
|
||||
try!(e.serialize_map_key(&mut state, k));
|
||||
try!(e.serialize_map_value(&mut state, v));
|
||||
}
|
||||
e.serialize_map_end(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Error for Error {
|
||||
fn custom<T: Into<String>>(msg: T) -> Error {
|
||||
Error::Custom(msg.into())
|
||||
}
|
||||
}
|
642
src/lib.rs
642
src/lib.rs
|
@ -1,507 +1,169 @@
|
|||
//! A TOML-parsing library
|
||||
//! A [TOML]-parsing library
|
||||
//!
|
||||
//! This library is an implementation in Rust of a parser for TOML configuration
|
||||
//! files [1]. It is focused around high quality errors including specific spans
|
||||
//! and detailed error messages when things go wrong.
|
||||
//! [TOML]: https://github.com/toml-lang/toml
|
||||
//!
|
||||
//! This implementation currently passes the language agnostic [test suite][2].
|
||||
//! This library implements a [TOML] v0.4.0 compatible parser. This crate also
|
||||
//! primarily supports the [`serde`] library for encoding/decoding support to
|
||||
//! various types in Rust.
|
||||
//!
|
||||
//! # Example
|
||||
//! TOML itself is a simple, ergonomic, and readable configuration format:
|
||||
//!
|
||||
//! ```
|
||||
//! let toml = r#"
|
||||
//! [test]
|
||||
//! foo = "bar"
|
||||
//! "#;
|
||||
//! ```toml
|
||||
//! [package]
|
||||
//! name = "toml"
|
||||
//! version = "0.2.1"
|
||||
//! authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
//!
|
||||
//! let value = toml::Parser::new(toml).parse().unwrap();
|
||||
//! println!("{:?}", value);
|
||||
//! [dependencies]
|
||||
//! serde = "0.9"
|
||||
//! ```
|
||||
//!
|
||||
//! # Conversions
|
||||
//! The TOML format tends to be relatively common throughout the Rust community
|
||||
//! for configuration, notably being used by [Cargo], Rust's package manager.
|
||||
//!
|
||||
//! This library also supports using the standard `Encodable` and `Decodable`
|
||||
//! traits with TOML values. This library provides the following conversion
|
||||
//! capabilities:
|
||||
//! ## TOML values
|
||||
//!
|
||||
//! * `String` => `toml::Value` - via `Parser`
|
||||
//! * `toml::Value` => `String` - via `Display`
|
||||
//! * `toml::Value` => rust object - via `Decoder`
|
||||
//! * rust object => `toml::Value` - via `Encoder`
|
||||
//! A value in TOML is represented with the `Value` enum in this crate:
|
||||
//!
|
||||
//! Convenience functions for performing multiple conversions at a time are also
|
||||
//! provided.
|
||||
//! ```rust,ignore
|
||||
//! pub enum Value {
|
||||
//! String(String),
|
||||
//! Integer(i64),
|
||||
//! Float(f64),
|
||||
//! Boolean(bool),
|
||||
//! Datetime(Datetime),
|
||||
//! Array(Array),
|
||||
//! Table(Table),
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [1]: https://github.com/mojombo/toml
|
||||
//! [2]: https://github.com/BurntSushi/toml-test
|
||||
//! You'll note that TOML is very similar to JSON with the notable addition of a
|
||||
//! `Datetime` type. In general TOML and JSON are interchangeable in terms of
|
||||
//! formats.
|
||||
//!
|
||||
//! # Encoding support
|
||||
//! ## Parsing TOML
|
||||
//!
|
||||
//! This crate optionally supports the [`rustc-serialize`] crate and also the
|
||||
//! [`serde`] crate through respective feature names. The `rustc-serialize`
|
||||
//! feature is enabled by default.
|
||||
//! The easiest way to parse a TOML document is via the `Value` type:
|
||||
//!
|
||||
//! [`rustc-serialize`]: http://github.com/rust-lang/rustc-serialize
|
||||
//! [`serde`]: http://github.com/serde-rs/serde
|
||||
//! ```rust
|
||||
//! use toml::Value;
|
||||
//!
|
||||
//! let value = "foo = 'bar'".parse::<Value>().unwrap();
|
||||
//!
|
||||
//! assert_eq!(value["foo"].as_str(), Some("bar"));
|
||||
//! ```
|
||||
//!
|
||||
//! The `Value` type implements a number of convenience methods and traits,
|
||||
//! where the example above is using `FromStr` to parse a `str` into a `Value`.
|
||||
//!
|
||||
//! ## Deserialization and Serialization
|
||||
//!
|
||||
//! This crate currently supports [`serde`] 0.9 with a number of
|
||||
//! implementations of the `Deserialize`, `Serialize`, `Deserializer`, and
|
||||
//! `Serializer` traits. Namely, you'll find in this crate:
|
||||
//!
|
||||
//! * `Deserialize for Value`
|
||||
//! * `Serialize for Value`
|
||||
//! * `Deserialize for Datetime`
|
||||
//! * `Serialize for Datetime`
|
||||
//!
|
||||
//! * `Deserializer for de::Deserializer`
|
||||
//! * `Serializer for ser::Serializer`
|
||||
//! * `Deserializer for Value`
|
||||
//!
|
||||
//! This notably means that you can use Serde to deserialize/serialize the
|
||||
//! `Value` type as well as the `Datetime` type in this crate. Similarly you can
|
||||
//! use the `Deserializer`, `Serializer`, or `Value` type itself to act as
|
||||
//! a deserializer/serializer for arbitrary types.
|
||||
//!
|
||||
//! An example of deserializing with TOML is:
|
||||
//!
|
||||
//! ```rust
|
||||
//! #[macro_use]
|
||||
//! extern crate serde_derive;
|
||||
//! extern crate toml;
|
||||
//!
|
||||
//! #[derive(Deserialize)]
|
||||
//! struct Config {
|
||||
//! ip: String,
|
||||
//! port: Option<u16>,
|
||||
//! keys: Keys,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Deserialize)]
|
||||
//! struct Keys {
|
||||
//! github: String,
|
||||
//! travis: Option<String>,
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let config: Config = toml::from_str(r#"
|
||||
//! ip = '127.0.0.1'
|
||||
//!
|
||||
//! [keys]
|
||||
//! github = 'xxxxxxxxxxxxxxxxx'
|
||||
//! travis = 'yyyyyyyyyyyyyyyyy'
|
||||
//! "#).unwrap();
|
||||
//!
|
||||
//! assert_eq!(config.ip, "127.0.0.1");
|
||||
//! assert_eq!(config.port, None);
|
||||
//! assert_eq!(config.keys.github, "xxxxxxxxxxxxxxxxx");
|
||||
//! assert_eq!(config.keys.travis.as_ref().unwrap(), "yyyyyyyyyyyyyyyyy");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Similarly you can serialize types in a similar fashion:
|
||||
//!
|
||||
//! ```rust
|
||||
//! #[macro_use]
|
||||
//! extern crate serde_derive;
|
||||
//! extern crate toml;
|
||||
//!
|
||||
//! #[derive(Serialize)]
|
||||
//! struct Config {
|
||||
//! ip: String,
|
||||
//! port: Option<u16>,
|
||||
//! keys: Keys,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Serialize)]
|
||||
//! struct Keys {
|
||||
//! github: String,
|
||||
//! travis: Option<String>,
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let config = Config {
|
||||
//! ip: "127.0.0.1".to_string(),
|
||||
//! port: None,
|
||||
//! keys: Keys {
|
||||
//! github: "xxxxxxxxxxxxxxxxx".to_string(),
|
||||
//! travis: Some("yyyyyyyyyyyyyyyyy".to_string()),
|
||||
//! },
|
||||
//! };
|
||||
//!
|
||||
//! let toml = toml::to_string(&config).unwrap();
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [Cargo]: https://crates.io/
|
||||
//! [`serde`]: https://serde.rs/
|
||||
|
||||
#![doc(html_root_url = "http://alexcrichton.com/toml-rs")]
|
||||
#![doc(html_root_url = "https://docs.rs/toml/0.3")]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
|
||||
#[cfg(feature = "rustc-serialize")] extern crate rustc_serialize;
|
||||
#[cfg(feature = "serde")] extern crate serde;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use parser::{Parser, ParserError};
|
||||
|
||||
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
|
||||
pub use self::encoder::{Encoder, Error, EncoderState, encode, encode_str};
|
||||
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
|
||||
pub use self::decoder::{Decoder, DecodeError, DecodeErrorKind, decode, decode_str};
|
||||
|
||||
mod parser;
|
||||
mod display;
|
||||
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
|
||||
mod encoder;
|
||||
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
|
||||
mod decoder;
|
||||
|
||||
/// Representation of a TOML value.
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Value {
|
||||
String(String),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Boolean(bool),
|
||||
Datetime(String),
|
||||
Array(Array),
|
||||
Table(Table),
|
||||
}
|
||||
|
||||
/// Type representing a TOML array, payload of the `Value::Array` variant
|
||||
pub type Array = Vec<Value>;
|
||||
|
||||
/// Type representing a TOML table, payload of the `Value::Table` variant
|
||||
pub type Table = BTreeMap<String, Value>;
|
||||
|
||||
impl Value {
|
||||
/// Tests whether this and another value have the same type.
|
||||
pub fn same_type(&self, other: &Value) -> bool {
|
||||
match (self, other) {
|
||||
(&Value::String(..), &Value::String(..)) |
|
||||
(&Value::Integer(..), &Value::Integer(..)) |
|
||||
(&Value::Float(..), &Value::Float(..)) |
|
||||
(&Value::Boolean(..), &Value::Boolean(..)) |
|
||||
(&Value::Datetime(..), &Value::Datetime(..)) |
|
||||
(&Value::Array(..), &Value::Array(..)) |
|
||||
(&Value::Table(..), &Value::Table(..)) => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a human-readable representation of the type of this value.
|
||||
pub fn type_str(&self) -> &'static str {
|
||||
match *self {
|
||||
Value::String(..) => "string",
|
||||
Value::Integer(..) => "integer",
|
||||
Value::Float(..) => "float",
|
||||
Value::Boolean(..) => "boolean",
|
||||
Value::Datetime(..) => "datetime",
|
||||
Value::Array(..) => "array",
|
||||
Value::Table(..) => "table",
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the string of this value if it is a string.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
match *self { Value::String(ref s) => Some(&**s), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the integer value if it is an integer.
|
||||
pub fn as_integer(&self) -> Option<i64> {
|
||||
match *self { Value::Integer(i) => Some(i), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the float value if it is a float.
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
match *self { Value::Float(f) => Some(f), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the boolean value if it is a boolean.
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match *self { Value::Boolean(b) => Some(b), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the datetime value if it is a datetime.
|
||||
///
|
||||
/// Note that a parsed TOML value will only contain ISO 8601 dates. An
|
||||
/// example date is:
|
||||
///
|
||||
/// ```notrust
|
||||
/// 1979-05-27T07:32:00Z
|
||||
/// ```
|
||||
pub fn as_datetime(&self) -> Option<&str> {
|
||||
match *self { Value::Datetime(ref s) => Some(&**s), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the array value if it is an array.
|
||||
pub fn as_slice(&self) -> Option<&[Value]> {
|
||||
match *self { Value::Array(ref s) => Some(&**s), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the table value if it is a table.
|
||||
pub fn as_table(&self) -> Option<&Table> {
|
||||
match *self { Value::Table(ref s) => Some(s), _ => None }
|
||||
}
|
||||
|
||||
/// Lookups for value at specified path.
|
||||
///
|
||||
/// Uses '.' as a path separator.
|
||||
///
|
||||
/// Note: arrays have zero-based indexes.
|
||||
///
|
||||
/// Note: empty path returns self.
|
||||
///
|
||||
/// ```
|
||||
/// # #![allow(unstable)]
|
||||
/// let toml = r#"
|
||||
/// [test]
|
||||
/// foo = "bar"
|
||||
///
|
||||
/// [[values]]
|
||||
/// foo = "baz"
|
||||
///
|
||||
/// [[values]]
|
||||
/// foo = "qux"
|
||||
/// "#;
|
||||
/// let value: toml::Value = toml.parse().unwrap();
|
||||
///
|
||||
/// let foo = value.lookup("test.foo").unwrap();
|
||||
/// assert_eq!(foo.as_str().unwrap(), "bar");
|
||||
///
|
||||
/// let foo = value.lookup("values.1.foo").unwrap();
|
||||
/// assert_eq!(foo.as_str().unwrap(), "qux");
|
||||
///
|
||||
/// let no_bar = value.lookup("test.bar");
|
||||
/// assert_eq!(no_bar.is_none(), true);
|
||||
/// ```
|
||||
pub fn lookup(&self, path: &str) -> Option<&Value> {
|
||||
let ref path = match Parser::new(path).lookup() {
|
||||
Some(path) => path,
|
||||
None => return None,
|
||||
};
|
||||
let mut cur_value = self;
|
||||
if path.is_empty() {
|
||||
return Some(cur_value)
|
||||
}
|
||||
|
||||
for key in path {
|
||||
match *cur_value {
|
||||
Value::Table(ref hm) => {
|
||||
match hm.get(key) {
|
||||
Some(v) => cur_value = v,
|
||||
None => return None
|
||||
}
|
||||
},
|
||||
Value::Array(ref v) => {
|
||||
match key.parse::<usize>().ok() {
|
||||
Some(idx) if idx < v.len() => cur_value = &v[idx],
|
||||
_ => return None
|
||||
}
|
||||
},
|
||||
_ => return None
|
||||
}
|
||||
};
|
||||
|
||||
Some(cur_value)
|
||||
|
||||
}
|
||||
/// Lookups for mutable value at specified path.
|
||||
///
|
||||
/// Uses '.' as a path separator.
|
||||
///
|
||||
/// Note: arrays have zero-based indexes.
|
||||
///
|
||||
/// Note: empty path returns self.
|
||||
///
|
||||
/// ```
|
||||
/// # #![allow(unstable)]
|
||||
/// let toml = r#"
|
||||
/// [test]
|
||||
/// foo = "bar"
|
||||
///
|
||||
/// [[values]]
|
||||
/// foo = "baz"
|
||||
///
|
||||
/// [[values]]
|
||||
/// foo = "qux"
|
||||
/// "#;
|
||||
/// let mut value: toml::Value = toml.parse().unwrap();
|
||||
/// {
|
||||
/// let string = value.lookup_mut("test.foo").unwrap();
|
||||
/// assert_eq!(string, &mut toml::Value::String(String::from("bar")));
|
||||
/// *string = toml::Value::String(String::from("foo"));
|
||||
/// }
|
||||
/// let result = value.lookup_mut("test.foo").unwrap();
|
||||
/// assert_eq!(result.as_str().unwrap(), "foo");
|
||||
/// ```
|
||||
pub fn lookup_mut(&mut self, path: &str) -> Option<&mut Value> {
|
||||
let ref path = match Parser::new(path).lookup() {
|
||||
Some(path) => path,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let mut cur = self;
|
||||
if path.is_empty() {
|
||||
return Some(cur)
|
||||
}
|
||||
|
||||
for key in path {
|
||||
let tmp = cur;
|
||||
match *tmp {
|
||||
Value::Table(ref mut hm) => {
|
||||
match hm.get_mut(key) {
|
||||
Some(v) => cur = v,
|
||||
None => return None
|
||||
}
|
||||
}
|
||||
Value::Array(ref mut v) => {
|
||||
match key.parse::<usize>().ok() {
|
||||
Some(idx) if idx < v.len() => cur = &mut v[idx],
|
||||
_ => return None
|
||||
}
|
||||
}
|
||||
_ => return None
|
||||
}
|
||||
}
|
||||
Some(cur)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Value {
|
||||
type Err = Vec<ParserError>;
|
||||
fn from_str(s: &str) -> Result<Value, Vec<ParserError>> {
|
||||
let mut p = Parser::new(s);
|
||||
match p.parse().map(Value::Table) {
|
||||
Some(n) => Ok(n),
|
||||
None => Err(p.errors),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Value;
|
||||
|
||||
#[test]
|
||||
fn lookup_mut_change() {
|
||||
let toml = r#"
|
||||
[test]
|
||||
foo = "bar"
|
||||
|
||||
[[values]]
|
||||
foo = "baz"
|
||||
|
||||
[[values]]
|
||||
foo = "qux"
|
||||
"#;
|
||||
|
||||
let mut value: Value = toml.parse().unwrap();
|
||||
{
|
||||
let foo = value.lookup_mut("values.0.foo").unwrap();
|
||||
*foo = Value::String(String::from("bar"));
|
||||
}
|
||||
let foo = value.lookup("values.0.foo").unwrap();
|
||||
assert_eq!(foo.as_str().unwrap(), "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_mut_valid() {
|
||||
let toml = r#"
|
||||
[test]
|
||||
foo = "bar"
|
||||
|
||||
[[values]]
|
||||
foo = "baz"
|
||||
|
||||
[[values]]
|
||||
foo = "qux"
|
||||
"#;
|
||||
|
||||
let mut value: Value = toml.parse().unwrap();
|
||||
|
||||
{
|
||||
let test_foo = value.lookup_mut("test.foo").unwrap();
|
||||
assert_eq!(test_foo.as_str().unwrap(), "bar");
|
||||
}
|
||||
|
||||
{
|
||||
let foo1 = value.lookup_mut("values.1.foo").unwrap();
|
||||
assert_eq!(foo1.as_str().unwrap(), "qux");
|
||||
}
|
||||
|
||||
assert!(value.lookup_mut("test.bar").is_none());
|
||||
assert!(value.lookup_mut("test.foo.bar").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_mut_invalid_index() {
|
||||
let toml = r#"
|
||||
[[values]]
|
||||
foo = "baz"
|
||||
"#;
|
||||
|
||||
let mut value: Value = toml.parse().unwrap();
|
||||
|
||||
{
|
||||
let foo = value.lookup_mut("test.foo");
|
||||
assert!(foo.is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let foo = value.lookup_mut("values.100.foo");
|
||||
assert!(foo.is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let foo = value.lookup_mut("values.str.foo");
|
||||
assert!(foo.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_mut_self() {
|
||||
let mut value: Value = r#"foo = "bar""#.parse().unwrap();
|
||||
|
||||
{
|
||||
let foo = value.lookup_mut("foo").unwrap();
|
||||
assert_eq!(foo.as_str().unwrap(), "bar");
|
||||
}
|
||||
|
||||
let foo = value.lookup_mut("").unwrap();
|
||||
assert!(foo.as_table().is_some());
|
||||
|
||||
let baz = foo.lookup_mut("foo").unwrap();
|
||||
assert_eq!(baz.as_str().unwrap(), "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_valid() {
|
||||
let toml = r#"
|
||||
[test]
|
||||
foo = "bar"
|
||||
|
||||
[[values]]
|
||||
foo = "baz"
|
||||
|
||||
[[values]]
|
||||
foo = "qux"
|
||||
"#;
|
||||
|
||||
let value: Value = toml.parse().unwrap();
|
||||
|
||||
let test_foo = value.lookup("test.foo").unwrap();
|
||||
assert_eq!(test_foo.as_str().unwrap(), "bar");
|
||||
|
||||
let foo1 = value.lookup("values.1.foo").unwrap();
|
||||
assert_eq!(foo1.as_str().unwrap(), "qux");
|
||||
|
||||
assert!(value.lookup("test.bar").is_none());
|
||||
assert!(value.lookup("test.foo.bar").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_invalid_index() {
|
||||
let toml = r#"
|
||||
[[values]]
|
||||
foo = "baz"
|
||||
"#;
|
||||
|
||||
let value: Value = toml.parse().unwrap();
|
||||
|
||||
let foo = value.lookup("test.foo");
|
||||
assert!(foo.is_none());
|
||||
|
||||
let foo = value.lookup("values.100.foo");
|
||||
assert!(foo.is_none());
|
||||
|
||||
let foo = value.lookup("values.str.foo");
|
||||
assert!(foo.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_self() {
|
||||
let value: Value = r#"foo = "bar""#.parse().unwrap();
|
||||
|
||||
let foo = value.lookup("foo").unwrap();
|
||||
assert_eq!(foo.as_str().unwrap(), "bar");
|
||||
|
||||
let foo = value.lookup("").unwrap();
|
||||
assert!(foo.as_table().is_some());
|
||||
|
||||
let baz = foo.lookup("foo").unwrap();
|
||||
assert_eq!(baz.as_str().unwrap(), "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_advanced() {
|
||||
let value: Value = "[table]\n\"value\" = 0".parse().unwrap();
|
||||
let looked = value.lookup("table.\"value\"").unwrap();
|
||||
assert_eq!(*looked, Value::Integer(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_advanced_table() {
|
||||
let value: Value = "[table.\"name.other\"]\nvalue = \"my value\"".parse().unwrap();
|
||||
let looked = value.lookup(r#"table."name.other".value"#).unwrap();
|
||||
assert_eq!(*looked, Value::String(String::from("my value")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_mut_advanced() {
|
||||
let mut value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap();
|
||||
let looked = value.lookup_mut("table.\"value\".1").unwrap();
|
||||
assert_eq!(*looked, Value::Integer(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_dot() {
|
||||
let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap();
|
||||
assert_eq!(None, value.lookup("."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_dot() {
|
||||
let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap();
|
||||
assert_eq!(None, value.lookup("0."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_inside() {
|
||||
let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap();
|
||||
assert_eq!(None, value.lookup("table.\"value.0\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_with_quotes() {
|
||||
let value: Value = "[table.\"element\"]\n\"value\" = [0, 1, 2]".parse().unwrap();
|
||||
assert_eq!(None, value.lookup("\"table.element\".\"value\".0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_with_quotes_2() {
|
||||
let value: Value = "[table.\"element\"]\n\"value\" = [0, 1, 2]".parse().unwrap();
|
||||
assert_eq!(Value::Integer(0), *value.lookup("table.\"element\".\"value\".0").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_characters() {
|
||||
let value = Value::String("\x05".to_string());
|
||||
assert_eq!(value.to_string(), r#""\u0005""#);
|
||||
}
|
||||
|
||||
}
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
|
||||
pub mod value;
|
||||
mod datetime;
|
||||
#[doc(no_inline)]
|
||||
pub use value::Value;
|
||||
|
||||
pub mod ser;
|
||||
#[doc(no_inline)]
|
||||
pub use ser::{to_string, to_vec, Serializer};
|
||||
pub mod de;
|
||||
#[doc(no_inline)]
|
||||
pub use de::{from_slice, from_str, Deserializer};
|
||||
mod tokens;
|
||||
|
|
1627
src/parser.rs
1627
src/parser.rs
File diff suppressed because it is too large
Load diff
1006
src/ser.rs
Normal file
1006
src/ser.rs
Normal file
File diff suppressed because it is too large
Load diff
620
src/tokens.rs
Normal file
620
src/tokens.rs
Normal file
|
@ -0,0 +1,620 @@
|
|||
use std::borrow::Cow;
|
||||
use std::char;
|
||||
use std::str;
|
||||
use std::string;
|
||||
|
||||
use self::Token::*;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum Token<'a> {
|
||||
Whitespace(&'a str),
|
||||
Newline,
|
||||
Comment(&'a str),
|
||||
|
||||
Equals,
|
||||
Period,
|
||||
Comma,
|
||||
Colon,
|
||||
Plus,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LeftBracket,
|
||||
RightBracket,
|
||||
|
||||
Keylike(&'a str),
|
||||
String { src: &'a str, val: Cow<'a, str> },
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum Error {
|
||||
InvalidCharInString(usize, char),
|
||||
InvalidEscape(usize, char),
|
||||
InvalidHexEscape(usize, char),
|
||||
InvalidEscapeValue(usize, u32),
|
||||
NewlineInString(usize),
|
||||
Unexpected(usize, char),
|
||||
UnterminatedString(usize),
|
||||
NewlineInTableKey(usize),
|
||||
EmptyTableKey(usize),
|
||||
Wanted { at: usize, expected: &'static str, found: &'static str },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Tokenizer<'a> {
|
||||
input: &'a str,
|
||||
chars: CrlfFold<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CrlfFold<'a> {
|
||||
chars: str::CharIndices<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MaybeString {
|
||||
NotEscaped(usize),
|
||||
Owned(string::String),
|
||||
}
|
||||
|
||||
impl<'a> Tokenizer<'a> {
|
||||
pub fn new(input: &'a str) -> Tokenizer<'a> {
|
||||
let mut t = Tokenizer {
|
||||
input: input,
|
||||
chars: CrlfFold {
|
||||
chars: input.char_indices(),
|
||||
},
|
||||
};
|
||||
// Eat utf-8 BOM
|
||||
t.eatc('\u{feff}');
|
||||
return t
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Result<Option<Token<'a>>, Error> {
|
||||
let token = match self.chars.next() {
|
||||
Some((_, '\n')) => Newline,
|
||||
Some((start, ' ')) => self.whitespace_token(start),
|
||||
Some((start, '\t')) => self.whitespace_token(start),
|
||||
Some((start, '#')) => self.comment_token(start),
|
||||
Some((_, '=')) => Equals,
|
||||
Some((_, '.')) => Period,
|
||||
Some((_, ',')) => Comma,
|
||||
Some((_, ':')) => Colon,
|
||||
Some((_, '+')) => Plus,
|
||||
Some((_, '{')) => LeftBrace,
|
||||
Some((_, '}')) => RightBrace,
|
||||
Some((_, '[')) => LeftBracket,
|
||||
Some((_, ']')) => RightBracket,
|
||||
Some((start, '\'')) => return self.literal_string(start).map(Some),
|
||||
Some((start, '"')) => return self.basic_string(start).map(Some),
|
||||
Some((start, ch)) if is_keylike(ch) => self.keylike(start),
|
||||
|
||||
Some((start, ch)) => return Err(Error::Unexpected(start, ch)),
|
||||
None => return Ok(None),
|
||||
};
|
||||
Ok(Some(token))
|
||||
}
|
||||
|
||||
pub fn peek(&mut self) -> Result<Option<Token<'a>>, Error> {
|
||||
self.clone().next()
|
||||
}
|
||||
|
||||
pub fn eat(&mut self, expected: Token<'a>) -> Result<bool, Error> {
|
||||
match self.peek()? {
|
||||
Some(ref found) if expected == *found => {}
|
||||
Some(_) => return Ok(false),
|
||||
None => return Ok(false),
|
||||
}
|
||||
drop(self.next());
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn expect(&mut self, expected: Token<'a>) -> Result<(), Error> {
|
||||
let current = self.current();
|
||||
match self.next()? {
|
||||
Some(found) => {
|
||||
if expected == found {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Wanted {
|
||||
at: current,
|
||||
expected: expected.describe(),
|
||||
found: found.describe(),
|
||||
})
|
||||
}
|
||||
}
|
||||
None => {
|
||||
Err(Error::Wanted {
|
||||
at: self.input.len(),
|
||||
expected: expected.describe(),
|
||||
found: "eof",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn table_key(&mut self) -> Result<Cow<'a, str>, Error> {
|
||||
let current = self.current();
|
||||
match self.next()? {
|
||||
Some(Token::Keylike(k)) => Ok(k.into()),
|
||||
Some(Token::String { src, val }) => {
|
||||
let offset = self.substr_offset(src);
|
||||
if val == "" {
|
||||
return Err(Error::EmptyTableKey(offset))
|
||||
}
|
||||
match src.find("\n") {
|
||||
None => Ok(val),
|
||||
Some(i) => Err(Error::NewlineInTableKey(offset + i)),
|
||||
}
|
||||
}
|
||||
Some(other) => {
|
||||
Err(Error::Wanted {
|
||||
at: current,
|
||||
expected: "a table key",
|
||||
found: other.describe(),
|
||||
})
|
||||
}
|
||||
None => {
|
||||
Err(Error::Wanted {
|
||||
at: self.input.len(),
|
||||
expected: "a table key",
|
||||
found: "eof",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eat_whitespace(&mut self) -> Result<(), Error> {
|
||||
while self.eatc(' ') || self.eatc('\t') {
|
||||
// ...
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eat_comment(&mut self) -> Result<bool, Error> {
|
||||
if !self.eatc('#') {
|
||||
return Ok(false)
|
||||
}
|
||||
drop(self.comment_token(0));
|
||||
self.eat_newline_or_eof().map(|()| true)
|
||||
}
|
||||
|
||||
pub fn eat_newline_or_eof(&mut self) -> Result<(), Error> {
|
||||
let current = self.current();
|
||||
match self.next()? {
|
||||
None |
|
||||
Some(Token::Newline) => Ok(()),
|
||||
Some(other) => {
|
||||
Err(Error::Wanted {
|
||||
at: current,
|
||||
expected: "newline",
|
||||
found: other.describe(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_to_newline(&mut self) {
|
||||
loop {
|
||||
match self.chars.next() {
|
||||
Some((_, '\n')) |
|
||||
None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eatc(&mut self, ch: char) -> bool {
|
||||
match self.chars.clone().next() {
|
||||
Some((_, ch2)) if ch == ch2 => {
|
||||
self.chars.next();
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(&mut self) -> usize {
|
||||
self.chars.clone().next().map(|i| i.0).unwrap_or(self.input.len())
|
||||
}
|
||||
|
||||
pub fn input(&self) -> &'a str {
|
||||
self.input
|
||||
}
|
||||
|
||||
fn whitespace_token(&mut self, start: usize) -> Token<'a> {
|
||||
while self.eatc(' ') || self.eatc('\t') {
|
||||
// ...
|
||||
}
|
||||
Whitespace(&self.input[start..self.current()])
|
||||
}
|
||||
|
||||
fn comment_token(&mut self, start: usize) -> Token<'a> {
|
||||
while let Some((_, ch)) = self.chars.clone().next() {
|
||||
if ch != '\t' && (ch < '\u{20}' || ch > '\u{10ffff}') {
|
||||
break
|
||||
}
|
||||
self.chars.next();
|
||||
}
|
||||
Comment(&self.input[start..self.current()])
|
||||
}
|
||||
|
||||
fn read_string(&mut self,
|
||||
delim: char,
|
||||
start: usize,
|
||||
new_ch: &mut FnMut(&mut Tokenizer, &mut MaybeString,
|
||||
bool, usize, char)
|
||||
-> Result<(), Error>)
|
||||
-> Result<Token<'a>, Error> {
|
||||
let mut multiline = false;
|
||||
if self.eatc(delim) {
|
||||
if self.eatc(delim) {
|
||||
multiline = true;
|
||||
} else {
|
||||
return Ok(String {
|
||||
src: &self.input[start..start+2],
|
||||
val: Cow::Borrowed(""),
|
||||
})
|
||||
}
|
||||
}
|
||||
let mut val = MaybeString::NotEscaped(self.current());
|
||||
let mut n = 0;
|
||||
'outer: loop {
|
||||
n += 1;
|
||||
match self.chars.next() {
|
||||
Some((i, '\n')) => {
|
||||
if multiline {
|
||||
if self.input.as_bytes()[i] == b'\r' {
|
||||
val.to_owned(&self.input[..i]);
|
||||
}
|
||||
if n == 1 {
|
||||
val = MaybeString::NotEscaped(self.current());
|
||||
} else {
|
||||
val.push('\n');
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
return Err(Error::NewlineInString(i))
|
||||
}
|
||||
}
|
||||
Some((i, ch)) if ch == delim => {
|
||||
if multiline {
|
||||
for _ in 0..2 {
|
||||
if !self.eatc(delim) {
|
||||
val.push(delim);
|
||||
continue 'outer
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(String {
|
||||
src: &self.input[start..self.current()],
|
||||
val: val.into_cow(&self.input[..i]),
|
||||
})
|
||||
}
|
||||
Some((i, c)) => try!(new_ch(self, &mut val, multiline, i, c)),
|
||||
None => return Err(Error::UnterminatedString(start))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn literal_string(&mut self, start: usize) -> Result<Token<'a>, Error> {
|
||||
self.read_string('\'', start, &mut |_me, val, _multi, i, ch| {
|
||||
if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}') {
|
||||
val.push(ch);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::InvalidCharInString(i, ch))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn basic_string(&mut self, start: usize) -> Result<Token<'a>, Error> {
|
||||
self.read_string('"', start, &mut |me, val, multi, i, ch| {
|
||||
match ch {
|
||||
'\\' => {
|
||||
val.to_owned(&me.input[..i]);
|
||||
match me.chars.next() {
|
||||
Some((_, '"')) => val.push('"'),
|
||||
Some((_, '\\')) => val.push('\\'),
|
||||
Some((_, 'b')) => val.push('\u{8}'),
|
||||
Some((_, 'f')) => val.push('\u{c}'),
|
||||
Some((_, 'n')) => val.push('\n'),
|
||||
Some((_, 'r')) => val.push('\r'),
|
||||
Some((_, 't')) => val.push('\t'),
|
||||
Some((i, c @ 'u')) |
|
||||
Some((i, c @ 'U')) => {
|
||||
let len = if c == 'u' {4} else {8};
|
||||
val.push(try!(me.hex(start, i, len)));
|
||||
}
|
||||
Some((_, '\n')) if multi => {
|
||||
while let Some((_, ch)) = me.chars.clone().next() {
|
||||
match ch {
|
||||
' ' | '\t' | '\n' => {
|
||||
me.chars.next();
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some((i, c)) => return Err(Error::InvalidEscape(i, c)),
|
||||
None => return Err(Error::UnterminatedString(start)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ch if '\u{20}' <= ch && ch <= '\u{10ffff}' => {
|
||||
val.push(ch);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidCharInString(i, ch))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn hex(&mut self, start: usize, i: usize, len: usize) -> Result<char, Error> {
|
||||
let mut val = 0;
|
||||
for _ in 0..len {
|
||||
match self.chars.next() {
|
||||
Some((_, ch)) if '0' <= ch && ch <= '9' => {
|
||||
val = val * 16 + (ch as u32 - '0' as u32);
|
||||
}
|
||||
Some((_, ch)) if 'A' <= ch && ch <= 'F' => {
|
||||
val = val * 16 + (ch as u32 - 'A' as u32) + 10;
|
||||
}
|
||||
Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)),
|
||||
None => return Err(Error::UnterminatedString(start)),
|
||||
}
|
||||
}
|
||||
match char::from_u32(val) {
|
||||
Some(ch) => Ok(ch),
|
||||
None => Err(Error::InvalidEscapeValue(i, val)),
|
||||
}
|
||||
}
|
||||
|
||||
fn keylike(&mut self, start: usize) -> Token<'a> {
|
||||
while let Some((_, ch)) = self.chars.clone().next() {
|
||||
if !is_keylike(ch) {
|
||||
break
|
||||
}
|
||||
self.chars.next();
|
||||
}
|
||||
Keylike(&self.input[start..self.current()])
|
||||
}
|
||||
|
||||
pub fn substr_offset(&self, s: &'a str) -> usize {
|
||||
assert!(s.len() < self.input.len());
|
||||
let a = self.input.as_ptr() as usize;
|
||||
let b = s.as_ptr() as usize;
|
||||
assert!(a <= b);
|
||||
b - a
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CrlfFold<'a> {
|
||||
type Item = (usize, char);
|
||||
|
||||
fn next(&mut self) -> Option<(usize, char)> {
|
||||
self.chars.next().map(|(i, c)| {
|
||||
if c == '\r' {
|
||||
let mut attempt = self.chars.clone();
|
||||
if let Some((_, '\n')) = attempt.next() {
|
||||
self.chars = attempt;
|
||||
return (i, '\n')
|
||||
}
|
||||
}
|
||||
(i, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MaybeString {
|
||||
fn push(&mut self, ch: char) {
|
||||
match *self {
|
||||
MaybeString::NotEscaped(..) => {}
|
||||
MaybeString::Owned(ref mut s) => s.push(ch),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_owned(&mut self, input: &str) {
|
||||
match *self {
|
||||
MaybeString::NotEscaped(start) => {
|
||||
*self = MaybeString::Owned(input[start..].to_owned());
|
||||
}
|
||||
MaybeString::Owned(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_cow<'a>(self, input: &'a str) -> Cow<'a, str> {
|
||||
match self {
|
||||
MaybeString::NotEscaped(start) => Cow::Borrowed(&input[start..]),
|
||||
MaybeString::Owned(s) => Cow::Owned(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_keylike(ch: char) -> bool {
|
||||
('A' <= ch && ch <= 'Z') ||
|
||||
('a' <= ch && ch <= 'z') ||
|
||||
('0' <= ch && ch <= '9') ||
|
||||
ch == '-' ||
|
||||
ch == '_'
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
pub fn describe(&self) -> &'static str {
|
||||
match *self {
|
||||
Token::Keylike(_) => "an identifier",
|
||||
Token::Equals => "an equals",
|
||||
Token::Period => "a period",
|
||||
Token::Comment(_) => "a comment",
|
||||
Token::Newline => "a newline",
|
||||
Token::Whitespace(_) => "whitespace",
|
||||
Token::Comma => "a comma",
|
||||
Token::RightBrace => "a right brace",
|
||||
Token::LeftBrace => "a left brace",
|
||||
Token::RightBracket => "a right bracket",
|
||||
Token::LeftBracket => "a left bracket",
|
||||
Token::String { .. } => "a string",
|
||||
Token::Colon => "a colon",
|
||||
Token::Plus => "a plus",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::borrow::Cow;
|
||||
use super::{Tokenizer, Token, Error};
|
||||
|
||||
fn err(input: &str, err: Error) {
|
||||
let mut t = Tokenizer::new(input);
|
||||
let token = t.next().unwrap_err();
|
||||
assert_eq!(token, err);
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_strings() {
|
||||
fn t(input: &str, val: &str) {
|
||||
let mut t = Tokenizer::new(input);
|
||||
let token = t.next().unwrap().unwrap();
|
||||
assert_eq!(token, Token::String {
|
||||
src: input,
|
||||
val: Cow::Borrowed(val),
|
||||
});
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
|
||||
t("''", "");
|
||||
t("''''''", "");
|
||||
t("'''\n'''", "");
|
||||
t("'a'", "a");
|
||||
t("'\"a'", "\"a");
|
||||
t("''''a'''", "'a");
|
||||
t("'''\n'a\n'''", "'a\n");
|
||||
t("'''a\n'a\r\n'''", "a\n'a\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_strings() {
|
||||
fn t(input: &str, val: &str) {
|
||||
let mut t = Tokenizer::new(input);
|
||||
let token = t.next().unwrap().unwrap();
|
||||
assert_eq!(token, Token::String {
|
||||
src: input,
|
||||
val: Cow::Borrowed(val),
|
||||
});
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
|
||||
t(r#""""#, "");
|
||||
t(r#""""""""#, "");
|
||||
t(r#""a""#, "a");
|
||||
t(r#""""a""""#, "a");
|
||||
t(r#""\t""#, "\t");
|
||||
t(r#""\u0000""#, "\0");
|
||||
t(r#""\U00000000""#, "\0");
|
||||
t(r#""\U000A0000""#, "\u{A0000}");
|
||||
t(r#""\\t""#, "\\t");
|
||||
t("\"\"\"\\\n\"\"\"", "");
|
||||
t("\"\"\"\\\n \t \t \\\r\n \t \n \t \r\n\"\"\"", "");
|
||||
t(r#""\r""#, "\r");
|
||||
t(r#""\n""#, "\n");
|
||||
t(r#""\b""#, "\u{8}");
|
||||
t(r#""a\fa""#, "a\u{c}a");
|
||||
t(r#""\"a""#, "\"a");
|
||||
t("\"\"\"\na\"\"\"", "a");
|
||||
t("\"\"\"\n\"\"\"", "");
|
||||
err(r#""\a"#, Error::InvalidEscape(2, 'a'));
|
||||
err("\"\\\n", Error::InvalidEscape(2, '\n'));
|
||||
err("\"\\\r\n", Error::InvalidEscape(2, '\n'));
|
||||
err("\"\\", Error::UnterminatedString(0));
|
||||
err("\"\u{0}", Error::InvalidCharInString(1, '\u{0}'));
|
||||
err(r#""\U00""#, Error::InvalidHexEscape(5, '"'));
|
||||
err(r#""\U00"#, Error::UnterminatedString(0));
|
||||
err(r#""\uD800"#, Error::InvalidEscapeValue(2, 0xd800));
|
||||
err(r#""\UFFFFFFFF"#, Error::InvalidEscapeValue(2, 0xffffffff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keylike() {
|
||||
fn t(input: &str) {
|
||||
let mut t = Tokenizer::new(input);
|
||||
let token = t.next().unwrap().unwrap();
|
||||
assert_eq!(token, Token::Keylike(input));
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
t("foo");
|
||||
t("0bar");
|
||||
t("bar0");
|
||||
t("1234");
|
||||
t("a-b");
|
||||
t("a_B");
|
||||
t("-_-");
|
||||
t("___");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all() {
|
||||
fn t(input: &str, expected: &[Token]) {
|
||||
let mut tokens = Tokenizer::new(input);
|
||||
let mut actual = Vec::new();
|
||||
while let Some(token) = tokens.next().unwrap() {
|
||||
actual.push(token);
|
||||
}
|
||||
for (a, b) in actual.iter().zip(expected) {
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
}
|
||||
|
||||
t(" a ", &[
|
||||
Token::Whitespace(" "),
|
||||
Token::Keylike("a"),
|
||||
Token::Whitespace(" "),
|
||||
]);
|
||||
|
||||
t(" a\t [[]] \t [] {} , . =\n# foo \r\n#foo \n ", &[
|
||||
Token::Whitespace(" "),
|
||||
Token::Keylike("a"),
|
||||
Token::Whitespace("\t "),
|
||||
Token::LeftBracket,
|
||||
Token::LeftBracket,
|
||||
Token::RightBracket,
|
||||
Token::RightBracket,
|
||||
Token::Whitespace(" \t "),
|
||||
Token::LeftBracket,
|
||||
Token::RightBracket,
|
||||
Token::Whitespace(" "),
|
||||
Token::LeftBrace,
|
||||
Token::RightBrace,
|
||||
Token::Whitespace(" "),
|
||||
Token::Comma,
|
||||
Token::Whitespace(" "),
|
||||
Token::Period,
|
||||
Token::Whitespace(" "),
|
||||
Token::Equals,
|
||||
Token::Newline,
|
||||
Token::Comment("# foo "),
|
||||
Token::Newline,
|
||||
Token::Comment("#foo "),
|
||||
Token::Newline,
|
||||
Token::Whitespace(" "),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_cr_bad() {
|
||||
err("\r", Error::Unexpected(0, '\r'));
|
||||
err("'\n", Error::NewlineInString(1));
|
||||
err("'\u{0}", Error::InvalidCharInString(1, '\u{0}'));
|
||||
err("'", Error::UnterminatedString(0));
|
||||
err("\u{0}", Error::Unexpected(0, '\u{0}'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_comment() {
|
||||
let mut t = Tokenizer::new("#\u{0}");
|
||||
t.next().unwrap().unwrap();
|
||||
assert_eq!(t.next(), Err(Error::Unexpected(1, '\u{0}')));
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
}
|
894
src/value.rs
Normal file
894
src/value.rs
Normal file
|
@ -0,0 +1,894 @@
|
|||
//! Definition of a TOML value
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::ops;
|
||||
use std::str::FromStr;
|
||||
use std::vec;
|
||||
|
||||
use serde::ser;
|
||||
use serde::de;
|
||||
|
||||
pub use datetime::{Datetime, DatetimeParseError};
|
||||
use datetime::{DatetimeFromString, SERDE_STRUCT_FIELD_NAME};
|
||||
|
||||
/// Representation of a TOML value.
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum Value {
|
||||
/// Represents a TOML string
|
||||
String(String),
|
||||
/// Represents a TOML integer
|
||||
Integer(i64),
|
||||
/// Represents a TOML float
|
||||
Float(f64),
|
||||
/// Represents a TOML boolean
|
||||
Boolean(bool),
|
||||
/// Represents a TOML datetime
|
||||
Datetime(Datetime),
|
||||
/// Represents a TOML array
|
||||
Array(Array),
|
||||
/// Represents a TOML table
|
||||
Table(Table),
|
||||
}
|
||||
|
||||
/// Type representing a TOML array, payload of the `Value::Array` variant
|
||||
pub type Array = Vec<Value>;
|
||||
|
||||
/// Type representing a TOML table, payload of the `Value::Table` variant
|
||||
pub type Table = BTreeMap<String, Value>;
|
||||
|
||||
impl Value {
|
||||
/// Convert a `T` into `toml::Value` which is an enum that can represent
|
||||
/// any valid TOML data.
|
||||
///
|
||||
/// This conversion can fail if `T`'s implementation of `Serialize` decides to
|
||||
/// fail, or if `T` contains a map with non-string keys.
|
||||
pub fn try_from<T>(value: T) -> Result<Value, ::ser::Error>
|
||||
where T: ser::Serialize,
|
||||
{
|
||||
value.serialize(Serializer)
|
||||
}
|
||||
|
||||
/// Interpret a `toml::Value` as an instance of type `T`.
|
||||
///
|
||||
/// This conversion can fail if the structure of the `Value` does not match the
|
||||
/// structure expected by `T`, for example if `T` is a struct type but the
|
||||
/// `Value` contains something other than a TOML table. It can also fail if the
|
||||
/// structure is correct but `T`'s implementation of `Deserialize` decides that
|
||||
/// something is wrong with the data, for example required struct fields are
|
||||
/// missing from the TOML map or some number is too big to fit in the expected
|
||||
/// primitive type.
|
||||
pub fn try_into<T>(self) -> Result<T, ::de::Error>
|
||||
where T: de::Deserialize,
|
||||
{
|
||||
de::Deserialize::deserialize(self)
|
||||
}
|
||||
|
||||
/// Index into a TOML array or map. A string index can be used to access a
|
||||
/// value in a map, and a usize index can be used to access an element of an
|
||||
/// array.
|
||||
///
|
||||
/// Returns `None` if the type of `self` does not match the type of the
|
||||
/// index, for example if the index is a string and `self` is an array or a
|
||||
/// number. Also returns `None` if the given key does not exist in the map
|
||||
/// or the given index is not within the bounds of the array.
|
||||
pub fn get<I: Index>(&self, index: I) -> Option<&Value> {
|
||||
index.index(self)
|
||||
}
|
||||
|
||||
/// Mutably index into a TOML array or map. A string index can be used to
|
||||
/// access a value in a map, and a usize index can be used to access an
|
||||
/// element of an array.
|
||||
///
|
||||
/// Returns `None` if the type of `self` does not match the type of the
|
||||
/// index, for example if the index is a string and `self` is an array or a
|
||||
/// number. Also returns `None` if the given key does not exist in the map
|
||||
/// or the given index is not within the bounds of the array.
|
||||
pub fn get_mut<I: Index>(&mut self, index: I) -> Option<&mut Value> {
|
||||
index.index_mut(self)
|
||||
}
|
||||
|
||||
/// Extracts the integer value if it is an integer.
|
||||
pub fn as_integer(&self) -> Option<i64> {
|
||||
match *self { Value::Integer(i) => Some(i), _ => None }
|
||||
}
|
||||
|
||||
/// Tests whether this value is an integer
|
||||
pub fn is_integer(&self) -> bool {
|
||||
self.as_integer().is_some()
|
||||
}
|
||||
|
||||
/// Extracts the float value if it is a float.
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
match *self { Value::Float(f) => Some(f), _ => None }
|
||||
}
|
||||
|
||||
/// Tests whether this value is an float
|
||||
pub fn is_float(&self) -> bool {
|
||||
self.as_float().is_some()
|
||||
}
|
||||
|
||||
/// Extracts the boolean value if it is a boolean.
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match *self { Value::Boolean(b) => Some(b), _ => None }
|
||||
}
|
||||
|
||||
/// Tests whether this value is an boolg
|
||||
pub fn is_bool(&self) -> bool {
|
||||
self.as_bool().is_some()
|
||||
}
|
||||
|
||||
/// Extracts the string of this value if it is a string.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
match *self { Value::String(ref s) => Some(&**s), _ => None }
|
||||
}
|
||||
|
||||
/// Tests if this value is a string
|
||||
pub fn is_str(&self) -> bool {
|
||||
self.as_str().is_some()
|
||||
}
|
||||
|
||||
/// Extracts the datetime value if it is a datetime.
|
||||
///
|
||||
/// Note that a parsed TOML value will only contain ISO 8601 dates. An
|
||||
/// example date is:
|
||||
///
|
||||
/// ```notrust
|
||||
/// 1979-05-27T07:32:00Z
|
||||
/// ```
|
||||
pub fn as_datetime(&self) -> Option<&Datetime> {
|
||||
match *self { Value::Datetime(ref s) => Some(s), _ => None }
|
||||
}
|
||||
|
||||
/// Tests whether this value is an datetime
|
||||
pub fn is_datetime(&self) -> bool {
|
||||
self.as_datetime().is_some()
|
||||
}
|
||||
|
||||
/// Extracts the array value if it is an array.
|
||||
pub fn as_array(&self) -> Option<&Vec<Value>> {
|
||||
match *self { Value::Array(ref s) => Some(s), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the array value if it is an array.
|
||||
pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value>> {
|
||||
match *self { Value::Array(ref mut s) => Some(s), _ => None }
|
||||
}
|
||||
|
||||
/// Tests whether this value is an array
|
||||
pub fn is_array(&self) -> bool {
|
||||
self.as_array().is_some()
|
||||
}
|
||||
|
||||
/// Extracts the table value if it is a table.
|
||||
pub fn as_table(&self) -> Option<&Table> {
|
||||
match *self { Value::Table(ref s) => Some(s), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the table value if it is a table.
|
||||
pub fn as_table_mut(&mut self) -> Option<&mut Table> {
|
||||
match *self { Value::Table(ref mut s) => Some(s), _ => None }
|
||||
}
|
||||
|
||||
/// Extracts the table value if it is a table.
|
||||
pub fn is_table(&self) -> bool {
|
||||
self.as_table().is_some()
|
||||
}
|
||||
|
||||
/// Tests whether this and another value have the same type.
|
||||
pub fn same_type(&self, other: &Value) -> bool {
|
||||
match (self, other) {
|
||||
(&Value::String(..), &Value::String(..)) |
|
||||
(&Value::Integer(..), &Value::Integer(..)) |
|
||||
(&Value::Float(..), &Value::Float(..)) |
|
||||
(&Value::Boolean(..), &Value::Boolean(..)) |
|
||||
(&Value::Datetime(..), &Value::Datetime(..)) |
|
||||
(&Value::Array(..), &Value::Array(..)) |
|
||||
(&Value::Table(..), &Value::Table(..)) => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a human-readable representation of the type of this value.
|
||||
pub fn type_str(&self) -> &'static str {
|
||||
match *self {
|
||||
Value::String(..) => "string",
|
||||
Value::Integer(..) => "integer",
|
||||
Value::Float(..) => "float",
|
||||
Value::Boolean(..) => "boolean",
|
||||
Value::Datetime(..) => "datetime",
|
||||
Value::Array(..) => "array",
|
||||
Value::Table(..) => "table",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ops::Index<I> for Value where I: Index {
|
||||
type Output = Value;
|
||||
|
||||
fn index(&self, index: I) -> &Value {
|
||||
self.get(index).expect("index not found")
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ops::IndexMut<I> for Value where I: Index {
|
||||
fn index_mut(&mut self, index: I) -> &mut Value {
|
||||
self.get_mut(index).expect("index not found")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Value {
|
||||
fn from(val: String) -> Value {
|
||||
Value::String(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Value {
|
||||
fn from(val: i64) -> Value {
|
||||
Value::Integer(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Value {
|
||||
fn from(val: f64) -> Value {
|
||||
Value::Float(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Value {
|
||||
fn from(val: bool) -> Value {
|
||||
Value::Boolean(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Array> for Value {
|
||||
fn from(val: Array) -> Value {
|
||||
Value::Array(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Table> for Value {
|
||||
fn from(val: Table) -> Value {
|
||||
Value::Table(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Datetime> for Value {
|
||||
fn from(val: Datetime) -> Value {
|
||||
Value::Datetime(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Types that can be used to index a `toml::Value`
|
||||
///
|
||||
/// Currently this is implemented for `usize` to index arrays and `str` to index
|
||||
/// tables.
|
||||
///
|
||||
/// This trait is sealed and not intended for implementation outside of the
|
||||
/// `toml` crate.
|
||||
pub trait Index: 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 {
|
||||
Value::Array(ref a) => a.get(*self),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> {
|
||||
match *val {
|
||||
Value::Array(ref mut a) => a.get_mut(*self),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index for str {
|
||||
fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> {
|
||||
match *val {
|
||||
Value::Table(ref a) => a.get(self),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> {
|
||||
match *val {
|
||||
Value::Table(ref mut a) => a.get_mut(self),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index for String {
|
||||
fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> {
|
||||
self[..].index(val)
|
||||
}
|
||||
|
||||
fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> {
|
||||
self[..].index_mut(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T: ?Sized> Index for &'s T where T: Index {
|
||||
fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> {
|
||||
(**self).index(val)
|
||||
}
|
||||
|
||||
fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> {
|
||||
(**self).index_mut(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
::ser::to_string(self).unwrap().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Value {
|
||||
type Err = ::de::Error;
|
||||
fn from_str(s: &str) -> Result<Value, Self::Err> {
|
||||
::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Serialize for Value {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: ser::Serializer
|
||||
{
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
match *self {
|
||||
Value::String(ref s) => serializer.serialize_str(s),
|
||||
Value::Integer(i) => serializer.serialize_i64(i),
|
||||
Value::Float(f) => serializer.serialize_f64(f),
|
||||
Value::Boolean(b) => serializer.serialize_bool(b),
|
||||
Value::Datetime(ref s) => s.serialize(serializer),
|
||||
Value::Array(ref a) => a.serialize(serializer),
|
||||
Value::Table(ref t) => {
|
||||
let mut map = serializer.serialize_map(Some(t.len()))?;
|
||||
// Be sure to visit non-tables first (and also non
|
||||
// array-of-tables) as all keys must be emitted first.
|
||||
for (k, v) in t {
|
||||
if !v.is_array() && !v.is_table() {
|
||||
map.serialize_key(k)?;
|
||||
map.serialize_value(v)?;
|
||||
}
|
||||
}
|
||||
for (k, v) in t {
|
||||
if v.is_array() {
|
||||
map.serialize_key(k)?;
|
||||
map.serialize_value(v)?;
|
||||
}
|
||||
}
|
||||
for (k, v) in t {
|
||||
if v.is_table() {
|
||||
map.serialize_key(k)?;
|
||||
map.serialize_value(v)?;
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl de::Deserialize for Value {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Value, D::Error>
|
||||
where D: de::Deserializer
|
||||
{
|
||||
struct ValueVisitor;
|
||||
|
||||
impl de::Visitor for ValueVisitor {
|
||||
type Value = Value;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("any valid TOML value")
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, value: bool) -> Result<Value, E> {
|
||||
Ok(Value::Boolean(value))
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, value: i64) -> Result<Value, E> {
|
||||
Ok(Value::Integer(value))
|
||||
}
|
||||
|
||||
fn visit_f64<E>(self, value: f64) -> Result<Value, E> {
|
||||
Ok(Value::Float(value))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Value, E> {
|
||||
Ok(Value::String(value.into()))
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Value, E> {
|
||||
Ok(Value::String(value))
|
||||
}
|
||||
|
||||
fn visit_some<D>(self, deserializer: D) -> Result<Value, D::Error>
|
||||
where D: de::Deserializer,
|
||||
{
|
||||
de::Deserialize::deserialize(deserializer)
|
||||
}
|
||||
|
||||
fn visit_seq<V>(self, visitor: V) -> Result<Value, V::Error>
|
||||
where V: de::SeqVisitor
|
||||
{
|
||||
let values = de::impls::VecVisitor::new().visit_seq(visitor)?;
|
||||
Ok(Value::Array(values))
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut visitor: V) -> Result<Value, V::Error>
|
||||
where V: de::MapVisitor
|
||||
{
|
||||
let mut key = String::new();
|
||||
let datetime = visitor.visit_key_seed(DatetimeOrTable {
|
||||
key: &mut key,
|
||||
})?;
|
||||
match datetime {
|
||||
Some(true) => {
|
||||
let date: DatetimeFromString = visitor.visit_value()?;
|
||||
return Ok(Value::Datetime(date.value))
|
||||
}
|
||||
None => return Ok(Value::Table(BTreeMap::new())),
|
||||
Some(false) => {}
|
||||
}
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(key, visitor.visit_value()?);
|
||||
while let Some(key) = visitor.visit_key()? {
|
||||
if map.contains_key(&key) {
|
||||
let msg = format!("duplicate key: `{}`", key);
|
||||
return Err(de::Error::custom(msg))
|
||||
}
|
||||
map.insert(key, visitor.visit_value()?);
|
||||
}
|
||||
Ok(Value::Table(map))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize(ValueVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl de::Deserializer for Value {
|
||||
type Error = ::de::Error;
|
||||
|
||||
fn deserialize<V>(self, visitor: V) -> Result<V::Value, ::de::Error>
|
||||
where V: de::Visitor,
|
||||
{
|
||||
match self {
|
||||
Value::Boolean(v) => visitor.visit_bool(v),
|
||||
Value::Integer(n) => visitor.visit_i64(n),
|
||||
Value::Float(n) => visitor.visit_f64(n),
|
||||
Value::String(v) => visitor.visit_string(v),
|
||||
Value::Datetime(v) => visitor.visit_string(v.to_string()),
|
||||
Value::Array(v) => {
|
||||
let len = v.len();
|
||||
let mut deserializer = SeqDeserializer::new(v);
|
||||
let seq = visitor.visit_seq(&mut deserializer)?;
|
||||
let remaining = deserializer.iter.len();
|
||||
if remaining == 0 {
|
||||
Ok(seq)
|
||||
} else {
|
||||
Err(de::Error::invalid_length(len, &"fewer elements in array"))
|
||||
}
|
||||
}
|
||||
Value::Table(v) => {
|
||||
let len = v.len();
|
||||
let mut deserializer = MapDeserializer::new(v);
|
||||
let map = visitor.visit_map(&mut deserializer)?;
|
||||
let remaining = deserializer.iter.len();
|
||||
if remaining == 0 {
|
||||
Ok(map)
|
||||
} else {
|
||||
Err(de::Error::invalid_length(len, &"fewer elements in map"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `None` is interpreted as a missing field so be sure to implement `Some`
|
||||
// as a present field.
|
||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, ::de::Error>
|
||||
where V: de::Visitor
|
||||
{
|
||||
visitor.visit_some(self)
|
||||
}
|
||||
|
||||
forward_to_deserialize! {
|
||||
bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq
|
||||
seq_fixed_size bytes byte_buf map unit_struct tuple_struct struct
|
||||
struct_field tuple ignored_any enum newtype_struct
|
||||
}
|
||||
}
|
||||
|
||||
struct SeqDeserializer {
|
||||
iter: vec::IntoIter<Value>,
|
||||
}
|
||||
|
||||
impl SeqDeserializer {
|
||||
fn new(vec: Vec<Value>) -> Self {
|
||||
SeqDeserializer {
|
||||
iter: vec.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl de::SeqVisitor for SeqDeserializer {
|
||||
type Error = ::de::Error;
|
||||
|
||||
fn visit_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, ::de::Error>
|
||||
where T: de::DeserializeSeed,
|
||||
{
|
||||
match self.iter.next() {
|
||||
Some(value) => seed.deserialize(value).map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
struct MapDeserializer {
|
||||
iter: <BTreeMap<String, Value> as IntoIterator>::IntoIter,
|
||||
value: Option<(String, Value)>,
|
||||
}
|
||||
|
||||
impl MapDeserializer {
|
||||
fn new(map: BTreeMap<String, Value>) -> Self {
|
||||
MapDeserializer {
|
||||
iter: map.into_iter(),
|
||||
value: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl de::MapVisitor for MapDeserializer {
|
||||
type Error = ::de::Error;
|
||||
|
||||
fn visit_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, ::de::Error>
|
||||
where T: de::DeserializeSeed,
|
||||
{
|
||||
match self.iter.next() {
|
||||
Some((key, value)) => {
|
||||
self.value = Some((key.clone(), value));
|
||||
seed.deserialize(Value::String(key)).map(Some)
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_value_seed<T>(&mut self, seed: T) -> Result<T::Value, ::de::Error>
|
||||
where T: de::DeserializeSeed,
|
||||
{
|
||||
let (key, res) = match self.value.take() {
|
||||
Some((key, value)) => (key, seed.deserialize(value)),
|
||||
None => return Err(de::Error::custom("value is missing")),
|
||||
};
|
||||
res.map_err(|mut error| {
|
||||
error.add_key_context(&key);
|
||||
error
|
||||
})
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
struct Serializer;
|
||||
|
||||
impl ser::Serializer for Serializer {
|
||||
type Ok = Value;
|
||||
type Error = ::ser::Error;
|
||||
|
||||
type SerializeSeq = SerializeVec;
|
||||
type SerializeTuple = ser::Impossible<Value, ::ser::Error>;
|
||||
type SerializeTupleStruct = ser::Impossible<Value, ::ser::Error>;
|
||||
type SerializeTupleVariant = ser::Impossible<Value, ::ser::Error>;
|
||||
type SerializeMap = SerializeMap;
|
||||
type SerializeStruct = SerializeMap;
|
||||
type SerializeStructVariant = ser::Impossible<Value, ::ser::Error>;
|
||||
|
||||
fn serialize_bool(self, value: bool) -> Result<Value, ::ser::Error> {
|
||||
Ok(Value::Boolean(value))
|
||||
}
|
||||
|
||||
fn serialize_i8(self, value: i8) -> Result<Value, ::ser::Error> {
|
||||
self.serialize_i64(value.into())
|
||||
}
|
||||
|
||||
fn serialize_i16(self, value: i16) -> Result<Value, ::ser::Error> {
|
||||
self.serialize_i64(value.into())
|
||||
}
|
||||
|
||||
fn serialize_i32(self, value: i32) -> Result<Value, ::ser::Error> {
|
||||
self.serialize_i64(value.into())
|
||||
}
|
||||
|
||||
fn serialize_i64(self, value: i64) -> Result<Value, ::ser::Error> {
|
||||
Ok(Value::Integer(value.into()))
|
||||
}
|
||||
|
||||
fn serialize_u8(self, value: u8) -> Result<Value, ::ser::Error> {
|
||||
self.serialize_i64(value.into())
|
||||
}
|
||||
|
||||
fn serialize_u16(self, value: u16) -> Result<Value, ::ser::Error> {
|
||||
self.serialize_i64(value.into())
|
||||
}
|
||||
|
||||
fn serialize_u32(self, value: u32) -> Result<Value, ::ser::Error> {
|
||||
self.serialize_i64(value.into())
|
||||
}
|
||||
|
||||
fn serialize_u64(self, value: u64) -> Result<Value, ::ser::Error> {
|
||||
if value <= i64::max_value() as u64 {
|
||||
self.serialize_i64(value as i64)
|
||||
} else {
|
||||
Err(ser::Error::custom("u64 value was too large"))
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_f32(self, value: f32) -> Result<Value, ::ser::Error> {
|
||||
self.serialize_f64(value.into())
|
||||
}
|
||||
|
||||
fn serialize_f64(self, value: f64) -> Result<Value, ::ser::Error> {
|
||||
Ok(Value::Float(value))
|
||||
}
|
||||
|
||||
fn serialize_char(self, value: char) -> Result<Value, ::ser::Error> {
|
||||
let mut s = String::new();
|
||||
s.push(value);
|
||||
self.serialize_str(&s)
|
||||
}
|
||||
|
||||
fn serialize_str(self, value: &str) -> Result<Value, ::ser::Error> {
|
||||
Ok(Value::String(value.to_owned()))
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, value: &[u8]) -> Result<Value, ::ser::Error> {
|
||||
let vec = value.iter().map(|&b| Value::Integer(b.into())).collect();
|
||||
Ok(Value::Array(vec))
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Value, ::ser::Error> {
|
||||
Err(::ser::Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str)
|
||||
-> Result<Value, ::ser::Error> {
|
||||
Err(::ser::Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(self,
|
||||
_name: &'static str,
|
||||
_variant_index: usize,
|
||||
_variant: &'static str)
|
||||
-> Result<Value, ::ser::Error> {
|
||||
Err(::ser::Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T: ?Sized>(self,
|
||||
_name: &'static str,
|
||||
value: &T)
|
||||
-> Result<Value, ::ser::Error>
|
||||
where T: ser::Serialize,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T: ?Sized>(self,
|
||||
_name: &'static str,
|
||||
_variant_index: usize,
|
||||
_variant: &'static str,
|
||||
_value: &T)
|
||||
-> Result<Value, ::ser::Error>
|
||||
where T: ser::Serialize,
|
||||
{
|
||||
Err(::ser::Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Value, ::ser::Error> {
|
||||
Err(::ser::Error::UnsupportedNone)
|
||||
}
|
||||
|
||||
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Value, ::ser::Error>
|
||||
where T: ser::Serialize,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_seq(self, len: Option<usize>)
|
||||
-> Result<Self::SerializeSeq, ::ser::Error>
|
||||
{
|
||||
Ok(SerializeVec {
|
||||
vec: Vec::with_capacity(len.unwrap_or(0))
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_seq_fixed_size(self, size: usize)
|
||||
-> Result<Self::SerializeSeq, ::ser::Error> {
|
||||
self.serialize_seq(Some(size))
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, ::ser::Error> {
|
||||
Err(::ser::Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(self, _name: &'static str, _len: usize)
|
||||
-> Result<Self::SerializeTupleStruct, ::ser::Error> {
|
||||
Err(::ser::Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(self,
|
||||
_name: &'static str,
|
||||
_variant_index: usize,
|
||||
_variant: &'static str,
|
||||
_len: usize)
|
||||
-> Result<Self::SerializeTupleVariant, ::ser::Error>
|
||||
{
|
||||
Err(::ser::Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>)
|
||||
-> Result<Self::SerializeMap, ::ser::Error>
|
||||
{
|
||||
Ok(SerializeMap {
|
||||
map: BTreeMap::new(),
|
||||
next_key: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_struct(self, _name: &'static str, len: usize)
|
||||
-> Result<Self::SerializeStruct, ::ser::Error> {
|
||||
self.serialize_map(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(self,
|
||||
_name: &'static str,
|
||||
_variant_index: usize,
|
||||
_variant: &'static str,
|
||||
_len: usize)
|
||||
-> Result<Self::SerializeStructVariant, ::ser::Error>
|
||||
{
|
||||
Err(::ser::Error::UnsupportedType)
|
||||
}
|
||||
}
|
||||
|
||||
struct SerializeVec {
|
||||
vec: Vec<Value>,
|
||||
}
|
||||
|
||||
struct SerializeMap {
|
||||
map: BTreeMap<String, Value>,
|
||||
next_key: Option<String>,
|
||||
}
|
||||
|
||||
impl ser::SerializeSeq for SerializeVec {
|
||||
type Ok = Value;
|
||||
type Error = ::ser::Error;
|
||||
|
||||
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), ::ser::Error>
|
||||
where T: ser::Serialize
|
||||
{
|
||||
self.vec.push(Value::try_from(value)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Value, ::ser::Error> {
|
||||
Ok(Value::Array(self.vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::SerializeMap for SerializeMap {
|
||||
type Ok = Value;
|
||||
type Error = ::ser::Error;
|
||||
|
||||
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), ::ser::Error>
|
||||
where T: ser::Serialize
|
||||
{
|
||||
match Value::try_from(key)? {
|
||||
Value::String(s) => self.next_key = Some(s),
|
||||
_ => return Err(::ser::Error::KeyNotString),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), ::ser::Error>
|
||||
where T: ser::Serialize
|
||||
{
|
||||
let key = self.next_key.take();
|
||||
let key = key.expect("serialize_value called before serialize_key");
|
||||
match Value::try_from(value) {
|
||||
Ok(value) => { self.map.insert(key, value); }
|
||||
Err(::ser::Error::UnsupportedNone) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Value, ::ser::Error> {
|
||||
Ok(Value::Table(self.map))
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::SerializeStruct for SerializeMap {
|
||||
type Ok = Value;
|
||||
type Error = ::ser::Error;
|
||||
|
||||
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), ::ser::Error>
|
||||
where T: ser::Serialize
|
||||
{
|
||||
try!(ser::SerializeMap::serialize_key(self, key));
|
||||
ser::SerializeMap::serialize_value(self, value)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Value, ::ser::Error> {
|
||||
ser::SerializeMap::end(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct DatetimeOrTable<'a> {
|
||||
key: &'a mut String,
|
||||
}
|
||||
|
||||
impl<'a> de::DeserializeSeed for DatetimeOrTable<'a> {
|
||||
type Value = bool;
|
||||
|
||||
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where D: de::Deserializer
|
||||
{
|
||||
deserializer.deserialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> de::Visitor for DatetimeOrTable<'a> {
|
||||
type Value = bool;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a string key")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, s: &str) -> Result<bool, E>
|
||||
where E: de::Error,
|
||||
{
|
||||
if s == SERDE_STRUCT_FIELD_NAME {
|
||||
Ok(true)
|
||||
} else {
|
||||
self.key.push_str(s);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, s: String) -> Result<bool, E>
|
||||
where E: de::Error,
|
||||
{
|
||||
if s == SERDE_STRUCT_FIELD_NAME {
|
||||
Ok(true)
|
||||
} else {
|
||||
*self.key = s;
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -132,81 +132,3 @@ impl<'a> fmt::Display for Key<'a> {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(warnings)]
|
||||
mod tests {
|
||||
use Value;
|
||||
use Value::{String, Integer, Float, Boolean, Datetime, Array, Table};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
macro_rules! map( ($($k:expr => $v:expr),*) => ({
|
||||
let mut _m = BTreeMap::new();
|
||||
$(_m.insert($k.to_string(), $v);)*
|
||||
_m
|
||||
}) );
|
||||
|
||||
#[test]
|
||||
fn simple_show() {
|
||||
assert_eq!(String("foo".to_string()).to_string(),
|
||||
"\"foo\"");
|
||||
assert_eq!(Integer(10).to_string(),
|
||||
"10");
|
||||
assert_eq!(Float(10.0).to_string(),
|
||||
"10.0");
|
||||
assert_eq!(Float(2.4).to_string(),
|
||||
"2.4");
|
||||
assert_eq!(Boolean(true).to_string(),
|
||||
"true");
|
||||
assert_eq!(Datetime("test".to_string()).to_string(),
|
||||
"test");
|
||||
assert_eq!(Array(vec![]).to_string(),
|
||||
"[]");
|
||||
assert_eq!(Array(vec![Integer(1), Integer(2)]).to_string(),
|
||||
"[1, 2]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table() {
|
||||
assert_eq!(Table(map! { }).to_string(),
|
||||
"");
|
||||
assert_eq!(Table(map! { "test" => Integer(2) }).to_string(),
|
||||
"test = 2\n");
|
||||
assert_eq!(Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Table(map! {
|
||||
"test" => String("wut".to_string())
|
||||
})
|
||||
}).to_string(),
|
||||
"test = 2\n\
|
||||
\n\
|
||||
[test2]\n\
|
||||
test = \"wut\"\n");
|
||||
assert_eq!(Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Table(map! {
|
||||
"test" => String("wut".to_string())
|
||||
})
|
||||
}).to_string(),
|
||||
"test = 2\n\
|
||||
\n\
|
||||
[test2]\n\
|
||||
test = \"wut\"\n");
|
||||
assert_eq!(Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Array(vec![Table(map! {
|
||||
"test" => String("wut".to_string())
|
||||
})])
|
||||
}).to_string(),
|
||||
"test = 2\n\
|
||||
\n\
|
||||
[[test2]]\n\
|
||||
test = \"wut\"\n");
|
||||
assert_eq!(Table(map! {
|
||||
"foo.bar" => Integer(2),
|
||||
"foo\"bar" => Integer(2)
|
||||
}).to_string(),
|
||||
"\"foo\\\"bar\" = 2\n\
|
||||
\"foo.bar\" = 2\n");
|
||||
}
|
||||
}
|
58
tests/datetime.rs
Normal file
58
tests/datetime.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
extern crate toml;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use toml::Value;
|
||||
|
||||
#[test]
|
||||
fn times() {
|
||||
fn good(s: &str) {
|
||||
let to_parse = format!("foo = {}", s);
|
||||
let value = Value::from_str(&to_parse).unwrap();
|
||||
assert_eq!(value["foo"].as_datetime().unwrap().to_string(), s);
|
||||
}
|
||||
|
||||
good("1997-09-09T09:09:09Z");
|
||||
good("1997-09-09T09:09:09+09:09");
|
||||
good("1997-09-09T09:09:09-09:09");
|
||||
good("1997-09-09T09:09:09");
|
||||
good("1997-09-09");
|
||||
good("09:09:09");
|
||||
good("1997-09-09T09:09:09.09Z");
|
||||
good("1997-09-09T09:09:09.09+09:09");
|
||||
good("1997-09-09T09:09:09.09-09:09");
|
||||
good("1997-09-09T09:09:09.09");
|
||||
good("09:09:09.09");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_times() {
|
||||
fn bad(s: &str) {
|
||||
let to_parse = format!("foo = {}", s);
|
||||
assert!(Value::from_str(&to_parse).is_err());
|
||||
}
|
||||
|
||||
bad("199-09-09");
|
||||
bad("199709-09");
|
||||
bad("1997-9-09");
|
||||
bad("1997-09-9");
|
||||
bad("1997-09-0909:09:09");
|
||||
bad("1997-09-09T09:09:09.");
|
||||
bad("T");
|
||||
bad("T.");
|
||||
bad("TZ");
|
||||
bad("1997-09-09T09:09:09.09+");
|
||||
bad("1997-09-09T09:09:09.09+09");
|
||||
bad("1997-09-09T09:09:09.09+09:9");
|
||||
bad("1997-09-09T09:09:09.09+0909");
|
||||
bad("1997-09-09T09:09:09.09-");
|
||||
bad("1997-09-09T09:09:09.09-09");
|
||||
bad("1997-09-09T09:09:09.09-09:9");
|
||||
bad("1997-09-09T09:09:09.09-0909");
|
||||
|
||||
bad("1997-00-09T09:09:09.09Z");
|
||||
bad("1997-09-00T09:09:09.09Z");
|
||||
bad("1997-09-09T30:09:09.09Z");
|
||||
bad("1997-09-09T12:69:09.09Z");
|
||||
bad("1997-09-09T12:09:69.09Z");
|
||||
}
|
97
tests/display.rs
Normal file
97
tests/display.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
extern crate toml;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use toml::Value::{String, Integer, Float, Boolean, Array, Table};
|
||||
|
||||
macro_rules! map( ($($k:expr => $v:expr),*) => ({
|
||||
let mut _m = BTreeMap::new();
|
||||
$(_m.insert($k.to_string(), $v);)*
|
||||
_m
|
||||
}) );
|
||||
|
||||
#[test]
|
||||
fn simple_show() {
|
||||
assert_eq!(String("foo".to_string()).to_string(),
|
||||
"\"foo\"");
|
||||
assert_eq!(Integer(10).to_string(),
|
||||
"10");
|
||||
assert_eq!(Float(10.0).to_string(),
|
||||
"10.0");
|
||||
assert_eq!(Float(2.4).to_string(),
|
||||
"2.4");
|
||||
assert_eq!(Boolean(true).to_string(),
|
||||
"true");
|
||||
assert_eq!(Array(vec![]).to_string(),
|
||||
"[]");
|
||||
assert_eq!(Array(vec![Integer(1), Integer(2)]).to_string(),
|
||||
"[1, 2]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table() {
|
||||
assert_eq!(Table(map! { }).to_string(),
|
||||
"");
|
||||
assert_eq!(Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Integer(3) }).to_string(),
|
||||
"test = 2\ntest2 = 3\n");
|
||||
assert_eq!(Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Table(map! {
|
||||
"test" => String("wut".to_string())
|
||||
})
|
||||
}).to_string(),
|
||||
"test = 2\n\
|
||||
\n\
|
||||
[test2]\n\
|
||||
test = \"wut\"\n");
|
||||
assert_eq!(Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Table(map! {
|
||||
"test" => String("wut".to_string())
|
||||
})
|
||||
}).to_string(),
|
||||
"test = 2\n\
|
||||
\n\
|
||||
[test2]\n\
|
||||
test = \"wut\"\n");
|
||||
assert_eq!(Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Array(vec![Table(map! {
|
||||
"test" => String("wut".to_string())
|
||||
})])
|
||||
}).to_string(),
|
||||
"test = 2\n\
|
||||
\n\
|
||||
[[test2]]\n\
|
||||
test = \"wut\"\n");
|
||||
assert_eq!(Table(map! {
|
||||
"foo.bar" => Integer(2),
|
||||
"foo\"bar" => Integer(2)
|
||||
}).to_string(),
|
||||
"\"foo\\\"bar\" = 2\n\
|
||||
\"foo.bar\" = 2\n");
|
||||
assert_eq!(Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Array(vec![Table(map! {
|
||||
"test" => Array(vec![Integer(2)])
|
||||
})])
|
||||
}).to_string(),
|
||||
"test = 2\n\
|
||||
\n\
|
||||
[[test2]]\n\
|
||||
test = [2]\n");
|
||||
let table = Table(map! {
|
||||
"test" => Integer(2),
|
||||
"test2" => Array(vec![Table(map! {
|
||||
"test" => Array(vec![Array(vec![Integer(2), Integer(3)]),
|
||||
Array(vec![String("foo".to_string()), String("bar".to_string())])])
|
||||
})])
|
||||
});
|
||||
assert_eq!(table.to_string(),
|
||||
"test = 2\n\
|
||||
\n\
|
||||
[[test2]]\n\
|
||||
test = [[2, 3], [\"foo\", \"bar\"]]\n");
|
||||
}
|
|
@ -1,19 +1,22 @@
|
|||
extern crate rustc_serialize;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate toml;
|
||||
use toml::encode_str;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
||||
use toml::to_string;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct User {
|
||||
pub name: String,
|
||||
pub surname: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct Users {
|
||||
pub user: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct TwoUsers {
|
||||
pub user0: User,
|
||||
pub user1: User,
|
||||
|
@ -21,7 +24,7 @@ struct TwoUsers {
|
|||
|
||||
#[test]
|
||||
fn no_unnecessary_newlines_array() {
|
||||
assert!(!encode_str(&Users {
|
||||
assert!(!to_string(&Users {
|
||||
user: vec![
|
||||
User {
|
||||
name: "John".to_string(),
|
||||
|
@ -32,13 +35,13 @@ fn no_unnecessary_newlines_array() {
|
|||
surname: "Dough".to_string(),
|
||||
},
|
||||
],
|
||||
})
|
||||
}).unwrap()
|
||||
.starts_with("\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_unnecessary_newlines_table() {
|
||||
assert!(!encode_str(&TwoUsers {
|
||||
assert!(!to_string(&TwoUsers {
|
||||
user0: User {
|
||||
name: "John".to_string(),
|
||||
surname: "Doe".to_string(),
|
||||
|
@ -47,6 +50,6 @@ fn no_unnecessary_newlines_table() {
|
|||
name: "Jane".to_string(),
|
||||
surname: "Dough".to_string(),
|
||||
},
|
||||
})
|
||||
}).unwrap()
|
||||
.starts_with("\n"));
|
||||
}
|
||||
|
|
12
tests/invalid-misc.rs
Normal file
12
tests/invalid-misc.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
extern crate toml;
|
||||
|
||||
#[test]
|
||||
fn bad() {
|
||||
fn bad(s: &str) {
|
||||
assert!(s.parse::<toml::Value>().is_err());
|
||||
}
|
||||
|
||||
bad("a = 01");
|
||||
bad("a = 1__1");
|
||||
bad("a = 1_");
|
||||
}
|
|
@ -1,17 +1,9 @@
|
|||
extern crate toml;
|
||||
|
||||
use toml::{Parser};
|
||||
|
||||
fn run(toml: &str) {
|
||||
let mut p = Parser::new(toml);
|
||||
let table = p.parse();
|
||||
assert!(table.is_none());
|
||||
assert!(p.errors.len() > 0);
|
||||
|
||||
// test Parser::to_linecol with the generated error offsets
|
||||
for error in &p.errors {
|
||||
p.to_linecol(error.lo);
|
||||
p.to_linecol(error.hi);
|
||||
println!("test if invalid:\n{}", toml);
|
||||
if let Ok(e) = toml.parse::<toml::Value>() {
|
||||
panic!("parsed to: {:#?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,8 +24,6 @@ test!(datetime_malformed_no_secs,
|
|||
include_str!("invalid/datetime-malformed-no-secs.toml"));
|
||||
test!(datetime_malformed_no_t,
|
||||
include_str!("invalid/datetime-malformed-no-t.toml"));
|
||||
test!(datetime_malformed_no_z,
|
||||
include_str!("invalid/datetime-malformed-no-z.toml"));
|
||||
test!(datetime_malformed_with_milli,
|
||||
include_str!("invalid/datetime-malformed-with-milli.toml"));
|
||||
test!(duplicate_keys,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
no-z = 1987-07-05T17:45:00
|
495
tests/parser.rs
Normal file
495
tests/parser.rs
Normal file
|
@ -0,0 +1,495 @@
|
|||
extern crate toml;
|
||||
|
||||
use toml::Value;
|
||||
|
||||
macro_rules! bad {
|
||||
($s:expr, $msg:expr) => ({
|
||||
match $s.parse::<Value>() {
|
||||
Ok(s) => panic!("successfully parsed as {}", s),
|
||||
Err(e) => {
|
||||
let e = e.to_string();
|
||||
assert!(e.contains($msg), "error: {}", e);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crlf() {
|
||||
"\
|
||||
[project]\r\n\
|
||||
\r\n\
|
||||
name = \"splay\"\r\n\
|
||||
version = \"0.1.0\"\r\n\
|
||||
authors = [\"alex@crichton.co\"]\r\n\
|
||||
\r\n\
|
||||
[[lib]]\r\n\
|
||||
\r\n\
|
||||
path = \"lib.rs\"\r\n\
|
||||
name = \"splay\"\r\n\
|
||||
description = \"\"\"\
|
||||
A Rust implementation of a TAR file reader and writer. This library does not\r\n\
|
||||
currently handle compression, but it is abstract over all I/O readers and\r\n\
|
||||
writers. Additionally, great lengths are taken to ensure that the entire\r\n\
|
||||
contents are never required to be entirely resident in memory all at once.\r\n\
|
||||
\"\"\"\
|
||||
".parse::<Value>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fun_with_strings() {
|
||||
let table = r#"
|
||||
bar = "\U00000000"
|
||||
key1 = "One\nTwo"
|
||||
key2 = """One\nTwo"""
|
||||
key3 = """
|
||||
One
|
||||
Two"""
|
||||
|
||||
key4 = "The quick brown fox jumps over the lazy dog."
|
||||
key5 = """
|
||||
The quick brown \
|
||||
|
||||
|
||||
fox jumps over \
|
||||
the lazy dog."""
|
||||
key6 = """\
|
||||
The quick brown \
|
||||
fox jumps over \
|
||||
the lazy dog.\
|
||||
"""
|
||||
# What you see is what you get.
|
||||
winpath = 'C:\Users\nodejs\templates'
|
||||
winpath2 = '\\ServerX\admin$\system32\'
|
||||
quoted = 'Tom "Dubs" Preston-Werner'
|
||||
regex = '<\i\c*\s*>'
|
||||
|
||||
regex2 = '''I [dw]on't need \d{2} apples'''
|
||||
lines = '''
|
||||
The first newline is
|
||||
trimmed in raw strings.
|
||||
All other whitespace
|
||||
is preserved.
|
||||
'''
|
||||
"#.parse::<Value>().unwrap();
|
||||
assert_eq!(table["bar"].as_str(), Some("\0"));
|
||||
assert_eq!(table["key1"].as_str(), Some("One\nTwo"));
|
||||
assert_eq!(table["key2"].as_str(), Some("One\nTwo"));
|
||||
assert_eq!(table["key3"].as_str(), Some("One\nTwo"));
|
||||
|
||||
let msg = "The quick brown fox jumps over the lazy dog.";
|
||||
assert_eq!(table["key4"].as_str(), Some(msg));
|
||||
assert_eq!(table["key5"].as_str(), Some(msg));
|
||||
assert_eq!(table["key6"].as_str(), Some(msg));
|
||||
|
||||
assert_eq!(table["winpath"].as_str(), Some(r"C:\Users\nodejs\templates"));
|
||||
assert_eq!(table["winpath2"].as_str(), Some(r"\\ServerX\admin$\system32\"));
|
||||
assert_eq!(table["quoted"].as_str(), Some(r#"Tom "Dubs" Preston-Werner"#));
|
||||
assert_eq!(table["regex"].as_str(), Some(r"<\i\c*\s*>"));
|
||||
assert_eq!(table["regex2"].as_str(), Some(r"I [dw]on't need \d{2} apples"));
|
||||
assert_eq!(table["lines"].as_str(),
|
||||
Some("The first newline is\n\
|
||||
trimmed in raw strings.\n\
|
||||
All other whitespace\n\
|
||||
is preserved.\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tables_in_arrays() {
|
||||
let table = r#"
|
||||
[[foo]]
|
||||
#…
|
||||
[foo.bar]
|
||||
#…
|
||||
|
||||
[[foo]] # ...
|
||||
#…
|
||||
[foo.bar]
|
||||
#...
|
||||
"#.parse::<Value>().unwrap();
|
||||
table["foo"][0]["bar"].as_table().unwrap();
|
||||
table["foo"][1]["bar"].as_table().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_table() {
|
||||
let table = r#"
|
||||
[foo]"#.parse::<Value>().unwrap();
|
||||
table["foo"].as_table().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fruit() {
|
||||
let table = r#"
|
||||
[[fruit]]
|
||||
name = "apple"
|
||||
|
||||
[fruit.physical]
|
||||
color = "red"
|
||||
shape = "round"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "red delicious"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "granny smith"
|
||||
|
||||
[[fruit]]
|
||||
name = "banana"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "plantain"
|
||||
"#.parse::<Value>().unwrap();
|
||||
assert_eq!(table["fruit"][0]["name"].as_str(), Some("apple"));
|
||||
assert_eq!(table["fruit"][0]["physical"]["color"].as_str(), Some("red"));
|
||||
assert_eq!(table["fruit"][0]["physical"]["shape"].as_str(), Some("round"));
|
||||
assert_eq!(table["fruit"][0]["variety"][0]["name"].as_str(), Some("red delicious"));
|
||||
assert_eq!(table["fruit"][0]["variety"][1]["name"].as_str(), Some("granny smith"));
|
||||
assert_eq!(table["fruit"][1]["name"].as_str(), Some("banana"));
|
||||
assert_eq!(table["fruit"][1]["variety"][0]["name"].as_str(), Some("plantain"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stray_cr() {
|
||||
"\r".parse::<Value>().unwrap_err();
|
||||
"a = [ \r ]".parse::<Value>().unwrap_err();
|
||||
"a = \"\"\"\r\"\"\"".parse::<Value>().unwrap_err();
|
||||
"a = \"\"\"\\ \r \"\"\"".parse::<Value>().unwrap_err();
|
||||
"a = '''\r'''".parse::<Value>().unwrap_err();
|
||||
"a = '\r'".parse::<Value>().unwrap_err();
|
||||
"a = \"\r\"".parse::<Value>().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blank_literal_string() {
|
||||
let table = "foo = ''".parse::<Value>().unwrap();
|
||||
assert_eq!(table["foo"].as_str(), Some(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_blank() {
|
||||
let table = "foo = \"\"\"\n\n\n\"\"\"".parse::<Value>().unwrap();
|
||||
assert_eq!(table["foo"].as_str(), Some("\n\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_eats_crlf() {
|
||||
let table = "
|
||||
foo = \"\"\"\\\r\n\"\"\"
|
||||
bar = \"\"\"\\\r\n \r\n \r\n a\"\"\"
|
||||
".parse::<Value>().unwrap();
|
||||
assert_eq!(table["foo"].as_str(), Some(""));
|
||||
assert_eq!(table["bar"].as_str(), Some("a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_no_newline() {
|
||||
"a = \"\n\"".parse::<Value>().unwrap_err();
|
||||
"a = '\n'".parse::<Value>().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_leading_zeros() {
|
||||
"a = 00".parse::<Value>().unwrap_err();
|
||||
"a = -00".parse::<Value>().unwrap_err();
|
||||
"a = +00".parse::<Value>().unwrap_err();
|
||||
"a = 00.0".parse::<Value>().unwrap_err();
|
||||
"a = -00.0".parse::<Value>().unwrap_err();
|
||||
"a = +00.0".parse::<Value>().unwrap_err();
|
||||
"a = 9223372036854775808".parse::<Value>().unwrap_err();
|
||||
"a = -9223372036854775809".parse::<Value>().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_floats() {
|
||||
"a = 0.".parse::<Value>().unwrap_err();
|
||||
"a = 0.e".parse::<Value>().unwrap_err();
|
||||
"a = 0.E".parse::<Value>().unwrap_err();
|
||||
"a = 0.0E".parse::<Value>().unwrap_err();
|
||||
"a = 0.0e".parse::<Value>().unwrap_err();
|
||||
"a = 0.0e-".parse::<Value>().unwrap_err();
|
||||
"a = 0.0e+".parse::<Value>().unwrap_err();
|
||||
"a = 0.0e+00".parse::<Value>().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floats() {
|
||||
macro_rules! t {
|
||||
($actual:expr, $expected:expr) => ({
|
||||
let f = format!("foo = {}", $actual);
|
||||
println!("{}", f);
|
||||
let a = f.parse::<Value>().unwrap();
|
||||
assert_eq!(a["foo"].as_float().unwrap(), $expected);
|
||||
})
|
||||
}
|
||||
|
||||
t!("1.0", 1.0);
|
||||
t!("1.0e0", 1.0);
|
||||
t!("1.0e+0", 1.0);
|
||||
t!("1.0e-0", 1.0);
|
||||
t!("1.001e-0", 1.001);
|
||||
t!("2e10", 2e10);
|
||||
t!("2e+10", 2e10);
|
||||
t!("2e-10", 2e-10);
|
||||
t!("2_0.0", 20.0);
|
||||
t!("2_0.0_0e1_0", 20.0e10);
|
||||
t!("2_0.1_0e1_0", 20.1e10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_key_names() {
|
||||
let a = "
|
||||
foo = 3
|
||||
foo_3 = 3
|
||||
foo_-2--3--r23f--4-f2-4 = 3
|
||||
_ = 3
|
||||
- = 3
|
||||
8 = 8
|
||||
\"a\" = 3
|
||||
\"!\" = 3
|
||||
\"a^b\" = 3
|
||||
\"\\\"\" = 3
|
||||
\"character encoding\" = \"value\"
|
||||
'ʎǝʞ' = \"value\"
|
||||
".parse::<Value>().unwrap();
|
||||
&a["foo"];
|
||||
&a["-"];
|
||||
&a["_"];
|
||||
&a["8"];
|
||||
&a["foo_3"];
|
||||
&a["foo_-2--3--r23f--4-f2-4"];
|
||||
&a["a"];
|
||||
&a["!"];
|
||||
&a["\""];
|
||||
&a["character encoding"];
|
||||
&a["ʎǝʞ"];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_keys() {
|
||||
"key\n=3".parse::<Value>().unwrap_err();
|
||||
"key=\n3".parse::<Value>().unwrap_err();
|
||||
"key|=3".parse::<Value>().unwrap_err();
|
||||
"\"\"=3".parse::<Value>().unwrap_err();
|
||||
"=3".parse::<Value>().unwrap_err();
|
||||
"\"\"|=3".parse::<Value>().unwrap_err();
|
||||
"\"\n\"|=3".parse::<Value>().unwrap_err();
|
||||
"\"\r\"|=3".parse::<Value>().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_table_names() {
|
||||
"[]".parse::<Value>().unwrap_err();
|
||||
"[.]".parse::<Value>().unwrap_err();
|
||||
"[\"\".\"\"]".parse::<Value>().unwrap_err();
|
||||
"[a.]".parse::<Value>().unwrap_err();
|
||||
"[\"\"]".parse::<Value>().unwrap_err();
|
||||
"[!]".parse::<Value>().unwrap_err();
|
||||
"[\"\n\"]".parse::<Value>().unwrap_err();
|
||||
"[a.b]\n[a.\"b\"]".parse::<Value>().unwrap_err();
|
||||
"[']".parse::<Value>().unwrap_err();
|
||||
"[''']".parse::<Value>().unwrap_err();
|
||||
"['''''']".parse::<Value>().unwrap_err();
|
||||
"['\n']".parse::<Value>().unwrap_err();
|
||||
"['\r\n']".parse::<Value>().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_names() {
|
||||
let a = "
|
||||
[a.\"b\"]
|
||||
[\"f f\"]
|
||||
[\"f.f\"]
|
||||
[\"\\\"\"]
|
||||
['a.a']
|
||||
['\"\"']
|
||||
".parse::<Value>().unwrap();
|
||||
println!("{:?}", a);
|
||||
&a["a"]["b"];
|
||||
&a["f f"];
|
||||
&a["f.f"];
|
||||
&a["\""];
|
||||
&a["\"\""];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_bare_numeral() {
|
||||
"4".parse::<Value>().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_tables() {
|
||||
"a = {}".parse::<Value>().unwrap();
|
||||
"a = {b=1}".parse::<Value>().unwrap();
|
||||
"a = { b = 1 }".parse::<Value>().unwrap();
|
||||
"a = {a=1,b=2}".parse::<Value>().unwrap();
|
||||
"a = {a=1,b=2,c={}}".parse::<Value>().unwrap();
|
||||
"a = {a=1,}".parse::<Value>().unwrap_err();
|
||||
"a = {,}".parse::<Value>().unwrap_err();
|
||||
"a = {a=1,a=1}".parse::<Value>().unwrap_err();
|
||||
"a = {\n}".parse::<Value>().unwrap_err();
|
||||
"a = {".parse::<Value>().unwrap_err();
|
||||
"a = {a=[\n]}".parse::<Value>().unwrap();
|
||||
"a = {\"a\"=[\n]}".parse::<Value>().unwrap();
|
||||
"a = [\n{},\n{},\n]".parse::<Value>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_underscores() {
|
||||
macro_rules! t {
|
||||
($actual:expr, $expected:expr) => ({
|
||||
let f = format!("foo = {}", $actual);
|
||||
let table = f.parse::<Value>().unwrap();
|
||||
assert_eq!(table["foo"].as_integer().unwrap(), $expected);
|
||||
})
|
||||
}
|
||||
|
||||
t!("1_0", 10);
|
||||
t!("1_0_0", 100);
|
||||
t!("1_000", 1000);
|
||||
t!("+1_000", 1000);
|
||||
t!("-1_000", -1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_underscores() {
|
||||
bad!("foo = 0_", "invalid number");
|
||||
bad!("foo = 0__0", "invalid number");
|
||||
bad!("foo = __0", "invalid number");
|
||||
bad!("foo = 1_0_", "invalid number");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_unicode_codepoint() {
|
||||
bad!("foo = \"\\uD800\"", "invalid escape value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_strings() {
|
||||
bad!("foo = \"\\uxx\"", "invalid hex escape");
|
||||
bad!("foo = \"\\u\"", "invalid hex escape");
|
||||
bad!("foo = \"\\", "unterminated");
|
||||
bad!("foo = '", "unterminated");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_string() {
|
||||
assert_eq!("foo = \"\"".parse::<Value>()
|
||||
.unwrap()["foo"]
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn booleans() {
|
||||
let table = "foo = true".parse::<Value>().unwrap();
|
||||
assert_eq!(table["foo"].as_bool(), Some(true));
|
||||
|
||||
let table = "foo = false".parse::<Value>().unwrap();
|
||||
assert_eq!(table["foo"].as_bool(), Some(false));
|
||||
|
||||
assert!("foo = true2".parse::<Value>().is_err());
|
||||
assert!("foo = false2".parse::<Value>().is_err());
|
||||
assert!("foo = t1".parse::<Value>().is_err());
|
||||
assert!("foo = f2".parse::<Value>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_nesting() {
|
||||
bad!("
|
||||
a = [2]
|
||||
[[a]]
|
||||
b = 5
|
||||
", "duplicate key: `a`");
|
||||
bad!("
|
||||
a = 1
|
||||
[a.b]
|
||||
", "duplicate key: `a`");
|
||||
bad!("
|
||||
a = []
|
||||
[a.b]
|
||||
", "duplicate key: `a`");
|
||||
bad!("
|
||||
a = []
|
||||
[[a.b]]
|
||||
", "duplicate key: `a`");
|
||||
bad!("
|
||||
[a]
|
||||
b = { c = 2, d = {} }
|
||||
[a.b]
|
||||
c = 2
|
||||
", "duplicate key: `b`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_table_redefine() {
|
||||
bad!("
|
||||
[a]
|
||||
foo=\"bar\"
|
||||
[a.b]
|
||||
foo=\"bar\"
|
||||
[a]
|
||||
", "redefinition of table `a`");
|
||||
bad!("
|
||||
[a]
|
||||
foo=\"bar\"
|
||||
b = { foo = \"bar\" }
|
||||
[a]
|
||||
", "redefinition of table `a`");
|
||||
bad!("
|
||||
[a]
|
||||
b = {}
|
||||
[a.b]
|
||||
", "duplicate key: `b`");
|
||||
|
||||
bad!("
|
||||
[a]
|
||||
b = {}
|
||||
[a]
|
||||
", "redefinition of table `a`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetimes() {
|
||||
macro_rules! t {
|
||||
($actual:expr) => ({
|
||||
let f = format!("foo = {}", $actual);
|
||||
let toml = f.parse::<Value>().expect(&format!("failed: {}", f));
|
||||
assert_eq!(toml["foo"].as_datetime().unwrap().to_string(), $actual);
|
||||
})
|
||||
}
|
||||
|
||||
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");
|
||||
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");
|
||||
bad!("foo = 2016-09-09T09:09:09-2:00", "failed to parse date");
|
||||
bad!("foo = 2016-09-09T09:09:09Z-2:00", "failed to parse date");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn require_newline_after_value() {
|
||||
bad!("0=0r=false", "invalid number at line 1");
|
||||
bad!(r#"
|
||||
0=""o=""m=""r=""00="0"q="""0"""e="""0"""
|
||||
"#, "expected newline");
|
||||
bad!(r#"
|
||||
[[0000l0]]
|
||||
0="0"[[0000l0]]
|
||||
0="0"[[0000l0]]
|
||||
0="0"l="0"
|
||||
"#, "expected newline");
|
||||
bad!(r#"
|
||||
0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
|
||||
"#, "expected newline");
|
||||
bad!(r#"
|
||||
0=0r0=0r=false
|
||||
"#, "invalid number at line 2");
|
||||
bad!(r#"
|
||||
0=0r0=0r=falsefal=false
|
||||
"#, "invalid number at line 2");
|
||||
}
|
496
tests/serde.rs
Normal file
496
tests/serde.rs
Normal file
|
@ -0,0 +1,496 @@
|
|||
extern crate serde;
|
||||
extern crate toml;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
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! equivalent {
|
||||
($literal:expr, $toml:expr,) => ({
|
||||
let toml = $toml;
|
||||
let literal = $literal;
|
||||
|
||||
// In/out of Value is equivalent
|
||||
println!("try_from");
|
||||
assert_eq!(t!(Value::try_from(literal.clone())), toml);
|
||||
println!("try_into");
|
||||
assert_eq!(literal, t!(toml.clone().try_into()));
|
||||
|
||||
// Through a string equivalent
|
||||
println!("to_string(literal)");
|
||||
assert_eq!(t!(toml::to_string(&literal)), toml.to_string());
|
||||
println!("to_string(toml)");
|
||||
assert_eq!(t!(toml::to_string(&toml)), toml.to_string());
|
||||
println!("literal, from_str(toml)");
|
||||
assert_eq!(literal, t!(toml::from_str(&toml.to_string())));
|
||||
println!("toml, from_str(toml)");
|
||||
assert_eq!(toml, t!(toml::from_str(&toml.to_string())));
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! error {
|
||||
($ty:ty, $toml:expr, $error:expr) => ({
|
||||
println!("attempting parsing");
|
||||
match toml::from_str::<$ty>(&$toml.to_string()) {
|
||||
Ok(_) => panic!("successful"),
|
||||
Err(e) => {
|
||||
assert!(e.to_string().contains($error),
|
||||
"bad error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
println!("attempting toml decoding");
|
||||
match $toml.try_into::<$ty>() {
|
||||
Ok(_) => panic!("successful"),
|
||||
Err(e) => {
|
||||
assert!(e.to_string().contains($error),
|
||||
"bad error: {}", e);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! decode( ($t:expr) => ({
|
||||
t!($t.try_into())
|
||||
}) );
|
||||
|
||||
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, Clone)]
|
||||
struct Foo { a: isize }
|
||||
|
||||
equivalent!(
|
||||
Foo { a: 2 },
|
||||
Table(map! { a: Integer(2) }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_hyphen() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo {
|
||||
a_b: isize,
|
||||
}
|
||||
|
||||
equivalent! {
|
||||
Foo { a_b: 2 },
|
||||
Table(map! { a_b: Integer(2) }),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo2 {
|
||||
#[serde(rename = "a-b")]
|
||||
a_b: isize,
|
||||
}
|
||||
|
||||
let mut m = BTreeMap::new();
|
||||
m.insert("a-b".to_string(), Integer(2));
|
||||
equivalent! {
|
||||
Foo2 { a_b: 2 },
|
||||
Table(m),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo { a: isize, b: Bar }
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Bar { a: String }
|
||||
|
||||
equivalent! {
|
||||
Foo { a: 2, b: Bar { a: "test".to_string() } },
|
||||
Table(map! {
|
||||
a: Integer(2),
|
||||
b: Table(map! {
|
||||
a: Value::String("test".to_string())
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_decode_error() {
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Range10(usize);
|
||||
impl Deserialize for Range10 {
|
||||
fn deserialize<D: Deserializer>(d: D) -> Result<Range10, D::Error> {
|
||||
let x: usize = try!(Deserialize::deserialize(d));
|
||||
if x > 10 {
|
||||
Err(serde::de::Error::custom("more than 10"))
|
||||
} else {
|
||||
Ok(Range10(x))
|
||||
}
|
||||
}
|
||||
}
|
||||
let d_good = Integer(5);
|
||||
let d_bad1 = Value::String("not an isize".to_string());
|
||||
let d_bad2 = Integer(11);
|
||||
|
||||
assert_eq!(Range10(5), d_good.try_into().unwrap());
|
||||
|
||||
let err1: Result<Range10, _> = d_bad1.try_into();
|
||||
assert!(err1.is_err());
|
||||
let err2: Result<Range10, _> = d_bad2.try_into();
|
||||
assert!(err2.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo { a: Vec<isize> }
|
||||
|
||||
equivalent! {
|
||||
Foo { a: vec![1, 2, 3, 4] },
|
||||
Table(map! {
|
||||
a: Array(vec![
|
||||
Integer(1),
|
||||
Integer(2),
|
||||
Integer(3),
|
||||
Integer(4)
|
||||
])
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_structs_with_options() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo {
|
||||
a: Option<Box<Foo>>,
|
||||
b: Bar,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Bar {
|
||||
a: String,
|
||||
b: f64,
|
||||
}
|
||||
|
||||
equivalent! {
|
||||
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 },
|
||||
},
|
||||
Table(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)
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashmap() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo {
|
||||
set: HashSet<char>,
|
||||
map: BTreeMap<String, isize>,
|
||||
}
|
||||
|
||||
equivalent! {
|
||||
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
|
||||
},
|
||||
},
|
||||
Table(map! {
|
||||
map: Table(map! {
|
||||
foo: Integer(10),
|
||||
bar: Integer(4)
|
||||
}),
|
||||
set: Array(vec![Value::String("a".to_string())])
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_array() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo { a: Vec<Bar>, }
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Bar { a: isize }
|
||||
|
||||
equivalent! {
|
||||
Foo { a: vec![Bar { a: 1 }, Bar { a: 2 }] },
|
||||
Table(map! {
|
||||
a: Array(vec![
|
||||
Table(map!{ a: Integer(1) }),
|
||||
Table(map!{ a: Integer(2) }),
|
||||
])
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_errors() {
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct Foo { bar: isize }
|
||||
|
||||
error! {
|
||||
Foo,
|
||||
Table(map! {
|
||||
bar: Value::String("a".to_string())
|
||||
}),
|
||||
"invalid type: string \"a\", expected isize for key `bar`"
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct Bar { foo: Foo }
|
||||
|
||||
error! {
|
||||
Bar,
|
||||
Table(map! {
|
||||
foo: Table(map! {
|
||||
bar: Value::String("a".to_string())
|
||||
})
|
||||
}),
|
||||
"invalid type: string \"a\", expected isize for key `foo.bar`"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_errors() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct Foo { bar: isize }
|
||||
|
||||
error! {
|
||||
Foo,
|
||||
Table(map! { }),
|
||||
"missing field `bar`"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_enum() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo { a: E }
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
enum E {
|
||||
Bar(isize),
|
||||
Baz(String),
|
||||
Last(Foo2),
|
||||
}
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo2 {
|
||||
test: String,
|
||||
}
|
||||
|
||||
equivalent! {
|
||||
Foo { a: E::Bar(10) },
|
||||
Table(map! { a: Integer(10) }),
|
||||
}
|
||||
|
||||
equivalent! {
|
||||
Foo { a: E::Baz("foo".to_string()) },
|
||||
Table(map! { a: Value::String("foo".to_string()) }),
|
||||
}
|
||||
|
||||
equivalent! {
|
||||
Foo { a: E::Last(Foo2 { test: "test".to_string() }) },
|
||||
Table(map! { a: Table(map! { test: Value::String("test".to_string()) }) }),
|
||||
}
|
||||
}
|
||||
|
||||
// #[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<String, String> }
|
||||
//
|
||||
// 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<String> }
|
||||
//
|
||||
// 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<Vec<String>> }
|
||||
//
|
||||
// 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<Bar> }
|
||||
// #[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, Clone)]
|
||||
struct Foo { a: Vec<Bar> }
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Bar;
|
||||
|
||||
equivalent! {
|
||||
Foo { a: vec![] },
|
||||
Table(map! {a: Array(Vec::new())}),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_arrays2() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Foo { a: Option<Vec<Bar>> }
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct Bar;
|
||||
|
||||
equivalent! {
|
||||
Foo { a: None },
|
||||
Table(map! {}),
|
||||
}
|
||||
|
||||
equivalent!{
|
||||
Foo { a: Some(vec![]) },
|
||||
Table(map! { a: Array(vec![]) }),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_keys() {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Foo { a: isize }
|
||||
|
||||
let toml = Table(map! { a: Integer(2), b: Integer(2) });
|
||||
assert!(toml.clone().try_into::<Foo>().is_ok());
|
||||
assert!(toml::from_str::<Foo>(&toml.to_string()).is_ok());
|
||||
}
|
|
@ -1,65 +1,61 @@
|
|||
extern crate rustc_serialize;
|
||||
extern crate toml;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use rustc_serialize::json::Json;
|
||||
use toml::Value as Toml;
|
||||
use serde_json::Value as Json;
|
||||
|
||||
use toml::{Parser, Value};
|
||||
use toml::Value::{Table, Integer, Float, Boolean, Datetime, Array};
|
||||
|
||||
fn to_json(toml: Value) -> Json {
|
||||
fn to_json(toml: toml::Value) -> Json {
|
||||
fn doit(s: &str, json: Json) -> Json {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(format!("{}", "type"), Json::String(format!("{}", s)));
|
||||
map.insert(format!("{}", "value"), json);
|
||||
let mut map = serde_json::Map::new();
|
||||
map.insert("type".to_string(), Json::String(s.to_string()));
|
||||
map.insert("value".to_string(), json);
|
||||
Json::Object(map)
|
||||
}
|
||||
|
||||
match toml {
|
||||
Value::String(s) => doit("string", Json::String(s)),
|
||||
Integer(i) => doit("integer", Json::String(format!("{}", i))),
|
||||
Float(f) => doit("float", Json::String({
|
||||
Toml::String(s) => doit("string", Json::String(s)),
|
||||
Toml::Integer(i) => doit("integer", Json::String(i.to_string())),
|
||||
Toml::Float(f) => doit("float", Json::String({
|
||||
let s = format!("{:.15}", f);
|
||||
let s = format!("{}", s.trim_right_matches('0'));
|
||||
if s.ends_with(".") {format!("{}0", s)} else {s}
|
||||
})),
|
||||
Boolean(b) => doit("bool", Json::String(format!("{}", b))),
|
||||
Datetime(s) => doit("datetime", Json::String(s)),
|
||||
Array(arr) => {
|
||||
Toml::Boolean(b) => doit("bool", Json::String(format!("{}", b))),
|
||||
Toml::Datetime(s) => doit("datetime", Json::String(s.to_string())),
|
||||
Toml::Array(arr) => {
|
||||
let is_table = match arr.first() {
|
||||
Some(&Table(..)) => true,
|
||||
Some(&Toml::Table(..)) => true,
|
||||
_ => false,
|
||||
};
|
||||
let json = Json::Array(arr.into_iter().map(to_json).collect());
|
||||
if is_table {json} else {doit("array", json)}
|
||||
}
|
||||
Table(table) => Json::Object(table.into_iter().map(|(k, v)| {
|
||||
(k, to_json(v))
|
||||
}).collect()),
|
||||
Toml::Table(table) => {
|
||||
let mut map = serde_json::Map::new();
|
||||
for (k, v) in table {
|
||||
map.insert(k, to_json(v));
|
||||
}
|
||||
Json::Object(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(toml: &str, json: &str) {
|
||||
let mut p = Parser::new(toml);
|
||||
let table = p.parse();
|
||||
assert!(p.errors.len() == 0, "had_errors: {:?}",
|
||||
p.errors.iter().map(|e| {
|
||||
(e.desc.clone(), &toml[e.lo - 5..e.hi + 5])
|
||||
}).collect::<Vec<(String, &str)>>());
|
||||
assert!(table.is_some());
|
||||
let toml = Table(table.unwrap());
|
||||
let toml_string = format!("{}", toml);
|
||||
println!("parsing:\n{}", toml);
|
||||
let toml: Toml = toml.parse().unwrap();
|
||||
let json: Json = json.parse().unwrap();
|
||||
|
||||
let json = Json::from_str(json).unwrap();
|
||||
// Assert toml == json
|
||||
let toml_json = to_json(toml.clone());
|
||||
assert!(json == toml_json,
|
||||
"expected\n{}\ngot\n{}\n",
|
||||
json.pretty(),
|
||||
toml_json.pretty());
|
||||
serde_json::to_string_pretty(&json).unwrap(),
|
||||
serde_json::to_string_pretty(&toml_json).unwrap());
|
||||
|
||||
let table2 = Parser::new(&toml_string).parse().unwrap();
|
||||
// floats are a little lossy
|
||||
if table2.values().any(|v| v.as_float().is_some()) { return }
|
||||
assert_eq!(toml, Table(table2));
|
||||
// Assert round trip
|
||||
println!("round trip parse: {}", toml);
|
||||
let toml2 = toml.to_string().parse().unwrap();
|
||||
assert_eq!(toml, toml2);
|
||||
}
|
||||
|
||||
macro_rules! test( ($name:ident, $toml:expr, $json:expr) => (
|
||||
|
|
Loading…
Reference in a new issue