Rewrite crate with serde support from ground up
This commit completely rewrites this crate from the ground up, supporting serde at the lowest levels as I believe serde support was intended to do. This is a major change from the previous versions of this crate, with a summary of changes being: * Serialization directly to TOML is now supported without going through a `Value` first. * Deserialization directly from TOML is now supported without going through a `Value`. Note that due to the TOML format some values still are buffered in intermediate memory, but overall this should be at a minimum now. * The API of `Value` was overhauled to match the API of `serde_json::Value`. The changes here were to: * Add `is_*` accessors * Add `get` and `get_mut` for one-field lookups. * Implement panicking lookups through `Index` The old `index` methods are now gone in favor of `get` and `Index` implementations. * A `Datetime` type has been added to represent a TOML datetime in a first-class fashion. Currently this type provides no accessors other than a `Display` implementation, but the idea is that this will grow support over time for decomposing the date. * Support for the `rustc-serialize` crate has been dropped, that'll stay on the 0.2 and 0.1 release trains. * This crate no longer supports the detection of unused fields, for that though you can use the `serde_ignored` crate on crates.io
This commit is contained in:
parent
4c358121bb
commit
e256931e9b
|
@ -7,11 +7,7 @@ sudo: false
|
||||||
before_script:
|
before_script:
|
||||||
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
|
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
|
||||||
script:
|
script:
|
||||||
- cargo build --verbose
|
- cargo test
|
||||||
- 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
|
|
||||||
- rustdoc --test README.md -L target
|
- rustdoc --test README.md -L target
|
||||||
- cargo doc --no-deps
|
- cargo doc --no-deps
|
||||||
after_success:
|
after_success:
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,5 +1,4 @@
|
||||||
[package]
|
[package]
|
||||||
|
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||||
|
@ -16,11 +15,8 @@ facilitate deserializing and serializing Rust structures.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustc-serialize = { optional = true, version = "0.3.0" }
|
serde = "0.9.6"
|
||||||
serde = { optional = true, version = "0.8" }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["rustc-serialize"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[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`
|
//! TOML into a Rust `struct`
|
||||||
//!
|
//!
|
||||||
//! Note that this works similarly with `serde` as well.
|
//! Note that this works similarly with `serde` as well.
|
||||||
|
@ -6,11 +6,12 @@
|
||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
extern crate toml;
|
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
|
/// This is what we're going to decode into. Each field is optional, meaning
|
||||||
/// that it doesn't have to be present in TOML.
|
/// that it doesn't have to be present in TOML.
|
||||||
#[derive(Debug, RustcDecodable)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
global_string: Option<String>,
|
global_string: Option<String>,
|
||||||
global_integer: Option<u64>,
|
global_integer: Option<u64>,
|
||||||
|
@ -22,13 +23,13 @@ struct Config {
|
||||||
/// table.
|
/// table.
|
||||||
///
|
///
|
||||||
/// Again, each field is optional, meaning they don't have to be present.
|
/// Again, each field is optional, meaning they don't have to be present.
|
||||||
#[derive(Debug, RustcDecodable)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ServerConfig {
|
struct ServerConfig {
|
||||||
ip: Option<String>,
|
ip: Option<String>,
|
||||||
port: Option<u64>,
|
port: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, RustcDecodable)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct PeerConfig {
|
struct PeerConfig {
|
||||||
ip: Option<String>,
|
ip: Option<String>,
|
||||||
port: Option<u64>,
|
port: Option<u64>,
|
||||||
|
@ -51,11 +52,6 @@ fn main() {
|
||||||
ip = "127.0.0.1"
|
ip = "127.0.0.1"
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
// Use the `decode_str` convenience here to decode a TOML string directly
|
let decoded: Config = toml::from_str(toml_str).unwrap();
|
||||||
// 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();
|
|
||||||
println!("{:#?}", decoded);
|
println!("{:#?}", decoded);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +1,51 @@
|
||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
extern crate toml;
|
extern crate toml;
|
||||||
extern crate rustc_serialize;
|
extern crate serde_json;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
use toml::Value;
|
use toml::Value as Toml;
|
||||||
use rustc_serialize::json::Json;
|
use serde_json::Value as Json;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = env::args();
|
let mut args = env::args();
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
let filename = if args.len() > 1 {
|
if args.len() > 1 {
|
||||||
let name = args.nth(1).unwrap();
|
let name = args.nth(1).unwrap();
|
||||||
File::open(&name).and_then(|mut f| {
|
File::open(&name).and_then(|mut f| {
|
||||||
f.read_to_string(&mut input)
|
f.read_to_string(&mut input)
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
name
|
|
||||||
} else {
|
} else {
|
||||||
io::stdin().read_to_string(&mut input).unwrap();
|
io::stdin().read_to_string(&mut input).unwrap();
|
||||||
"<stdin>".to_string()
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let mut parser = toml::Parser::new(&input);
|
match input.parse() {
|
||||||
let toml = match parser.parse() {
|
Ok(toml) => {
|
||||||
Some(toml) => toml,
|
let json = convert(toml);
|
||||||
None => {
|
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
||||||
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
|
|
||||||
}
|
}
|
||||||
};
|
Err(error) => println!("failed to parse TOML: {}", error),
|
||||||
let json = convert(Value::Table(toml));
|
}
|
||||||
println!("{}", json.pretty());
|
}
|
||||||
}
|
|
||||||
|
fn convert(toml: Toml) -> Json {
|
||||||
fn convert(toml: Value) -> Json {
|
match toml {
|
||||||
match toml {
|
Toml::String(s) => Json::String(s),
|
||||||
Value::String(s) => Json::String(s),
|
Toml::Integer(i) => Json::Number(i.into()),
|
||||||
Value::Integer(i) => Json::I64(i),
|
Toml::Float(f) => {
|
||||||
Value::Float(f) => Json::F64(f),
|
let n = serde_json::Number::from_f64(f)
|
||||||
Value::Boolean(b) => Json::Boolean(b),
|
.expect("float infinite and nan not allowed");
|
||||||
Value::Array(arr) => Json::Array(arr.into_iter().map(convert).collect()),
|
Json::Number(n)
|
||||||
Value::Table(table) => Json::Object(table.into_iter().map(|(k, v)| {
|
}
|
||||||
(k, convert(v))
|
Toml::Boolean(b) => Json::Bool(b),
|
||||||
}).collect()),
|
Toml::Array(arr) => Json::Array(arr.into_iter().map(convert).collect()),
|
||||||
Value::Datetime(dt) => Json::String(dt),
|
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
|
//! [TOML]: https://github.com/toml-lang/toml
|
||||||
//! files [1]. It is focused around high quality errors including specific spans
|
|
||||||
//! and detailed error messages when things go wrong.
|
|
||||||
//!
|
//!
|
||||||
//! 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:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```toml
|
||||||
//! let toml = r#"
|
//! [package]
|
||||||
//! [test]
|
//! name = "toml"
|
||||||
//! foo = "bar"
|
//! version = "0.2.1"
|
||||||
//! "#;
|
//! authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||||
//!
|
//!
|
||||||
//! let value = toml::Parser::new(toml).parse().unwrap();
|
//! [dependencies]
|
||||||
//! println!("{:?}", value);
|
//! 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`
|
//! ## TOML values
|
||||||
//! traits with TOML values. This library provides the following conversion
|
|
||||||
//! capabilities:
|
|
||||||
//!
|
//!
|
||||||
//! * `String` => `toml::Value` - via `Parser`
|
//! A value in TOML is represented with the `Value` enum in this crate:
|
||||||
//! * `toml::Value` => `String` - via `Display`
|
|
||||||
//! * `toml::Value` => rust object - via `Decoder`
|
|
||||||
//! * rust object => `toml::Value` - via `Encoder`
|
|
||||||
//!
|
//!
|
||||||
//! Convenience functions for performing multiple conversions at a time are also
|
//! ```rust,ignore
|
||||||
//! provided.
|
//! pub enum Value {
|
||||||
|
//! String(String),
|
||||||
|
//! Integer(i64),
|
||||||
|
//! Float(f64),
|
||||||
|
//! Boolean(bool),
|
||||||
|
//! Datetime(Datetime),
|
||||||
|
//! Array(Array),
|
||||||
|
//! Table(Table),
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! [1]: https://github.com/mojombo/toml
|
//! You'll note that TOML is very similar to JSON with the notable addition of a
|
||||||
//! [2]: https://github.com/BurntSushi/toml-test
|
//! `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
|
//! The easiest way to parse a TOML document is via the `Value` type:
|
||||||
//! [`serde`] crate through respective feature names. The `rustc-serialize`
|
|
||||||
//! feature is enabled by default.
|
|
||||||
//!
|
//!
|
||||||
//! [`rustc-serialize`]: http://github.com/rust-lang/rustc-serialize
|
//! ```rust
|
||||||
//! [`serde`]: http://github.com/serde-rs/serde
|
//! 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)]
|
#![deny(missing_docs)]
|
||||||
#![cfg_attr(test, deny(warnings))]
|
|
||||||
|
|
||||||
#[cfg(feature = "rustc-serialize")] extern crate rustc_serialize;
|
#[macro_use]
|
||||||
#[cfg(feature = "serde")] extern crate serde;
|
extern crate serde;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
pub mod value;
|
||||||
use std::str::FromStr;
|
mod datetime;
|
||||||
|
#[doc(no_inline)]
|
||||||
pub use parser::{Parser, ParserError};
|
pub use value::Value;
|
||||||
|
|
||||||
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
|
pub mod ser;
|
||||||
pub use self::encoder::{Encoder, Error, EncoderState, encode, encode_str};
|
#[doc(no_inline)]
|
||||||
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
|
pub use ser::{to_string, to_vec, Serializer};
|
||||||
pub use self::decoder::{Decoder, DecodeError, DecodeErrorKind, decode, decode_str};
|
pub mod de;
|
||||||
|
#[doc(no_inline)]
|
||||||
mod parser;
|
pub use de::{from_slice, from_str, Deserializer};
|
||||||
mod display;
|
mod tokens;
|
||||||
#[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""#);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
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(())
|
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;
|
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 {
|
struct User {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub surname: String,
|
pub surname: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
struct Users {
|
struct Users {
|
||||||
pub user: Vec<User>,
|
pub user: Vec<User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
struct TwoUsers {
|
struct TwoUsers {
|
||||||
pub user0: User,
|
pub user0: User,
|
||||||
pub user1: User,
|
pub user1: User,
|
||||||
|
@ -21,7 +24,7 @@ struct TwoUsers {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_unnecessary_newlines_array() {
|
fn no_unnecessary_newlines_array() {
|
||||||
assert!(!encode_str(&Users {
|
assert!(!to_string(&Users {
|
||||||
user: vec![
|
user: vec![
|
||||||
User {
|
User {
|
||||||
name: "John".to_string(),
|
name: "John".to_string(),
|
||||||
|
@ -32,13 +35,13 @@ fn no_unnecessary_newlines_array() {
|
||||||
surname: "Dough".to_string(),
|
surname: "Dough".to_string(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
}).unwrap()
|
||||||
.starts_with("\n"));
|
.starts_with("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_unnecessary_newlines_table() {
|
fn no_unnecessary_newlines_table() {
|
||||||
assert!(!encode_str(&TwoUsers {
|
assert!(!to_string(&TwoUsers {
|
||||||
user0: User {
|
user0: User {
|
||||||
name: "John".to_string(),
|
name: "John".to_string(),
|
||||||
surname: "Doe".to_string(),
|
surname: "Doe".to_string(),
|
||||||
|
@ -47,6 +50,6 @@ fn no_unnecessary_newlines_table() {
|
||||||
name: "Jane".to_string(),
|
name: "Jane".to_string(),
|
||||||
surname: "Dough".to_string(),
|
surname: "Dough".to_string(),
|
||||||
},
|
},
|
||||||
})
|
}).unwrap()
|
||||||
.starts_with("\n"));
|
.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;
|
extern crate toml;
|
||||||
|
|
||||||
use toml::{Parser};
|
|
||||||
|
|
||||||
fn run(toml: &str) {
|
fn run(toml: &str) {
|
||||||
let mut p = Parser::new(toml);
|
println!("test if invalid:\n{}", toml);
|
||||||
let table = p.parse();
|
if let Ok(e) = toml.parse::<toml::Value>() {
|
||||||
assert!(table.is_none());
|
panic!("parsed to: {:#?}", e);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +24,6 @@ test!(datetime_malformed_no_secs,
|
||||||
include_str!("invalid/datetime-malformed-no-secs.toml"));
|
include_str!("invalid/datetime-malformed-no-secs.toml"));
|
||||||
test!(datetime_malformed_no_t,
|
test!(datetime_malformed_no_t,
|
||||||
include_str!("invalid/datetime-malformed-no-t.toml"));
|
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,
|
test!(datetime_malformed_with_milli,
|
||||||
include_str!("invalid/datetime-malformed-with-milli.toml"));
|
include_str!("invalid/datetime-malformed-with-milli.toml"));
|
||||||
test!(duplicate_keys,
|
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 toml;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use toml::Value as Toml;
|
||||||
use rustc_serialize::json::Json;
|
use serde_json::Value as Json;
|
||||||
|
|
||||||
use toml::{Parser, Value};
|
fn to_json(toml: toml::Value) -> Json {
|
||||||
use toml::Value::{Table, Integer, Float, Boolean, Datetime, Array};
|
|
||||||
|
|
||||||
fn to_json(toml: Value) -> Json {
|
|
||||||
fn doit(s: &str, json: Json) -> Json {
|
fn doit(s: &str, json: Json) -> Json {
|
||||||
let mut map = BTreeMap::new();
|
let mut map = serde_json::Map::new();
|
||||||
map.insert(format!("{}", "type"), Json::String(format!("{}", s)));
|
map.insert("type".to_string(), Json::String(s.to_string()));
|
||||||
map.insert(format!("{}", "value"), json);
|
map.insert("value".to_string(), json);
|
||||||
Json::Object(map)
|
Json::Object(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
match toml {
|
match toml {
|
||||||
Value::String(s) => doit("string", Json::String(s)),
|
Toml::String(s) => doit("string", Json::String(s)),
|
||||||
Integer(i) => doit("integer", Json::String(format!("{}", i))),
|
Toml::Integer(i) => doit("integer", Json::String(i.to_string())),
|
||||||
Float(f) => doit("float", Json::String({
|
Toml::Float(f) => doit("float", Json::String({
|
||||||
let s = format!("{:.15}", f);
|
let s = format!("{:.15}", f);
|
||||||
let s = format!("{}", s.trim_right_matches('0'));
|
let s = format!("{}", s.trim_right_matches('0'));
|
||||||
if s.ends_with(".") {format!("{}0", s)} else {s}
|
if s.ends_with(".") {format!("{}0", s)} else {s}
|
||||||
})),
|
})),
|
||||||
Boolean(b) => doit("bool", Json::String(format!("{}", b))),
|
Toml::Boolean(b) => doit("bool", Json::String(format!("{}", b))),
|
||||||
Datetime(s) => doit("datetime", Json::String(s)),
|
Toml::Datetime(s) => doit("datetime", Json::String(s.to_string())),
|
||||||
Array(arr) => {
|
Toml::Array(arr) => {
|
||||||
let is_table = match arr.first() {
|
let is_table = match arr.first() {
|
||||||
Some(&Table(..)) => true,
|
Some(&Toml::Table(..)) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
let json = Json::Array(arr.into_iter().map(to_json).collect());
|
let json = Json::Array(arr.into_iter().map(to_json).collect());
|
||||||
if is_table {json} else {doit("array", json)}
|
if is_table {json} else {doit("array", json)}
|
||||||
}
|
}
|
||||||
Table(table) => Json::Object(table.into_iter().map(|(k, v)| {
|
Toml::Table(table) => {
|
||||||
(k, to_json(v))
|
let mut map = serde_json::Map::new();
|
||||||
}).collect()),
|
for (k, v) in table {
|
||||||
|
map.insert(k, to_json(v));
|
||||||
|
}
|
||||||
|
Json::Object(map)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(toml: &str, json: &str) {
|
fn run(toml: &str, json: &str) {
|
||||||
let mut p = Parser::new(toml);
|
println!("parsing:\n{}", toml);
|
||||||
let table = p.parse();
|
let toml: Toml = toml.parse().unwrap();
|
||||||
assert!(p.errors.len() == 0, "had_errors: {:?}",
|
let json: Json = json.parse().unwrap();
|
||||||
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);
|
|
||||||
|
|
||||||
let json = Json::from_str(json).unwrap();
|
// Assert toml == json
|
||||||
let toml_json = to_json(toml.clone());
|
let toml_json = to_json(toml.clone());
|
||||||
assert!(json == toml_json,
|
assert!(json == toml_json,
|
||||||
"expected\n{}\ngot\n{}\n",
|
"expected\n{}\ngot\n{}\n",
|
||||||
json.pretty(),
|
serde_json::to_string_pretty(&json).unwrap(),
|
||||||
toml_json.pretty());
|
serde_json::to_string_pretty(&toml_json).unwrap());
|
||||||
|
|
||||||
let table2 = Parser::new(&toml_string).parse().unwrap();
|
// Assert round trip
|
||||||
// floats are a little lossy
|
println!("round trip parse: {}", toml);
|
||||||
if table2.values().any(|v| v.as_float().is_some()) { return }
|
let toml2 = toml.to_string().parse().unwrap();
|
||||||
assert_eq!(toml, Table(table2));
|
assert_eq!(toml, toml2);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! test( ($name:ident, $toml:expr, $json:expr) => (
|
macro_rules! test( ($name:ident, $toml:expr, $json:expr) => (
|
||||||
|
|
Loading…
Reference in a new issue