diff --git a/.travis.yml b/.travis.yml index b208245..94efa46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,34 @@ +sudo: false +dist: trusty language: rust -rust: -- stable -- beta -- nightly -script: -- cargo build --verbose -- cargo test --verbose + +cache: +- cargo + +before_cache: +- rm -r $TRAVIS_BUILD_DIR/target/debug + +jobs: + include: + - os: linux + rust: stable + - os: linux + rust: beta + - os: linux + rust: nightly + + # deploy + - stage: publish + os: linux + rust: stable + env: + # CARGO_TOKEN + - secure: "IPm2lHXAtXUY8pJtUMRV+jgLSCPKRJQyP3ax5aUhyaGlR83w4krEOcIpqQgu5a4rgw3SZUjDhxQMsRbzRZIehU+C3u2LTDpn0yycPnGmFRKVqnoS5dHdFMQ6IKLDZQK99MFZ/vMThipImnS9WFm2D1X/8XS31Mpn81Y7o54rIHk=" + install: true + script: true + + deploy: + - provider: script + script: 'cargo publish --verbose --token=$CARGO_TOKEN' + on: + tags: true diff --git a/Cargo.toml b/Cargo.toml index bdb5e22..7b92f07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,20 +3,21 @@ name = "quire" description = "A YAML-based configuration parsing library" license = "MIT/Apache-2.0" -readme = "README.rst" +readme = "README.md" keywords = ["config", "yaml", "parser"] homepage = "http://github.com/tailhook/rust-quire" -version = "0.2.3" +categories = ["config"] +version = "0.4.1" authors = ["paul@colomiets.name"] [dependencies] -rustc-serialize = "0.3" -quick-error = "1.0.0" -regex = "0.1.80" -humantime = "1.0.0" -num-traits = "0.1.36" +quick-error = "1.2.0" +num-traits = "0.2.5" humannum = "0.1.0" serde = "1.0.10" [dev-dependencies] serde_derive = "1.0.10" +serde_json = "1.0.2" +serde-transcode = "1.0.0" +serde-humantime = "0.1.1" diff --git a/README.rst b/README.md similarity index 57% rename from README.rst rename to README.md index 7bf61c9..0910898 100644 --- a/README.rst +++ b/README.md @@ -1,18 +1,19 @@ -========== Rust Quire ========== -:Status: Beta +*Status: Beta* -The ``rust-quire`` is a Rust_ port for quire_ configuration parsing library. -It also contains YAML_ parser (however, it tuned for configuration parsing -rather than generic YAML parser, e.g. it assumes YAML always fits memory). +The ``rust-quire`` is a [Rust][1] port for [quire][2] configuration parsing +library. It also contains [YAML][3] parser (however, it tuned for +configuration parsing rather than generic YAML parser, e.g. it assumes YAML +always fits memory). +See `doc/` for additional information. -.. _quire: http://github.com/tailhook/quire -.. _YAML: http://yaml.org -.. _Rust: http://rust-lang.org +[1]: http://github.com/tailhook/quire +[2]: http://yaml.org +[3]: http://rust-lang.org License diff --git a/bulk.yaml b/bulk.yaml new file mode 100644 index 0000000..bf99854 --- /dev/null +++ b/bulk.yaml @@ -0,0 +1,15 @@ +minimum-bulk: v0.4.5 + +versions: + +- file: Cargo.toml + block-start: ^\[package\] + block-end: ^\[.*\] + regex: ^version\s*=\s*"(\S+)" + +- file: doc/conf.py + regex: ^version\s*=\s*'(\S+)' + partial-version: ^\d+\.\d+ + +- file: doc/conf.py + regex: ^release\s*=\s*'(\S+)' diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..d27ec78 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,11 @@ +doc/ +==== + +In this directory documentation in [sphinx-doc][1] format. + +Type `make html` to get HTML generated. Expect to get errors +when *sphinx-doc* is not installed on your system. + +With `make` you get a list of possible output formats. + +[1]: http://www.sphinx-doc.org/ diff --git a/doc/conf.py b/doc/conf.py index 029ed57..98d8303 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -42,16 +42,16 @@ # General information about the project. project = 'Quire' -copyright = '2013, Paul Colomiets' +copyright = '2013-2019, Paul Colomiets' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '2.0' +version = '0.4' # The full version, including alpha/beta/rc tags. -release = '2.0-beta1' +release = '0.4.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/user.rst b/doc/user.rst index 35f38d4..5df98c6 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -190,7 +190,7 @@ node that contains tag. For example: .. code-block:: yaml # config.yaml - items: !Include items.yaml + items: !*Include items.yaml .. code-block:: yaml @@ -209,6 +209,89 @@ Is equivalent of: - banana +.. _include-seq: + +Include Sequences of Files +-------------------------- + +The ``!*IncludeSeq`` tag includes files matched by glob as a sequence: + +.. code-block:: yaml + + items: !*IncludeSeq "fruits/*.yaml" + +Can be parsed as: + +.. code-block:: yaml + + items: + - apple + - banana + - cherry + +This depends on the exact application, but usually files returned by `glob` +are sorted in alphabetical order. All the power of globs is supported, so you +can do: + +.. code-block:: yaml + + items: !*IncludeSeq "files/**/*.yaml" + +Another trick is merge multiple files, each with it's own set of keys into +a single one (see map-merge_ below): + +.. code-block:: yaml + + # file1.yaml + key1: 1 + key2: 2 + # file2.yaml + key3: 3 + key4: 4 + # main.yaml + <<: !*IncludeSeq "configs/*.yaml" + +This results into the following config: + +.. code-block:: yaml + + key1: 1 + key2: 2 + key3: 3 + key4: 4 + +Note: merging is not recursive, i.e. top level keys are considered as a whole, +even if they are dicts. + + +.. _include-map: + +Include Mapping from Files +-------------------------- + +The ``!*IncludeMap`` tag works similarly to ``!*IncludeSeq`` but requires to +mark part of a name used as a key. For example: + +.. code-block:: yaml + + items: !*IncludeMap "fruits/(*).yaml" + +Might result into the folowing: + +.. code-block:: yaml + + items: + apple: { color: orange } + banana: { color: yellow } + cherry: { color: red } + +You can parenthesize any part as well as a whole path: + +.. code-block:: yaml + + files: !*IncludeMap "(**/*.yaml)" + + .. _map-merge: Merging Mappings diff --git a/examples/item_lists.rs b/examples/item_lists.rs new file mode 100644 index 0000000..dbe4098 --- /dev/null +++ b/examples/item_lists.rs @@ -0,0 +1,92 @@ +extern crate quire; +#[macro_use] extern crate serde_derive; +use quire::{parse_config, Options}; +use quire::validate::{Structure, Scalar, Sequence}; +//e quire::validate::{Structure, Scalar, Sequence, Enum}; +// TODO Marker_002 Validate against known vehicles + + +#[derive(Deserialize)] +#[allow(dead_code)] +struct Journey { + name: String, + year: String, + team: Members, + vehicles: Vehicles, + locations: Locations, +} + +#[derive(Debug,Deserialize)] +struct Members(Vec); + + +/* TODO Marker_002 Validate against known vehicles +enum KnownVehicles { + Bobcat, + Jeep, + Landrover, + Unimog, +} +// TODO Marker_002 Validate against known vehicles +*/ + +#[derive(Deserialize)] +struct Vehicles(Vec); +// TODO Marker_002 Validate against known vehicles + +//#[derive(Deserialize)] +#[derive(Debug,Deserialize)] +struct Locations(Vec); + + +// give it a method +// so we can create a iterator for it +#[allow(dead_code)] +impl Members { + fn new() -> Members { + Members(Vec::new()) + } +} + +// and implement IntoIterator +impl IntoIterator for Members { + type Item = String; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +fn validator() -> Structure<'static> { + Structure::new() + .member("name", Scalar::new()) + .member("year", Scalar::new()) + .member("team", Sequence::new(Scalar::new())) + .member("vehicles", Sequence::new(Scalar::new())) + .member("locations", Sequence::new(Scalar::new())) +} + +fn work(jrny: &Journey) { + println!("name is {}.", jrny.name); + println!("year is {}.", jrny.year); + /* + for tm in jrny.team { + println!("team member {}.", tm); + } + TODO make team iterable + */ + println!("team members {:?} DBG.", jrny.team); // TODO make team iterable + println!("{:?} DBG a.k.a. DeBuG", jrny.locations); + // + // TODO show more of what has been read from the YAML configuration + // +} + +fn main() { + let jrny: Journey; + jrny = parse_config("examples/journey.yaml", + &validator(), &Options::default()) + .expect("valid config"); + work(&jrny) +} diff --git a/examples/journey.yaml b/examples/journey.yaml new file mode 100644 index 0000000..615fe2a --- /dev/null +++ b/examples/journey.yaml @@ -0,0 +1,23 @@ + +# journey.yaml +# "configuration" for item_lists + +--- +name: Voyage Voyage +year: 2020 +team: + - Alice + - Bob + - Claire + - David +vehicles: + - Jeep + - Landrover + # Beware: TODO Marker_002 Validate against known vehicles +locations: + - Home + - Sweet + - Home + +... +# l l diff --git a/src/ast.rs b/src/ast.rs index ad6a7a0..f993a92 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -8,7 +8,7 @@ use std::collections::BTreeMap; use super::tokenizer::Pos; use super::errors::{Error, ErrorCollector}; use super::parser::Node as P; -use super::parser::{Directive, Node, Document}; +use super::parser::{Node, Document}; use super::tokenizer::TokenType as T; use self::Ast::*; use self::NullKind::*; @@ -118,7 +118,6 @@ impl Ast { struct Context<'a, 'b: 'a> { options: &'a Options<'b>, - directives: Vec>, err: &'a ErrorCollector, } @@ -179,6 +178,14 @@ impl<'a, 'b: 'a> Context<'a, 'b> { return self.options.include(&tok.start, &Include::File { filename: val }, self.err); } + P::Scalar(Some("!*IncludeSeq"), _anch, ref val, ref tok) => { + return self.options.include(&tok.start, + &Include::Sequence { pattern: val }, self.err); + } + P::Scalar(Some("!*IncludeMap"), _anch, ref val, ref tok) => { + return self.options.include(&tok.start, + &Include::Mapping { pattern: val }, self.err); + } P::Scalar(ref tag, _, ref val, ref tok) => { let pos = tok.start.clone(); let tag = self.string_to_tag(&pos, tag); @@ -371,7 +378,6 @@ impl<'a, 'b: 'a> Context<'a, 'b> { pub fn process(opt: &Options, doc: Document, err: &ErrorCollector) -> Ast { let mut ctx = Context { options: opt, - directives: doc.directives, err: err, }; return ctx.process(&doc.root); diff --git a/src/de.rs b/src/de.rs index f273c53..b665c86 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,38 +1,289 @@ -use std::marker::PhantomData; -use std::ops::{Neg, AddAssign, MulAssign}; -use std::str::FromStr; +use std::fmt::Write; +use std::collections::BTreeMap; +use std::collections::btree_map; +use std::iter::{Peekable, Enumerate}; +use std::mem::replace; use std::slice; +use std::str::FromStr; -use serde::de::{self, Deserialize, DeserializeSeed, Visitor, SeqAccess, - MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; +use serde::de::{self, DeserializeSeed, Visitor, SeqAccess}; +use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; -use ast::Ast; -use ast::Ast as A; -use errors::{Error, ErrorCollector}; +use ast::{Ast, Ast as A, Tag}; +use errors::{Error, add_info}; type Result = ::std::result::Result; +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Mode { + Normal, + Enum, +} + +#[derive(Debug)] pub struct Deserializer<'a> { ast: &'a Ast, - err: ErrorCollector, + mode: Mode, path: String, } -struct ListVisitor<'a, 'b: 'a>(slice::Iter<'b, Ast>, &'a mut Deserializer<'b>); + +struct ListVisitor<'a, 'b: 'a>(Enumerate>, &'a mut Deserializer<'b>); +struct MapVisitor<'a, 'b: 'a>(Peekable>, + usize, &'a mut Deserializer<'b>); +struct EnumVisitor<'a, 'b: 'a>(&'a mut Deserializer<'b>); +struct VariantVisitor<'a, 'b: 'a>(&'a mut Deserializer<'b>); +struct KeyDeserializer(String); impl<'de> Deserializer<'de> { - // By convention, `Deserializer` constructors are named like `from_xyz`. - // That way basic use cases are satisfied by something like - // `serde_json::from_str(...)` while advanced use cases that require a - // deserializer can make one with `serde_json::Deserializer::from_str(...)`. - pub fn new<'x>(ast: &'x Ast, err: &ErrorCollector) -> Deserializer<'x> { + pub fn new<'x>(ast: &'x Ast) -> Deserializer<'x> { Deserializer { ast: &ast, - err: err.clone(), + mode: Mode::Normal, path: "".to_string(), } } + + fn map_err(&self, result: Result) -> Result { + add_info(&self.ast.pos(), &self.path, result) + } +} + +impl<'a> de::Deserializer<'a> for KeyDeserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_string(self.0) + } + fn deserialize_bool(self, visitor: V) -> Result + where V: Visitor<'a> + { + let value = match &self.0[..] { + "true" => true, + "false" => false, + _ => { + let e: Error = de::Error::custom(format!("bad boolean {:?}", self.0)); + return Err(e); + } + }; + visitor.visit_bool(value) + } + + fn deserialize_i8(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_i8(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_i16(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_i16(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_i32(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_i32(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_i64(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_i64(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_u8(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_u8(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_u16(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_u16(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_u32(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_u32(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_u64(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_u64(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_f32(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_f32(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_f64(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_f64(FromStr::from_str(&self.0).map_err(Error::custom)?) + } + + fn deserialize_char(self, visitor: V) -> Result + where V: Visitor<'a> + { + let mut chars = self.0.chars(); + let val = (chars.next() + .ok_or_else(|| { + de::Error::custom("single character expected") + }) as Result<_>)?; + if chars.next().is_some() { + return Err(de::Error::custom("single character expected")) + } + visitor.visit_char(val) + } + + fn deserialize_str(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_str(&self.0) + } + + fn deserialize_string(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_string(self.0) + } + + // The `Serializer` implementation on the previous page serialized byte + // arrays as JSON arrays of bytes. Handle that representation here. + fn deserialize_bytes(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_bytes(self.0.as_bytes()) + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where V: Visitor<'a> + { + visitor.visit_bytes(self.0.as_bytes()) + } + + fn deserialize_option(self, visitor: V) -> Result + where V: Visitor<'a> + { + match &self.0[..] { + "" | "~" | "null" => { + return visitor.visit_none(); + } + _ => { + return visitor.visit_some(self) + } + } + } + + fn deserialize_unit(self, _visitor: V) -> Result + where V: Visitor<'a> + { + unimplemented!(); + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + _visitor: V + ) -> Result + where V: Visitor<'a> + { + unimplemented!(); + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V + ) -> Result + where V: Visitor<'a> + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, _visitor: V) -> Result + where V: Visitor<'a> + { + Err(de::Error::custom("sequence can't be mapping key in quire")) + } + + fn deserialize_tuple( + self, + _len: usize, + _visitor: V + ) -> Result + where V: Visitor<'a> + { + Err(de::Error::custom("tuple can't be mapping key in quire")) + } + + // Tuple structs look just like sequences in JSON. + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + _visitor: V + ) -> Result + where V: Visitor<'a> + { + Err(de::Error::custom("tuple struct can't be mapping key in quire")) + } + + fn deserialize_map(self, _visitor: V) -> Result + where V: Visitor<'a> + { + Err(de::Error::custom("mapping can't be mapping key in quire")) + } + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + _visitor: V + ) -> Result + where V: Visitor<'a> + { + Err(de::Error::custom("struct can't be mapping key in quire")) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + _visitor: V + ) -> Result + where V: Visitor<'a> + { + // TODO(tailhook) some support might work + Err(de::Error::custom("enum can't be mapping key in quire")) + } + + fn deserialize_identifier( + self, + visitor: V + ) -> Result + where V: Visitor<'a> + { + visitor.visit_string(self.0) + } + + fn deserialize_ignored_any( + self, + visitor: V + ) -> Result + where V: Visitor<'a> + { + self.deserialize_unit(visitor) + } } impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { @@ -44,7 +295,12 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de> { - unimplemented!(); + match *self.ast { + A::Map(..) => self.deserialize_map(visitor), + A::Seq(..) => self.deserialize_seq(visitor), + A::Scalar(..) => self.deserialize_string(visitor), + A::Null(..) => self.deserialize_unit(visitor), + } } // Uses the `parse_bool` parsing function defined above to read the JSON @@ -159,7 +415,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { } ref node => { Err(Error::decode_error(&node.pos(), &self.path, - format!("Can't parse {:?} as char", node))) + format!("expected a single character, got {}", node))) } }; visitor.visit_char(c?) @@ -167,25 +423,25 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { fn deserialize_str(self, visitor: V) -> Result where V: Visitor<'de> - { - unimplemented!(); - } - - fn deserialize_string(self, visitor: V) -> Result - where V: Visitor<'de> { let val = match *self.ast { - A::Scalar(ref pos, _, _, ref val) => { + A::Scalar(_, _, _, ref val) => { val } ref node => { return Err(Error::decode_error(&node.pos(), &self.path, - format!("Can't parse {:?} as char", node))) + format!("expected string, got {}", node))) } }; visitor.visit_str(val) } + fn deserialize_string(self, visitor: V) -> Result + where V: Visitor<'de> + { + self.deserialize_str(visitor) + } + // The `Serializer` implementation on the previous page serialized byte // arrays as JSON arrays of bytes. Handle that representation here. fn deserialize_bytes(self, _visitor: V) -> Result @@ -204,7 +460,10 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { where V: Visitor<'de> { match *self.ast { - A::Null(..) => { + A::Null(_, Tag::NonSpecific, _) => { + return visitor.visit_none() + } + A::Null(_, Tag::LocalTag(_), _) if self.mode == Mode::Enum => { return visitor.visit_none() } _ => { @@ -217,7 +476,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { fn deserialize_unit(self, visitor: V) -> Result where V: Visitor<'de> { - unimplemented!() + visitor.visit_unit() } // Unit struct means a named value containing no data. @@ -228,7 +487,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - unimplemented!() + visitor.visit_unit() } // As is done here, serializers are encouraged to treat newtype structs as @@ -241,25 +500,30 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - unimplemented!() + visitor.visit_newtype_struct(self) } // Deserialization of compound types like sequences and maps happens by // passing the visitor an "Access" object that gives it the ability to // iterate through the data contained in the sequence. - fn deserialize_seq(mut self, visitor: V) -> Result + fn deserialize_seq(self, visitor: V) -> Result where V: Visitor<'de> { match *self.ast { A::Seq(_, _, ref seq) => { let ast = self.ast; - let result = visitor.visit_seq(ListVisitor(seq.iter(), self)); + let result = visitor.visit_seq( + ListVisitor(seq.iter().enumerate(), self)); + let result = self.map_err(result); self.ast = ast; return result; } + A::Null(..) => { + return visitor.visit_seq(Vec::<()>::new().into_deserializer()); + } ref node => { return Err(Error::decode_error(&node.pos(), &self.path, - format!("sequence expected got {}", node))) + format!("sequence expected, got {}", node))) } } } @@ -273,7 +537,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { fn deserialize_tuple( self, _len: usize, - visitor: V + _visitor: V ) -> Result where V: Visitor<'de> { @@ -285,17 +549,34 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { self, _name: &'static str, _len: usize, - visitor: V + _visitor: V ) -> Result where V: Visitor<'de> { unimplemented!(); } - fn deserialize_map(mut self, visitor: V) -> Result + fn deserialize_map(self, visitor: V) -> Result where V: Visitor<'de> { - unimplemented!(); + match *self.ast { + A::Map(_, _, ref map) => { + let ast = self.ast; + let result = visitor.visit_map( + MapVisitor(map.iter().peekable(), self.path.len(), self)); + let result = self.map_err(result); + self.ast = ast; + return result; + } + A::Null(..) => { + return visitor.visit_map( + BTreeMap::<(), ()>::new().into_deserializer()); + } + ref node => { + return Err(Error::decode_error(&node.pos(), &self.path, + format!("mapping expected, got {}", node))) + } + } } // Structs look just like maps in JSON. @@ -312,7 +593,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - unimplemented!(); + self.deserialize_map(visitor) } fn deserialize_enum( @@ -323,7 +604,10 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - unimplemented!(); + let mode = replace(&mut self.mode, Mode::Enum); + let result = visitor.visit_enum(EnumVisitor(self)); + self.mode = mode; + return result; } // An identifier in Serde is the type that identifies a field of a struct or @@ -336,7 +620,20 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - unimplemented!(); + match *self.ast.tag() { + Tag::GlobalTag(_) => unimplemented!(), + Tag::LocalTag(ref val) => Ok(visitor.visit_str::(val)?), + Tag::NonSpecific => match *self.ast { + A::Scalar(_, _, _, ref val) => { + Ok(visitor.visit_string::(val.replace("-", "_"))?) + } + ref node => { + return Err(Error::decode_error(&node.pos(), &self.path, + format!("identifier (string, or tag) \ + expected, got {}", node))) + } + }, + } } // Like `deserialize_any` but indicates to the `Deserializer` that it makes @@ -356,7 +653,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - unimplemented!(); + self.deserialize_unit(visitor) } } @@ -385,9 +682,13 @@ impl<'de, 'a, 'b: 'a> SeqAccess<'de> for ListVisitor<'a, 'b> { where T: DeserializeSeed<'de> { match self.0.next() { - Some(x) => { + Some((idx, x)) => { self.1.ast = x; - seed.deserialize(&mut *self.1).map(Some) + let plen = self.1.path.len(); + write!(&mut self.1.path, "[{}]", idx).unwrap(); + let result = seed.deserialize(&mut *self.1).map(Some); + self.1.path.truncate(plen); + return result; } None => { return Ok(None); @@ -396,11 +697,98 @@ impl<'de, 'a, 'b: 'a> SeqAccess<'de> for ListVisitor<'a, 'b> { } } +impl<'de, 'a, 'b: 'a> MapAccess<'de> for MapVisitor<'a, 'b> { + type Error = Error; + + fn next_key_seed(&mut self, seed: T) -> Result> + where T: DeserializeSeed<'de> + { + match self.0.peek() { + Some(&(key, _)) => { + write!(&mut self.2.path, ".{}", key).unwrap(); + let result = seed.deserialize(KeyDeserializer(key.clone())); + Ok(Some(self.2.map_err(result)?)) + } + None => { + return Ok(None); + } + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'de> + { + let (_, value) = self.0.next().unwrap(); + self.2.ast = value; + let result = seed.deserialize(&mut *self.2); + let result = self.2.map_err(result); + self.2.path.truncate(self.1); + return result; + } +} + +impl<'de, 'a, 'b: 'a> EnumAccess<'de> for EnumVisitor<'a, 'b> { + type Error = Error; + type Variant = VariantVisitor<'a, 'b>; + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> + where V: DeserializeSeed<'de> + { + let mode = replace(&mut self.0.mode, Mode::Enum); + let val = seed.deserialize(&mut *self.0)?; + self.0.mode = mode; + Ok((val, VariantVisitor(&mut *self.0))) + } +} + +impl<'de, 'a, 'b: 'a> VariantAccess<'de> for VariantVisitor<'a, 'b> { + type Error = Error; + fn unit_variant(self) -> Result<()> { + match *self.0.ast { + A::Null(..) => Ok(()), + // TODO(tailhook) check what happens if value doesn't match + // any anum tag + A::Scalar(_, _, _, _) => Ok(()), + ref node => { + return Err(Error::decode_error(&node.pos(), &self.0.path, + format!("nothing expected, got {}", node))); + } + } + } + fn newtype_variant_seed(self, seed: T) + -> Result + where + T: DeserializeSeed<'de> + { + seed.deserialize(self.0) + } + fn tuple_variant( + self, + _len: usize, + _visitor: V + ) -> Result + where + V: Visitor<'de> + { + unimplemented!(); + } + fn struct_variant( + self, + _fields: &'static [&'static str], + _visitor: V + ) -> Result + where + V: Visitor<'de> + { + unimplemented!(); + } +} #[cfg(test)] mod test { use std::rc::Rc; use std::path::PathBuf; - use std::collections::BTreeMap; + use std::collections::{BTreeMap, HashMap}; + use std::time::Duration; use serde::Deserialize; @@ -418,7 +806,7 @@ mod test { data, |doc| { process(&Options::default(), doc, &err) } ).map_err(|e| err.into_fatal(e)).unwrap(); - T::deserialize(&mut Deserializer::new(&ast, &err)) + T::deserialize(&mut Deserializer::new(&ast)) .map_err(|e| err.into_fatal(e)) .unwrap() } @@ -435,6 +823,17 @@ mod test { assert_eq!(decode::("false"), false); } + #[test] + fn decode_duration() { + #[derive(Deserialize, PartialEq, Debug)] + struct Dur { + #[serde(with="::serde_humantime")] + dur: Duration, + } + assert_eq!(decode::("dur: 1m 15s"), + Dur { dur: Duration::new(75, 0) }); + } + #[test] fn decode_i8() { assert_eq!(decode::("1"), 1); @@ -464,6 +863,20 @@ mod test { assert_eq!(decode::>("~"), None); } + #[test] + fn decode_nothing() { + #[derive(Deserialize, Debug, PartialEq)] + struct Nothing; + + assert_eq!(decode::(""), Nothing); + assert_eq!(decode::("null"), Nothing); + assert_eq!(decode::("~"), Nothing); + + assert_eq!(decode::<()>(""), ()); + assert_eq!(decode::<()>("null"), ()); + assert_eq!(decode::<()>("~"), ()); + } + #[test] fn decode_struct() { assert_eq!(decode::("a: 1\nb: hello"), TestStruct { @@ -479,7 +892,7 @@ mod test { } #[test] - #[should_panic(expected="sequence expected got Scalar")] + #[should_panic(expected="sequence expected, got Scalar")] fn decode_list_error() { decode::>("test"); } @@ -639,9 +1052,24 @@ mod test { } #[test] - #[should_panic(expected = "Expected sequence, got string")] + #[should_panic(expected = "sequence expected, got Scalar")] fn test_struct_items_tag() { decode::("items:\n 'hello'"); } + #[derive(PartialEq, Eq, Deserialize, Debug)] + struct NewType(u8); + + #[test] + fn test_newtype() { + assert_eq!(decode::("12"), NewType(12)); + } + + #[test] + fn test_map_with_non_string_keys() { + assert_eq!(decode::>("1: 2\n3: 4"), vec![ + (1, 2), + (3, 4), + ].into_iter().collect::>()); + } } diff --git a/src/decode.rs b/src/decode.rs deleted file mode 100644 index 7c1ae51..0000000 --- a/src/decode.rs +++ /dev/null @@ -1,770 +0,0 @@ -use std::mem::swap; -use std::fmt::Display; -use std::str::FromStr; -use std::default::Default; -use rustc_serialize::{Decoder}; - -use super::ast::Ast as A; -use super::ast::Tag; -use super::ast::NullKind; -use super::ast::Ast; -use super::errors::{Error, ErrorCollector}; -use super::tokenizer::Pos; -use self::ParserState::*; - - -pub type DecodeResult = Result; - -/* -#[derive(Debug)] -struct AnyJson(Json); - -impl Deref for AnyJson { - type Target = Json; - fn deref<'x>(&'x self) -> &'x Json { - let AnyJson(ref val) = *self; - return val; - } -} - -impl Decodable for AnyJson { - fn decode(dec: &mut D) - -> Result - { - let dec: &mut YamlDecoder = (dec as &mut Any).downcast_mut().unwrap(); - match dec.state { - Node(ref node) => { - return Ok(AnyJson(node.to_json())); - } - Byte(_, _) => unimplemented!(), - Map(_) | Seq(_) | ByteSeq(_, _) => unreachable!(), - Key(_, ref val) => return Ok(AnyJson(J::String(val.clone()))), - } - } -} - -impl PartialEq for AnyJson { - fn eq(&self, other: &AnyJson) -> bool { - let AnyJson(ref selfj) = *self; - let AnyJson(ref otherj) = *other; - return selfj == otherj; - } -} -impl Eq for AnyJson {} -impl Display for AnyJson { - fn fmt(&self, fmt:&mut Formatter) -> Result<(), FormatError> { - let AnyJson(ref selfj) = *self; - write!(fmt, "{}", selfj) - } -} -*/ - -#[derive(Debug)] -enum ParserState { - Node(Ast), - Map(Vec<(String, Ast)>), // used only in read_map_elt_key/elt_val - Seq(Vec), // used only in read_seq_elt - ByteSeq(Pos, Vec), // used for decoding Path - Byte(Pos, u8), // used for decoding Path - Key(Pos, String), -} - -pub struct YamlDecoder { - state: ParserState, - skip_tag: bool, - err: ErrorCollector, - path: String, -} - -impl YamlDecoder { - - pub fn new(ast: Ast, err: &ErrorCollector) -> YamlDecoder { - return YamlDecoder { - state: Node(ast), - skip_tag: false, - err: err.clone(), - path: "".to_string(), - } - } - - fn from_str(&mut self) -> DecodeResult - where T: FromStr+Default+'static, - E: Display - { - match self.state { - Node(A::Scalar(ref pos, _, _, ref val)) | Key(ref pos, ref val) => { - match FromStr::from_str(&val[..]) { - Ok(x) => Ok(x), - Err(err) => { - return Err(Error::decode_error(pos, &self.path, - // TODO(tailhook) print type name somehow - format!("Can't parse value: {}", err))); - } - } - } - Node(ref node) => { - return Err(Error::decode_error(&node.pos(), &self.path, - format!("Expected scalar, got {}", node))); - } - Byte(ref pos, _) => { - // The string is a sequence of bytes to make Path (which - // decodes from a sequence of bytes) work - // But if string specified instead of sequence of scalars - // we should emit an error - return Err(Error::decode_error(pos, &self.path, - format!("Expected sequence, got string"))); - } - Map(_) | Seq(_) | ByteSeq(_, _) => unreachable!(), - } - } -} - - -impl Decoder for YamlDecoder { - type Error = Error; - fn read_nil(&mut self) -> DecodeResult<()> { - match self.state { - Node(A::Null(_, _, _)) => return Ok(()), - Node(ref node) => { - self.err.add_error(Error::decode_error(&node.pos(), &self.path, - format!("Expected null"))); - return Ok(()) - } - Key(_, _) => unimplemented!(), - Byte(_, _) => unimplemented!(), - Map(_) | Seq(_) | ByteSeq(_, _) => unreachable!(), - } - } - - - fn read_u64(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - fn read_u32(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - fn read_u16(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - fn read_u8 (&mut self) -> DecodeResult { - if let Byte(_, x) = self.state { - return Ok(x); - } - Ok(self.from_str()?) - } - fn read_usize(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - - fn read_i64(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - fn read_i32(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - fn read_i16(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - fn read_i8 (&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - fn read_isize(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - - fn read_bool(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - - fn read_f64(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - - fn read_f32(&mut self) -> DecodeResult { - Ok(self.from_str()?) - } - - - fn read_char(&mut self) -> DecodeResult { - unimplemented!(); - } - - fn read_str(&mut self) -> DecodeResult { - // TODO(tailhook) Is it fast enought? - match self.state { - Node(A::Scalar(ref pos, _, _, ref val)) | Key(ref pos, ref val) => { - return Ok(val.clone()); - } - Node(ref node) => { - return Err(Error::decode_error(&node.pos(), &self.path, - format!("Expected scalar, got {}", node))); - } - Byte(ref pos, _) => { - // The string is a sequence of bytes to make Path (which - // decodes from a sequence of bytes) work - // But if string specified instead of sequence of scalars - // we should emit an error - return Err(Error::decode_error(pos, &self.path, - format!("Expected sequence, got string"))); - } - Map(_) | Seq(_) | ByteSeq(_, _) => unreachable!(), - } - } - - fn read_enum(&mut self, _name: &str, - f: F) -> DecodeResult - where F: FnOnce(&mut Self) -> DecodeResult - { - return f(self); - } - - fn read_enum_variant(&mut self, - names: &[&str], mut f: F) - -> DecodeResult - where F: FnMut(&mut Self, usize) -> DecodeResult - { - let mut idx = None; - match self.state { - Node(ref node) if node.tag().is_specific() => { - match node.tag() { - &Tag::NonSpecific => unreachable!(), - &Tag::LocalTag(ref tag) => { - for (i, name) in names.iter().enumerate() { - if *name == &tag[..] { - idx = Some(i); - } - } - if idx.is_none() { - return Err(Error::decode_error(&node.pos(), - &self.path, - format!("{} is not one of {:?}", tag, names))); - } - self.skip_tag = true; - } - &Tag::GlobalTag(_) => unimplemented!(), - } - } - Node(A::Scalar(ref pos, _, _, ref value)) => { - let programmatic_name = value.replace("-", "_"); - for (i, name) in names.iter().enumerate() { - if *name == &value[..] || - *name == &programmatic_name[..] { - idx = Some(i); - } - } - if idx.is_none() { - return Err(Error::decode_error(pos, &self.path, - format!("{} is not one of {:?}", value, names))); - } - } - Node(ref node) => { - return Err(Error::decode_error(&node.pos(), &self.path, - format!("Scalar or tagged value expected"))); - } - Byte(ref pos, _) => { - // This is a little bit heuristically determined. - // The Byte state is achieved when we have a string on a - // sequence position. We do that to decode paths - // (which unfortunately are sequences of bytes). - // So we have to determine the error here. - return Err(Error::decode_error(pos, &self.path, - format!("Expected sequence, got string. \ - Perhaps you forgot dash before the element \ - (use `- x` instead of `x`)"))); - } - ref node => panic!("Not implemented: state {:?}", node), - } - return f(self, idx.unwrap()); - } - - fn read_enum_variant_arg(&mut self, idx: usize, mut f: F) - -> DecodeResult - where F: FnOnce(&mut Self) -> DecodeResult - { - if idx == 0 { - return f(self); - } - unimplemented!(); - } - - fn read_enum_struct_variant(&mut self, names: &[&str], f: F) - -> DecodeResult - { - unimplemented!(); - } - - - fn read_enum_struct_variant_field(&mut self, - _name: &str, _idx: usize, _f: F) - -> DecodeResult - { - unimplemented!(); - } - - fn read_struct(&mut self, _name: &str, _len: usize, f: F) - -> DecodeResult - where F: FnOnce(&mut Self) -> DecodeResult - { - match self.state { - Node(A::Map(_, _, _)) => {} - Node(A::Null(ref pos, _, _)) => { - return f(&mut YamlDecoder { - state: Node(A::Map(pos.clone(), Tag::NonSpecific, - Default::default())), - skip_tag: false, - err: self.err.clone(), - path: self.path.clone(), - }); - } - Node(ref node) => { - return Err(Error::decode_error(&node.pos(), &self.path, - "Mapping expected".to_string())); - } - Byte(_, _) => unimplemented!(), - Map(_) | Seq(_) | ByteSeq(_, _) => unreachable!(), - Key(_, _) => unimplemented!(), - }; - return f(self); - } - - fn read_struct_field(&mut self, - name: &str, _idx: usize, f: F) - -> DecodeResult - where F: FnOnce(&mut Self) -> DecodeResult - { - if let Node(A::Map(ref pos, _, ref mut children)) = self.state { - match children.remove(&name.to_string()) { - None => { - return f(&mut YamlDecoder { - state: Node(A::Null(pos.clone(), Tag::NonSpecific, - NullKind::Implicit)), - skip_tag: false, - err: self.err.clone(), - path: format!("{}.{}", self.path, name), - }); - } - Some(node) => { - return f(&mut YamlDecoder { - state: Node(node), - skip_tag: false, - err: self.err.clone(), - path: format!("{}.{}", self.path, name), - }); - } - }; - } - unreachable!(); - } - - fn read_tuple(&mut self, _len: usize, _f: F) - -> DecodeResult - { - unimplemented!(); - } - - fn read_tuple_arg(&mut self, _idx: usize, _f: F) - -> DecodeResult - { - unimplemented!(); - } - - fn read_tuple_struct(&mut self, _name: &str, _len: usize, _f: F) - -> DecodeResult - { - unimplemented!(); - } - - fn read_tuple_struct_arg(&mut self, _idx: usize, _f: F) - -> DecodeResult - { - unimplemented!(); - } - - fn read_option(&mut self, f: F) - -> DecodeResult - where F: FnOnce(&mut Self, bool) -> Result - { - match self.state { - Node(A::Null(_, Tag::NonSpecific, _)) => f(self, false), - Node(A::Null(_, _, _)) if self.skip_tag => f(self, false), - Node(_) => f(self, true), - Key(_, _) => unimplemented!(), - Byte(_, _) => unimplemented!(), - Map(_) | Seq(_) | ByteSeq(_, _) => unreachable!(), - } - } - - fn read_seq(&mut self, f: F) - -> DecodeResult - where F: FnOnce(&mut Self, usize) -> Result - { - let items = match self.state { - Node(A::Seq(_, _, ref mut children)) => { - let mut ch = Default::default(); - swap(children, &mut ch); - ch - } - Node(A::Scalar(ref pos, _, _, ref val)) => { - let bytes = val.as_bytes(); - return f(&mut YamlDecoder { - state: ByteSeq(pos.clone(), bytes.to_vec()), - skip_tag: false, - err: self.err.clone(), - path: self.path.clone(), - }, bytes.len()); - } - Node(A::Null(_, _, _)) => Vec::new(), - Node(ref node) => { - return Err(Error::decode_error(&node.pos(), &self.path, - "Sequence expected".to_string())); - } - Byte(_, _) => unimplemented!(), - Map(_) | Seq(_) | ByteSeq(_, _) => unreachable!(), - Key(ref pos, ref val) => { - let bytes = val.as_bytes(); - return f(&mut YamlDecoder { - state: ByteSeq(pos.clone(), bytes.to_vec()), - skip_tag: false, - err: self.err.clone(), - path: self.path.clone(), - }, bytes.len()); - } - }; - let len = items.len(); - return f(&mut YamlDecoder { - state: Seq(items), - skip_tag: false, - err: self.err.clone(), - path: self.path.clone(), - }, len); - } - - fn read_seq_elt(&mut self, idx: usize, f: F) - -> DecodeResult - where F: FnOnce(&mut Self) -> Result - { - match self.state { - Seq(ref mut els) => { - let val = els.remove(0); - return f(&mut YamlDecoder { - state: Node(val), - skip_tag: false, - err: self.err.clone(), - path: format!("{}[{}]", self.path, idx), - }); - } - ByteSeq(ref pos, ref vec) => { - return f(&mut YamlDecoder { - state: Byte(pos.clone(), vec[idx]), - skip_tag: false, - err: self.err.clone(), - path: format!("{}[{}]", self.path, idx), - }); - } - _ => unreachable!(), - } - } - - fn read_map(&mut self, f: F) - -> DecodeResult - where F: FnOnce(&mut Self, usize) -> Result - { - let items = match self.state { - Node(A::Map(_, _, ref mut children)) => { - let mut ch = Default::default(); - swap(children, &mut ch); - ch.into_iter().collect() - } - Node(A::Null(_, _, _)) => Vec::new(), - Node(ref node) => { - return Err(Error::decode_error(&node.pos(), &self.path, - "Mapping expected".to_string())); - } - Byte(_, _) => unimplemented!(), - Map(_) | Seq(_) | ByteSeq(_, _) => unreachable!(), - Key(_, _) => unimplemented!(), - }; - let len = items.len(); - return f(&mut YamlDecoder { - state: Map(items), - skip_tag: false, - err: self.err.clone(), - path: self.path.clone(), - }, len); - } - - fn read_map_elt_key(&mut self, _idx: usize, f: F) - -> DecodeResult - where F: FnOnce(&mut Self) -> Result - { - if let Map(ref mut vec) = self.state { - let (ref key, ref val) = (*vec)[0]; - return f(&mut YamlDecoder { - state: Key(val.pos().clone(), key.clone()), - skip_tag: false, - err: self.err.clone(), - path: self.path.clone() + ".", - }); - } - unreachable!(); - } - - fn read_map_elt_val(&mut self, _idx: usize, f: F) - -> DecodeResult - where F: FnOnce(&mut Self) -> Result - { - if let Map(ref mut els) = self.state { - let (key, val) = els.remove(0); - return f(&mut YamlDecoder { - state: Node(val), - skip_tag: false, - err: self.err.clone(), - path: self.path.clone() + "." + &key[..], - }); - } - unreachable!(); - } - - fn error(&mut self, err: &str) -> Error { - let pos = match self.state { - Node(ref node) => node.pos().clone(), - Byte(_, _) => unimplemented!(), - Map(_) | Seq(_) | ByteSeq(_, _) => unimplemented!(), - Key(ref pos, _) => pos.clone(), - }; - return Error::decode_error(&pos, &self.path, err.to_string()) - } -} - -#[cfg(test)] -mod test { - use std::rc::Rc; - use std::path::PathBuf; - use std::collections::BTreeMap; - use rustc_serialize::Decodable; - - use test_util::decode; - use self::TestEnum::*; - - #[test] - fn decode_bool() { - assert_eq!(decode::("true"), true); - assert_eq!(decode::("false"), false); - } - - #[test] - fn decode_i8() { - assert_eq!(decode::("1"), 1); - assert_eq!(decode::("123"), 123); - assert_eq!(decode::("0"), 0); - } - - #[test] - fn decode_char() { - assert_eq!(decode::("1"), '1'); - assert_eq!(decode::("x"), 'x'); - assert_eq!(decode::("\"y\""), 'y'); - } - - #[test] - fn decode_string() { - assert_eq!(decode::("1"), "1"); - assert_eq!(decode::("x"), "x"); - assert_eq!(decode::("\"yz\""), "yz"); - } - - #[test] - fn decode_option() { - assert_eq!(decode::>("1"), Some(1)); - assert_eq!(decode::>(""), None); - assert_eq!(decode::>("null"), None); - assert_eq!(decode::>("~"), None); - } - - #[derive(Clone, Debug, PartialEq, Eq, RustcDecodable)] - struct TestStruct { - a: usize, - b: String, - } - - #[test] - fn decode_struct() { - assert_eq!(decode::("a: 1\nb: hello"), TestStruct { - a: 1, - b: "hello".to_string(), - }); - } - - #[test] - fn decode_list() { - assert_eq!(decode::>("- a\n- b"), - vec!("a".to_string(), "b".to_string())); - } - - #[test] - #[should_panic(expected="Expected sequence, got string")] - fn decode_list_error() { - decode::>("test"); - } - - #[test] - fn decode_map() { - let mut res = BTreeMap::new(); - res.insert("a".to_string(), 1); - res.insert("b".to_string(), 2); - assert_eq!(decode::>("a: 1\nb: 2"), res); - } - - - #[derive(PartialEq, Eq, RustcDecodable, Debug)] - struct TestOption { - path: Option, - } - /* - #[derive(Debug, PartialEq, Eq, RustcDecodable)] - struct TestJson { - json: AnyJson, - } - - - This test does not compile for some reason - #[test] - fn decode_json() { - let (ast, _) = parse(Rc::new("".to_string()), - "json:\n a: 1\n b: test", - |doc| { process(Default::default(), doc) }).unwrap(); - let mut warnings = vec!(); - let (tx, rx) = channel(); - let val: TestJson = { - let mut dec = YamlDecoder::new(ast, tx); - Decodable::decode(&mut dec).unwrap() - }; - warnings.extend(rx.iter()); - assert_eq!(val, TestJson { - json: AnyJson(from_str(r#"{"a": 1, "b": "test"}"#).unwrap()), - }); - assert_eq!(warnings.len(), 0); - } -*/ - - #[test] - fn decode_option_some() { - let val: TestOption = decode("path: test/value"); - assert!(val.path == Some("test/value".to_string())); - } - - #[test] - fn decode_option_none() { - let val: TestOption = decode("path:"); - assert!(val.path == None); - } - - #[test] - fn decode_option_no_key() { - let val: TestOption = decode("{}"); - assert!(val.path == None); - } - - #[derive(PartialEq, Eq, RustcDecodable)] - struct TestPath { - path: PathBuf, - } - - #[test] - fn decode_path() { - let val: TestPath = decode("path: test/dir"); - assert!(val.path == PathBuf::from("test/dir")); - } - - #[derive(PartialEq, Eq, RustcDecodable)] - struct TestPathMap { - paths: BTreeMap, - } - - #[test] - fn decode_path_map() { - let val: TestPathMap = decode("paths: {test/dir: 1}"); - let tree: BTreeMap; - tree = vec!((PathBuf::from("test/dir"), 1)).into_iter().collect(); - assert!(val.paths == tree); - } - - #[derive(PartialEq, Eq, RustcDecodable, Debug)] - #[allow(non_camel_case_types)] - enum TestEnum { - Alpha, - Beta, - beta_gamma, - Gamma(isize), - Delta(TestStruct), - Sigma(Vec), - } - - #[test] - fn test_enum_1() { - assert_eq!(decode::("Alpha"), Alpha); - } - - #[test] - fn test_enum_2() { - assert_eq!(decode::("Beta"), Beta); - } - - #[test] - fn test_enum_2_e() { - assert_eq!(decode::("beta-gamma"), beta_gamma); - } - - #[test] - fn test_enum_3() { - assert_eq!(decode::("!Beta"), Beta); - } - - #[test] - fn test_enum_4() { - assert_eq!(decode::("!Alpha"), Alpha); - } - - #[test] - fn test_enum_5() { - assert_eq!(decode::("!Gamma 5"), Gamma(5)); - } - - #[test] - fn test_enum_map() { - assert_eq!(decode::("!Delta\na: 1\nb: a"), Delta(TestStruct { - a: 1, - b: "a".to_string(), - })); - } - - #[test] - fn test_enum_map_flow() { - assert_eq!(decode::("!Delta {a: 2, b: b}"), Delta(TestStruct { - a: 2, - b: "b".to_string(), - })); - } - - #[test] - fn test_enum_seq_flow() { - assert_eq!(decode::("!Sigma [1, 2]"), Sigma(vec!(1, 2))); - } - - #[test] - fn test_enum_seq() { - assert_eq!(decode::("!Sigma\n- 1\n- 2"), Sigma(vec!(1, 2))); - } - - #[derive(PartialEq, Eq, RustcDecodable, Debug)] - struct TestStruct2 { - items: Vec, - } - - #[test] - #[should_panic(expected = "Expected sequence, got string")] - fn test_struct_items_tag() { - decode::("items:\n 'hello'"); - } - -} diff --git a/src/emit.rs b/src/emit.rs index 59cf79a..8c7f982 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -2,7 +2,8 @@ use std::io::Result as IoResult; use std::io::Error as IoError; use std::io::Write; use std::string::ToString; -use rustc_serialize::{Encodable, Encoder}; + +use serde::de::{Deserialize, Deserializer}; use super::parser::Node; @@ -384,7 +385,7 @@ pub fn emit_ast(tree: &Ast, stream: &mut Write) } /// Emit encodable object in yaml form -pub fn emit_object<'x, T: Encodable>( +pub fn emit_object<'x, T: Deserialize>( val: &T, wr: &'x mut Write) -> Result<(), IoError> { let mut encoder = Context::new(wr); @@ -392,7 +393,7 @@ pub fn emit_object<'x, T: Encodable>( } -impl<'a> Encoder for Context<'a> { +impl<'a> Deserializer for Context<'a> { type Error = IoError; fn emit_nil(&mut self) -> Result<(), IoError> { return self.emit(Opcode::Null(None, None, Null::Nothing)); diff --git a/src/errors.rs b/src/errors.rs index b4ba211..709a8cd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,8 +1,9 @@ use std::io; use std::fmt; use std::rc::Rc; +use std::error::Error as StdError; use std::slice::Iter; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::cell::RefCell; use super::tokenizer::{self, Pos}; @@ -10,13 +11,17 @@ use super::tokenizer::{self, Pos}; #[derive(Clone, Debug)] pub struct ErrorPos(String, usize, usize); +pub trait AssertSendSync: Send + Sync {} +impl AssertSendSync for Error {} +impl AssertSendSync for ErrorList {} + quick_error! { /// Single error when of parsing configuration file /// /// Usually you use `ErrorList` which embeds multiple errors encountered /// during configuration file parsing #[derive(Debug)] - pub enum Error { + pub enum Error wraps pub ErrorEnum { OpenError(filename: PathBuf, err: io::Error) { display("{}: Error reading file: {}", filename.display(), err) } @@ -42,45 +47,82 @@ quick_error! { filename=pos.0, line=pos.1, offset=pos.2, path=path, text=msg) } - Custom(message: String) { - display("{}", message) - description(message) + SerdeError(msg: String) { + display("{}", msg) + } + CustomError(pos: Option, err: Box) { + display(x) -> ("{loc}{err}", + loc=if let &Some(ref p) = pos { + format!("{filename}:{line}:{offset}: ", + filename=p.0, line=p.1, offset=p.2) + } else { + "".to_string() + }, + err=err) + cause(&**err) } } } impl ::serde::de::Error for Error { - fn custom(msg: T) -> Self { - return Error::Custom(format!("{}", msg)); + fn custom(msg: T) -> Self { + ErrorEnum::SerdeError(format!("{}", msg)).into() } } impl Error { + pub fn open_error(path: &Path, err: io::Error) -> Error { + ErrorEnum::OpenError(path.to_path_buf(), err).into() + } pub fn parse_error(pos: &Pos, message: String) -> Error { - return Error::ParseError( + ErrorEnum::ParseError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), - message); + message).into() } pub fn tokenizer_error((pos, err): (Pos, tokenizer::Error)) -> Error { - return Error::TokenizerError( + ErrorEnum::TokenizerError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), - err); + err).into() } pub fn validation_error(pos: &Pos, message: String) -> Error { - return Error::ValidationError( + ErrorEnum::ValidationError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), - message); + message).into() } pub fn decode_error(pos: &Pos, path: &String, message: String) -> Error { - return Error::DecodeError( + ErrorEnum::DecodeError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), path.clone(), - message); + message).into() } pub fn preprocess_error(pos: &Pos, message: String) -> Error { - return Error::PreprocessError( + ErrorEnum::PreprocessError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), - message); + message).into() + } + + pub fn custom(err: T) + -> Error + { + ErrorEnum::CustomError(None, Box::new(err)).into() + } + + pub fn custom_at(pos: &Pos, err: T) + -> Error + { + ErrorEnum::CustomError( + Some(ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset)), + Box::new(err)).into() + } + + pub fn downcast_ref(&self) -> Option<&T> { + match self.0 { + ErrorEnum::OpenError(_, ref e) => { + (e as &StdError).downcast_ref::() + }, + ErrorEnum::CustomError(_, ref e) => e.downcast_ref::(), + _ => None, + } } } @@ -122,7 +164,7 @@ impl fmt::Debug for ErrorList { /// /// It's exposed only to handler of include file. Use `ErrorCollector` /// to submit your errors from include file handler. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ErrorCollector(Rc>>); impl ErrorCollector { @@ -162,3 +204,14 @@ impl ErrorCollector { self.0.borrow_mut().take().unwrap() } } + +pub fn add_info(pos: &Pos, path: &String, result: Result) + -> Result +{ + match result { + Err(Error(ErrorEnum::SerdeError(e))) => { + Err(Error::decode_error(pos, path, format!("{}", e))) + } + result => result, + } +} diff --git a/src/json.rs b/src/json.rs deleted file mode 100644 index 7546d25..0000000 --- a/src/json.rs +++ /dev/null @@ -1,534 +0,0 @@ -use std::str::FromStr; - -use std::collections::BTreeMap; -use rustc_serialize::json::{ToJson, Json}; -use rustc_serialize::json::Json as J; - -use super::ast::Ast; -use super::ast::Ast as A; -use super::ast::ScalarKind::{Quoted, Plain}; - - -impl ToJson for Ast { - fn to_json(&self) -> Json { - return match *self { - A::Map(_, _, ref tm) => { - let mut ob = BTreeMap::new(); - for (k, v) in tm.iter() { - ob.insert(k.clone(), v.to_json()); - } - J::Object(ob) - }, - A::Seq(_, _, ref lst) => { - J::Array(lst.iter().map(|ref val| val.to_json()).collect()) - } - A::Null(_, _, _) => J::Null, - A::Scalar(_, _, Plain, ref val) => { - match FromStr::from_str(val) { - Ok(x) => return J::U64(x), - Err(_) => {} - } - match FromStr::from_str(val) { - Ok(x) => return J::I64(x), - Err(_) => {} - } - match FromStr::from_str(val) { - Ok(x) => return J::F64(x), - Err(_) => {} - } - if &val[..] == "~" || &val[..] == "null" { - return J::Null; - } - J::String(val.clone()) - } - A::Scalar(_, _, Quoted, ref val) => { - J::String(val.clone()) - } - }; - } -} - - -#[cfg(test)] -mod test { - use std::rc::Rc; - use rustc_serialize::json::ToJson; - use rustc_serialize::json as J; - use super::super::parser::parse; - use super::super::ast::process; - use ast::Ast; - use errors::ErrorCollector; - use {Options, Include}; - - fn assert_yaml_eq_json(a: &'static str, b: &'static str) { - let err = ErrorCollector::new(); - let ast = parse(Rc::new("".to_string()), a, - |doc| { process(&Options::default(), doc, &err) }, - ).map_err(|e| err.into_fatal(e)).unwrap(); - err.into_result(()).unwrap(); - let aj = ast.to_json(); - let bj = J::Json::from_str(&b).unwrap(); - assert_eq!(aj, bj); - } - - #[test] - fn test_to_json_1() { - assert_yaml_eq_json("1", "1"); - } - - #[test] - fn test_to_json_str_1_sq() { - assert_yaml_eq_json("'1'", r#""1""#); - } - - #[test] - fn test_to_json_str_1_dq() { - assert_yaml_eq_json(r#""1""#, r#""1""#); - } - - #[test] - fn test_to_json_str() { - assert_yaml_eq_json("test", r#""test""#); - } - - #[test] - fn test_to_json_str_quoted() { - assert_yaml_eq_json(r#""abc""#, r#""abc""#); - } - - #[test] - fn test_to_json_str_apos() { - assert_yaml_eq_json("'abc'", "\"abc\""); - } - - #[test] - fn test_to_json_map1() { - assert_yaml_eq_json("a: b", "{\"a\": \"b\"}"); - } - - #[test] - fn test_merge1() { - assert_yaml_eq_json("a: 1\n<<:\n b: 2", "{\"a\": 1, \"b\": 2}"); - } - - #[test] - fn test_multiple_merge() { - assert_yaml_eq_json("<<: [{a: 1, b: 2}, {b: 3, c: 4}]", - r#"{"a": 1, "b": 2, "c": 4}"#); - } - - #[test] - fn test_no_merge1() { - assert_yaml_eq_json("a: 1\n'<<':\n b: 2", - "{\"a\": 1, \"<<\": {\"b\": 2}}"); - } - - #[test] - fn test_to_json_map2() { - assert_yaml_eq_json("1: 2", "{\"1\": 2}"); - } - - #[test] - fn test_to_json_map3() { - assert_yaml_eq_json("'a':", "{\"a\": null}"); - } - - #[test] - fn test_to_json_map4() { - assert_yaml_eq_json("\"a\": ", "{\"a\": null}"); - } - - #[test] - fn test_to_json_map5() { - assert_yaml_eq_json("abc: ~", "{\"abc\": null}"); - } - - #[test] - fn test_to_json_1level() { - assert_yaml_eq_json("abc:\ndef:", "{\"abc\": null, \"def\": null}"); - } - - #[test] - fn test_to_json_two_keys() { - assert_yaml_eq_json("a: 1\nb: 2", "{\"a\": 1, \"b\": 2}"); - } - - #[test] - fn test_to_json_two_nested() { - assert_yaml_eq_json("a:\n b:\n c:\nd:", - r#"{"a": {"b": {"c": null}}, "d": null}"#); - } - - #[test] - fn test_to_json_nested() { - assert_yaml_eq_json("a:\n b: 2", "{\"a\": {\"b\": 2}}"); - } - - #[test] - fn test_to_json_nested_2() { - assert_yaml_eq_json("a:\n b: 2\n c: 3\nd: 4", - "{\"a\": {\"b\": 2, \"c\": 3}, \"d\": 4}"); - } - - - #[test] - fn test_to_json_list_1() { - assert_yaml_eq_json("-", "[null]"); - } - - #[test] - fn test_to_json_list_2() { - assert_yaml_eq_json("- 1", "[1]"); - } - - #[test] - fn test_to_json_list_3() { - assert_yaml_eq_json("- '1'", "[\"1\"]"); - } - - #[test] - fn test_to_json_list_4() { - assert_yaml_eq_json("-\n-", "[null, null]"); - } - - #[test] - fn test_to_json_list_5() { - assert_yaml_eq_json("- ab\n- cd", "[\"ab\", \"cd\"]"); - } - - #[test] - fn test_to_json_list_6() { - assert_yaml_eq_json("-\n -", "[[null]]"); - } - - #[test] - fn test_to_json_list_7() { - assert_yaml_eq_json("-\n- -", "[null, [null]]"); - } - - #[test] - fn test_to_json_list_8() { - assert_yaml_eq_json("-\n - a\n - b", "[[\"a\", \"b\"]]"); - } - - #[test] - fn test_to_json_list_map() { - assert_yaml_eq_json("- a:", r#"[{"a": null}]"#); - } - - #[test] - fn test_to_json_list_map2() { - assert_yaml_eq_json("- a: 1\n b: 2", r#"[{"a": 1, "b": 2}]"#); - } - - #[test] - fn test_to_json_list_map3() { - assert_yaml_eq_json("- a: 1\n- b: 2", r#"[{"a": 1}, {"b": 2}]"#); - } - - #[test] - fn test_to_json_map_list_1() { - assert_yaml_eq_json("a:\n-", r#"{"a": [null]}"#); - } - - #[test] - fn test_to_json_map_list_2() { - assert_yaml_eq_json("a:\n -", r#"{"a": [null]}"#); - } - - #[test] - fn test_flow_list_1() { - assert_yaml_eq_json("[]", "[]"); - } - - #[test] - fn test_flow_list_2() { - assert_yaml_eq_json(r#"[a]"#, r#"["a"]"#); - } - - #[test] - fn test_flow_list_3() { - assert_yaml_eq_json(r#"[a,]"#, r#"["a"]"#); - } - - #[test] - fn test_flow_list_4() { - assert_yaml_eq_json(r#"[a,b]"#, r#"["a", "b"]"#); - } - - #[test] - fn test_flow_list_5() { - assert_yaml_eq_json(r#"[[a],b]"#, r#"[["a"], "b"]"#); - } - - #[test] - fn test_flow_map_1() { - assert_yaml_eq_json("{}", "{}"); - } - - #[test] - fn test_flow_map_2() { - assert_yaml_eq_json(r#"{a: 1}"#, r#"{"a":1}"#); - } - - #[test] - fn test_flow_map_3() { - assert_yaml_eq_json(r#"{a: 1,}"#, r#"{"a":1}"#); - } - - #[test] - fn test_flow_map_4() { - assert_yaml_eq_json(r#"{a: 1,b: 2}"#, r#"{"a":1, "b":2}"#); - } - - #[test] - fn test_flow_map_5() { - assert_yaml_eq_json(r#"{a:{c: 1},b: 2}"#, r#"{"a":{"c":1}, "b": 2}"#); - } - - #[test] - fn test_flow_map_quotes_no_space() { - assert_yaml_eq_json(r#"{"a":1}"#, r#"{"a":1}"#); - } - - #[test] - fn test_combined() { - assert_yaml_eq_json("a: {}", r#"{"a":{}}"#); - } - - #[test] - fn test_nl_dquoted() { - assert_yaml_eq_json("\"a \nb\"", r#""a b""#); - } - - #[test] - fn test_nl_quoted() { - assert_yaml_eq_json("'a \nb'", r#""a b""#); - } - - #[test] - fn test_nl_plain() { - assert_yaml_eq_json("a \nb", r#""a b""#); - } - - #[test] - fn test_nl2_dquoted() { - assert_yaml_eq_json("\"a \n \n b\"", r#""a\nb""#); - } - - #[test] - fn test_nl_slash_dquoted() { - assert_yaml_eq_json("\"a \\\n \n b\"", r#""a \nb""#); - } - - #[test] - fn test_nl_slash_middle_dquoted() { - assert_yaml_eq_json("\"a \\ \n \n b\"", r#""a \nb""#); - } - - #[test] - fn test_slash_dquoted() { - assert_yaml_eq_json("\"a \\\n b\"", r#""a b""#); - } - - #[test] - fn test_slash_dquoted_nospace() { - assert_yaml_eq_json("\"a\\\n b\"", r#""ab""#); - } - - #[test] - fn test_slash_middle_dquoted() { - assert_yaml_eq_json("\"a \\ \nb\"", r#""a b""#); - } - - #[test] - fn test_nl2_quoted() { - assert_yaml_eq_json("'a \n \n b'", r#""a\nb""#); - } - - #[test] - fn test_nl2_plain() { - assert_yaml_eq_json("a \n \n b", r#""a\nb""#); - } - - #[test] - fn test_literal() { - assert_yaml_eq_json( - "a: |\n hello\n world\n", - r#"{"a": "hello\nworld\n"}"#); - } - - #[test] - fn test_literal_with_whitespace() { - assert_yaml_eq_json( - "a: | \n hello\n world\n", - r#"{"a": "hello\nworld\n"}"#); - } - - #[test] - fn test_map_and_scalar() { - assert_yaml_eq_json( - "a:\n b:\n hello\n world\n c: end", - r#"{"a": {"b": "hello world", "c": "end"}}"#); - } - - #[test] - fn yaml_words_in_list() { - assert_yaml_eq_json("- a\n b\n", r#"["a b"]"#); - } - - #[test] - fn yaml_literal_in_list() { - assert_yaml_eq_json("- |\n val\n", r#"["val\n"]"#); - } - - #[test] - fn yaml_literal_with_empty_line_in_a_list() { - assert_yaml_eq_json("- |\n val\n\n line2", r#"["val\n\nline2\n"]"#); - } - - #[test] - fn yaml_words_with_space() { - assert_yaml_eq_json(" a\nb", r#""a b""#); - } - - #[test] - fn indented_map() { - assert_yaml_eq_json(" a: 1\n b: 2\n", r#"{"a": 1, "b": 2}"#); - } - - #[test] - fn map_map() { - assert_yaml_eq_json("a: {\n b: {}}", - r#"{"a": {"b": {}}}"#); - } - - #[test] - fn test_alias() { - assert_yaml_eq_json("- &a hello\n- *a", r#"["hello", "hello"]"#); - } - - #[test] - fn test_unpack() { - assert_yaml_eq_json("- !*Unpack [[hello]]\n", r#"["hello"]"#); - } - - #[test] - fn test_multiple_alias_merge() { - assert_yaml_eq_json("- &a {hello: world, foo: bar}\n- &b {foo: 123}\n- <<: [*a, *b]", - r#"[{"hello": "world", "foo": "bar"}, {"foo": 123}, {"hello": "world", "foo": "bar"}]"#); - } - - #[test] - #[should_panic] - fn wrong_escape_incrorrect() { - assert_yaml_eq_json(r#"a: "a\.b""#, r#"{"a": "a\\.b"}"#); - } - - #[test] - fn wrong_escape_raw_incorrect() { - assert_yaml_eq_json(r#"a: a\.b"#, r#"{"a": "a\\.b"}"#); - } - - #[test] - fn wrong_escape_correct() { - assert_yaml_eq_json(r#"a: "a\\.b""#, r#"{"a": "a\\.b"}"#); - } - - #[test] - fn wrong_escape_raw_correct() { - assert_yaml_eq_json(r#"a: "a\\.b""#, r#"{"a": "a\\.b"}"#); - } - - #[test] - fn yaml_tag_null_in_map() { - assert_yaml_eq_json("x: \n a: !Tag\n b: x", - r#"{"x": {"a": null, "b": "x"}}"#); - } - - fn assert_yaml_eq_json_incl(a: &'static str, inc_data: &'static str, - b: &'static str) - { - let mut opt = Options::default(); - opt.allow_include(|pos, incl, err, opt| { - // any include is the same in example - match *incl { - Include::File { filename } => { - parse(Rc::new(filename.to_string()), inc_data, - |doc| { process(&opt, doc, err) }, - ).map_err(|e| err.add_error(e)) - .unwrap_or_else(|_| Ast::void(pos)) - } - } - }); - let err = ErrorCollector::new(); - let ast = parse(Rc::new("".to_string()), a, - |doc| { process(&opt, doc, &err) }, - ).map_err(|e| err.into_fatal(e)).unwrap(); - err.into_result(()).unwrap(); - let aj = ast.to_json(); - let bj = J::Json::from_str(&b).unwrap(); - assert_eq!(aj, bj); - } - - #[test] - fn test_incl_one() { - assert_yaml_eq_json_incl( - "x: !*Include 'y.yaml'", - "y: 1", - r#"{"x": {"y": 1}}"#); - } - - #[test] - fn test_incl_unpack() { - assert_yaml_eq_json_incl( - "- !*Unpack [!*Include 'y.yaml']\n\ - - !*Unpack [!*Include 'y.yaml']", - "[7, 8]", - r#"[7, 8, 7, 8]"#); - } - - #[test] - fn test_incl_merge() { - assert_yaml_eq_json_incl( - "x: 7\n<<: !*Include 'y.yaml'", - "y: 1", - r#"{"x": 7, "y": 1}"#); - } - - #[test] - fn test_doc_start() { - assert_yaml_eq_json("---\nx: 1", r#"{"x": 1}"#); - } - - #[test] - fn test_doc_end_map() { - assert_yaml_eq_json("x: 1\n...", r#"{"x": 1}"#); - } - - #[test] - fn test_doc_end_map_early() { - assert_yaml_eq_json("x:\n...", r#"{"x": null}"#); - } - - #[test] - fn test_doc_end_list() { - assert_yaml_eq_json("- x\n...", r#"["x"]"#); - } - - #[test] - fn test_doc_both() { - assert_yaml_eq_json("---\nx: 1\n...", r#"{"x": 1}"#); - } - - #[test] - fn test_doc_end_trailing() { - assert_yaml_eq_json("x: 1\n...\nhell@", r#"{"x": 1}"#); - } - - #[test] - fn test_doc_end_trailing2() { - assert_yaml_eq_json("- t\n...\nhell@", r#"["t"]"#); - } - -} diff --git a/src/lib.rs b/src/lib.rs index 79e3446..5fa9035 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,62 +4,65 @@ //! //! ```rust,ignore # for some reason this crashes compiler //! extern crate quire; -//! extern crate rustc_serialize; +//! #[macro_use] extern crate serde_derive; //! use quire::{parse_config, Options}; //! use quire::validate::{Structure, Scalar}; //! -//! #[derive(RustcDecodable)] +//! #[derive(Deserialize)] +//! #[allow(dead_code)] //! struct Config { //! item1: String, //! item2: Option, //! } //! -//! fn validator<'static>() -> Structure<'static> { +//! fn validator() -> Structure<'static> { //! Structure::new() //! .member("item1", Scalar::new()) //! .member("item2", Scalar::new().optional()) //! } //! -//! let cfg: Config; -//! cfg = parse_config("config.yaml", &validator(), &Options::default()) -//! .expect("valid config"); +//! fn work(cfg: &Config) { +//! println!("item1 is {}.", cfg.item1); +//! //intln!("item2 is {}.", cfg.item2); +//! // hey, this is just demonstration code ... +//! } //! +//! fn main() { +//! let cfg: Config; +//! cfg = parse_config("config.yaml", &validator(), &Options::default()) +//! .expect("valid config"); +//! work(&cfg) +//! } //! ``` //! -//#![warn(missing_docs)] -#![allow(dead_code)] -#![allow(unused_variables)] +#![warn(missing_debug_implementations)] -extern crate serde; -extern crate rustc_serialize; -extern crate regex; -extern crate humantime; extern crate humannum; extern crate num_traits; -#[macro_use] extern crate quick_error; +extern crate serde; #[cfg(test)] #[macro_use] extern crate serde_derive; +#[cfg(test)] extern crate serde_humantime; +#[cfg(test)] extern crate serde_json; +#[cfg(test)] extern crate serde_transcode; +#[macro_use] extern crate quick_error; pub use sky::{parse_config, parse_string}; pub use options::{Options, Include}; pub use errors::{Error, ErrorList, ErrorCollector}; pub use tokenizer::{Pos}; pub use parser::{parse as raw_parse}; -pub use emit::{emit_ast, emit_object}; -pub use special_cases::De; +//pub use emit::{emit_ast, emit_object}; mod chars; mod errors; mod tokenizer; mod options; mod parser; -mod json; -mod emit; -pub mod ast; -mod decode; +//mod emit; mod de; -pub mod validate; mod sky; -mod special_cases; +pub mod ast; +pub mod validate; #[cfg(test)] mod test_errors; -#[cfg(test)] mod test_util; +#[cfg(test)] mod test_transcode; diff --git a/src/options.rs b/src/options.rs index 83620e9..741e12a 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,3 +1,5 @@ +use std::fmt; + use ast::Ast; use errors::{Error, ErrorCollector}; use tokenizer::Pos; @@ -7,17 +9,25 @@ pub type IncludeHandler<'a> = Fn(&Pos, &Include, &ErrorCollector, &Options) -> Ast + 'a; /// The kind of include tag that encountered in config +#[derive(Debug)] pub enum Include<'a> { /// Looks like `!Include some/file.yaml` File { filename: &'a str }, // TODO(tailhook) // /// Looks like `!*Include some/file.yaml:some_key` // SubKey { filename: &'a str, key: &'a str }, - // /// Looks like `!*IncludeSeq some/*.yaml` - // Sequence { directory: &'a str, prefix: &'a str, suffix: &'a str }, - // /// Looks like `!*IncludeMap some/*.yaml`. - // /// Everything matched by star is used as a key - // Mapping { directory: &'a str, prefix: &'a str, suffix: &'a str }, + /// Looks like `!*IncludeSeq some/*.yaml` + /// + /// It's expected that included files are sorted (both `glob` and + /// `capturing_glob` support that). + Sequence { pattern: &'a str }, + /// Looks like `!*IncludeMap some/(*).yaml`. + /// + /// Everything in parenthesis should be used as a key + /// Use `capturing_glob` crate to parse and match files + Mapping { pattern: &'a str }, + #[doc(hidden)] + __Nonexhaustive, } /// Options for parsing configuration file @@ -62,3 +72,10 @@ impl<'a> Options<'a> { self } } + +impl<'a> fmt::Debug for Options<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Options") + .finish() + } +} diff --git a/src/parser.rs b/src/parser.rs index ffa13e7..3fd2745 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -144,13 +144,22 @@ fn plain_value<'a>(tok: &Token<'a>) -> Result { Some('L') => res.push('\u{2028}'), Some('P') => res.push('\u{2029}'), Some('x') => { - unimplemented!(); + // TODO(tailhook) support hex escapes + return Err(Error::parse_error(&tok.start, + "hex escapes aren't supported yet" + .into())); }, Some('u') => { - unimplemented!(); + // TODO(tailhook) support unicode escapes + return Err(Error::parse_error(&tok.start, + "unicode escapes aren't supported yet" + .into())); }, Some('U') => { - unimplemented!(); + // TODO(tailhook) support unicode escapes + return Err(Error::parse_error(&tok.start, + "extended unicode escapes \ + aren't supported yet".into())); }, Some('\n') => { escaped_space = res.len(); @@ -194,7 +203,7 @@ fn plain_value<'a>(tok: &Token<'a>) -> Result { } } T::Literal => { - let mut lines = tok.value.split('\n'); + let mut lines = tok.value.lines(); let fline = lines.next().unwrap(); if fline.trim() == "|" { let mut indent = 0; @@ -211,19 +220,25 @@ fn plain_value<'a>(tok: &Token<'a>) -> Result { res.push('\n'); } } else { - unimplemented!(); + // TODO(tailhook) better support of block literals + return Err(Error::parse_error(&tok.start, + "only bare block literals are supported yet".into())); } } T::Folded => { - unimplemented!(); + // TODO(tailhook) support block literals + return Err(Error::parse_error(&tok.start, + "folded literals aren't supported yet".into())); } _ => unreachable!(), } return Ok(res); } +#[derive(Debug)] pub struct Directive<'a>(&'a Token<'a>); +#[derive(Debug)] pub struct Document<'a> { pub directives: Vec>, pub root: Node<'a>, @@ -619,8 +634,11 @@ fn _parse_node<'x>(tokiter: &mut TokenIter<'x>, aliases: &mut Aliases<'x>) tokiter.next(); indent = true; } - let anchor = maybe_parse_anchor(tokiter); - let mut tag = maybe_parse_tag(tokiter); + let mut anchor = maybe_parse_anchor(tokiter); + let tag = maybe_parse_tag(tokiter); + if anchor.is_none() { + anchor = maybe_parse_anchor(tokiter); + } tok = tokiter.peek(0); if !indent && tok.kind == T::Indent { // Otherwise indent is after tag tokiter.next(); @@ -659,6 +677,16 @@ fn _parse_node<'x>(tokiter: &mut TokenIter<'x>, aliases: &mut Aliases<'x>) parse_flow_map(tokiter, aliases, tag, anchor) } T::Alias => { + if let Some(tag) = tag { + return Err(Error::parse_error(&tok.start, + format!("Alias can't be preceded by tag (remove `{}`)", + tag))); + } + if let Some(anchor) = anchor { + return Err(Error::parse_error(&tok.start, + format!("Alias can't be preceded by anchor (remove `&{}`)", + anchor))); + } tokiter.next(); match aliases.get(&tok.value[1..]) { Some(x) => Ok(x.clone()), diff --git a/src/sky.rs b/src/sky.rs index b8e05b5..32eadde 100644 --- a/src/sky.rs +++ b/src/sky.rs @@ -2,41 +2,43 @@ use std::rc::Rc; use std::io::Read; use std::fs::File; use std::path::Path; -use rustc_serialize::{Decodable}; +use serde::de::{Deserialize}; -use super::ast; -pub use super::errors::{Error, ErrorList}; +use ast; +use de::Deserializer; use super::errors::ErrorCollector; use super::parser::parse; -use super::decode::YamlDecoder; use super::validate::Validator; use {Options}; +use errors::{ErrorEnum, ErrorList}; /// Parse configuration from a file -pub fn parse_config>( +pub fn parse_config<'x, T: Deserialize<'x>, P: AsRef>( filename: P, validator: &Validator, options: &Options) -> Result { let filename = filename.as_ref(); let err = ErrorCollector::new(); let mut file = File::open(filename).map_err( - |e| err.into_fatal(Error::OpenError(filename.to_path_buf(), e)))?; + |e| err.into_fatal(ErrorEnum::OpenError( + filename.to_path_buf(), e).into()))?; let mut body = String::new(); file.read_to_string(&mut body).map_err( - |e| err.into_fatal(Error::OpenError(filename.to_path_buf(), e)))?; + |e| err.into_fatal(ErrorEnum::OpenError( + filename.to_path_buf(), e).into()))?; let filename = Rc::new(format!("{}", filename.display())); let ast = parse(filename, &body, |doc| { ast::process(options, doc, &err) } ).map_err(|e| err.into_fatal(e))?; let ast = validator.validate(ast, &err); - let res = Decodable::decode(&mut YamlDecoder::new(ast, &err)) + let res = Deserialize::deserialize(&mut Deserializer::new(&ast)) .map_err(|e| err.into_fatal(e))?; return err.into_result(res); } /// Parse configuration from a string -pub fn parse_string(filename: &str, data: &str, +pub fn parse_string<'x, T: Deserialize<'x>>(filename: &str, data: &str, validator: &Validator, options: &Options) -> Result { @@ -45,7 +47,7 @@ pub fn parse_string(filename: &str, data: &str, |doc| { ast::process(options, doc, &err) } ).map_err(|e| err.into_fatal(e))?; let ast = validator.validate(ast, &err); - let res = Decodable::decode(&mut YamlDecoder::new(ast, &err)) + let res = Deserialize::deserialize(&mut Deserializer::new(&ast)) .map_err(|e| err.into_fatal(e))?; return err.into_result(res); } diff --git a/src/special_cases/duration.rs b/src/special_cases/duration.rs deleted file mode 100644 index 4d491c5..0000000 --- a/src/special_cases/duration.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::time::Duration; - -use humantime::parse_duration; -use rustc_serialize::{Decoder, Decodable}; - -use super::De; - -impl Decodable for De { - fn decode(dec: &mut D) - -> Result, D::Error> - { - let value = dec.read_str()?; - parse_duration(&value) - .map(De) - .map_err(|e| dec.error( - &format!("error decoding duration {:?}: {}", value, e))) - } -} - -#[cfg(test)] -mod test { - - use std::time::Duration; - use test_util::decode; - use De; - - #[test] - fn decode_15min() { - assert_eq!(decode::>("15 min"), - De::from(Duration::new(900, 0))); - } -} diff --git a/src/special_cases/mod.rs b/src/special_cases/mod.rs deleted file mode 100644 index 046105a..0000000 --- a/src/special_cases/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! This module contains a wrapper for types that is used to provide -//! custom implementation for deserialization for things that either -//! have no Decodable implemented or maybe have wrong -//! -use std::ops::Deref; - -mod regex; -mod duration; - -/// A wrapper around type that has Decodable implementation -/// -/// This is a temporary solution that will go with the releasing of -/// macros 1.1 (and migration to serde) I think. -/// -/// There are many ways to convert this to the real value: -/// -/// * Deref value `*x` -/// * Default converter `x.into()` or `x.clone().into()` -/// * Convert to reference `x.as_ref()` -/// -/// I.e. it many cases it should work seamlessly instead of the reference -/// to encopassed original type -#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] -pub struct De(T); - -impl De { - pub fn new(val: T) -> De { - De(val) - } -} - -impl Deref for De { - type Target = T; - fn deref(&self) -> &T { - &self.0 - } -} - -impl AsRef for De - where T: AsRef -{ - fn as_ref(&self) -> &U { - self.0.as_ref() - } -} - -impl From for De { - fn from(value: T) -> De { - De(value) - } -} diff --git a/src/special_cases/regex.rs b/src/special_cases/regex.rs deleted file mode 100644 index 83e5dc1..0000000 --- a/src/special_cases/regex.rs +++ /dev/null @@ -1,31 +0,0 @@ -use regex::Regex; -use rustc_serialize::{Decoder, Decodable}; - -use super::De; - -impl Decodable for De { - fn decode(dec: &mut D) - -> Result, D::Error> - { - let value = dec.read_str()?; - Regex::new(&value) - .map(De) - .map_err(|e| dec.error( - &format!("error decoding regex {:?}: {}", value, e))) - } -} - -#[cfg(test)] -mod test { - - use regex::Regex; - use test_util::decode; - use De; - - #[test] - fn decode_simple_regex() { - let re = decode::>("a..a"); - assert!(re.is_match("abba")); - assert!(!re.is_match("baab")); - } -} diff --git a/src/test_errors.rs b/src/test_errors.rs index 9b073bc..f4f7841 100644 --- a/src/test_errors.rs +++ b/src/test_errors.rs @@ -1,45 +1,47 @@ use std::rc::Rc; -use rustc_serialize::Decodable; +use ast::process; +use de::Deserializer; +use errors::ErrorCollector; +use parser::parse; +use serde::Deserialize; use {Options}; -use super::decode::YamlDecoder; -use super::ast::process; -use super::parser::parse; -use super::errors::ErrorCollector; -#[derive(RustcDecodable, PartialEq, Eq, Debug)] +#[derive(Deserialize, PartialEq, Eq, Debug)] struct Struct1 { list: Vec, } -#[derive(RustcDecodable, PartialEq, Eq, Debug)] +#[derive(Deserialize, PartialEq, Eq, Debug)] struct Struct2 { value: String, } -fn decode_struct(data: &str) -> Result { +fn decode<'x, T: Deserialize<'x>>(data: &str) -> Result { let err = ErrorCollector::new(); - parse( + let ast = parse( Rc::new("".to_string()), data, |doc| { process(&Options::default(), doc, &err) } - ).map_err(|e| err.into_fatal(e)) - .and_then(|ast| { - Decodable::decode(&mut YamlDecoder::new(ast, &err)) - .map_err(|e| err.into_fatal(e)) - .and_then(|v| err.into_result(v)) - }) - .map_err(|e| format!("{}", e)) + ).map_err(|e| err.into_fatal(e).to_string())?; + T::deserialize(&mut Deserializer::new(&ast)) + .map_err(|e| err.into_fatal(e)) + .and_then(|v| err.into_result(v)) + .map_err(|e| e.to_string()) +} + +fn decode_struct(data: &str) -> Result { + decode(data) } #[test] fn test_path() { assert_eq!(decode_struct("list:\n- {}"), - Err(":2:3: Decode error at .list[0].value: \ - Expected scalar, got Null\n".to_string())); + Err(":2:3: Decode error at .list[0]: \ + missing field `value`\n".to_string())); } #[test] diff --git a/src/test_transcode.rs b/src/test_transcode.rs new file mode 100644 index 0000000..8fbfbe6 --- /dev/null +++ b/src/test_transcode.rs @@ -0,0 +1,562 @@ +use std::rc::Rc; + +use serde_json::Value; +use serde_json::ser::Serializer; +use serde_json::de::{from_str, from_slice}; +use serde_transcode::transcode; + +use ast::{Ast, Tag}; +use ast::process; +use de::Deserializer; +use errors::ErrorCollector; +use parser::parse; + +use {Options, Include}; + +fn assert_yaml_eq_json(a: &'static str, b: &'static str) { + let err = ErrorCollector::new(); + let ast = parse(Rc::new("".to_string()), a, + |doc| { process(&Options::default(), doc, &err) }, + ).map_err(|e| err.into_fatal(e)).unwrap(); + err.into_result(()).unwrap(); + let mut de = Deserializer::new(&ast); + let mut buf = Vec::with_capacity(100); + transcode(&mut de, &mut Serializer::new(&mut buf)).unwrap(); + let aj: Value = from_slice(&buf).unwrap(); + let bj: Value = from_str(b).unwrap(); + assert_eq!(aj, bj); +} + +#[test] +fn test_to_json_1() { + assert_yaml_eq_json("1", r#""1""#); +} + +#[test] +fn test_to_json_str_1_sq() { + assert_yaml_eq_json("'1'", r#""1""#); +} + +#[test] +fn test_to_json_str_1_dq() { + assert_yaml_eq_json(r#""1""#, r#""1""#); +} + +#[test] +fn test_to_json_str() { + assert_yaml_eq_json("test", r#""test""#); +} + +#[test] +fn test_to_json_str_quoted() { + assert_yaml_eq_json(r#""abc""#, r#""abc""#); +} + +#[test] +fn test_to_json_str_apos() { + assert_yaml_eq_json("'abc'", "\"abc\""); +} + +#[test] +fn test_to_json_map1() { + assert_yaml_eq_json("a: b", "{\"a\": \"b\"}"); +} + +#[test] +fn test_merge1() { + assert_yaml_eq_json("a: 1\n<<:\n b: 2", "{\"a\": \"1\", \"b\": \"2\"}"); +} + +#[test] +fn test_multiple_merge() { + assert_yaml_eq_json("<<: [{a: 1, b: 2}, {b: 3, c: 4}]", + r#"{"a": "1", "b": "2", "c": "4"}"#); +} + +#[test] +fn test_no_merge1() { + assert_yaml_eq_json("a: 1\n'<<':\n b: 2", + "{\"a\": \"1\", \"<<\": {\"b\": \"2\"}}"); +} + +#[test] +fn test_to_json_map2() { + assert_yaml_eq_json("1: 2", "{\"1\": \"2\"}"); +} + +#[test] +fn test_to_json_map3() { + assert_yaml_eq_json("'a':", "{\"a\": null}"); +} + +#[test] +fn test_to_json_map4() { + assert_yaml_eq_json("\"a\": ", "{\"a\": null}"); +} + +#[test] +fn test_to_json_map5() { + assert_yaml_eq_json("abc: ~", "{\"abc\": null}"); +} + +#[test] +fn test_to_json_1level() { + assert_yaml_eq_json("abc:\ndef:", "{\"abc\": null, \"def\": null}"); +} + +#[test] +fn test_to_json_two_keys() { + assert_yaml_eq_json("a: 1\nb: 2", "{\"a\": \"1\", \"b\": \"2\"}"); +} + +#[test] +fn test_to_json_two_nested() { + assert_yaml_eq_json("a:\n b:\n c:\nd:", + r#"{"a": {"b": {"c": null}}, "d": null}"#); +} + +#[test] +fn test_to_json_nested() { + assert_yaml_eq_json("a:\n b: 2", "{\"a\": {\"b\": \"2\"}}"); +} + +#[test] +fn test_to_json_nested_2() { + assert_yaml_eq_json("a:\n b: 2\n c: 3\nd: 4", + "{\"a\": {\"b\": \"2\", \"c\": \"3\"}, \"d\": \"4\"}"); +} + + +#[test] +fn test_to_json_list_1() { + assert_yaml_eq_json("-", "[null]"); +} + +#[test] +fn test_to_json_list_2() { + assert_yaml_eq_json("- 1", "[\"1\"]"); +} + +#[test] +fn test_to_json_list_3() { + assert_yaml_eq_json("- '1'", "[\"1\"]"); +} + +#[test] +fn test_to_json_list_4() { + assert_yaml_eq_json("-\n-", "[null, null]"); +} + +#[test] +fn test_to_json_list_5() { + assert_yaml_eq_json("- ab\n- cd", "[\"ab\", \"cd\"]"); +} + +#[test] +fn test_to_json_list_6() { + assert_yaml_eq_json("-\n -", "[[null]]"); +} + +#[test] +fn test_to_json_list_7() { + assert_yaml_eq_json("-\n- -", "[null, [null]]"); +} + +#[test] +fn test_to_json_list_8() { + assert_yaml_eq_json("-\n - a\n - b", "[[\"a\", \"b\"]]"); +} + +#[test] +fn test_to_json_list_map() { + assert_yaml_eq_json("- a:", r#"[{"a": null}]"#); +} + +#[test] +fn test_to_json_list_map2() { + assert_yaml_eq_json("- a: 1\n b: 2", r#"[{"a": "1", "b": "2"}]"#); +} + +#[test] +fn test_to_json_list_map3() { + assert_yaml_eq_json("- a: 1\n- b: 2", r#"[{"a": "1"}, {"b": "2"}]"#); +} + +#[test] +fn test_to_json_map_list_1() { + assert_yaml_eq_json("a:\n-", r#"{"a": [null]}"#); +} + +#[test] +fn test_to_json_map_list_2() { + assert_yaml_eq_json("a:\n -", r#"{"a": [null]}"#); +} + +#[test] +fn test_flow_list_1() { + assert_yaml_eq_json("[]", "[]"); +} + +#[test] +fn test_flow_list_2() { + assert_yaml_eq_json(r#"[a]"#, r#"["a"]"#); +} + +#[test] +fn test_flow_list_3() { + assert_yaml_eq_json(r#"[a,]"#, r#"["a"]"#); +} + +#[test] +fn test_flow_list_4() { + assert_yaml_eq_json(r#"[a,b]"#, r#"["a", "b"]"#); +} + +#[test] +fn test_flow_list_5() { + assert_yaml_eq_json(r#"[[a],b]"#, r#"[["a"], "b"]"#); +} + +#[test] +fn test_flow_map_1() { + assert_yaml_eq_json("{}", "{}"); +} + +#[test] +fn test_flow_map_2() { + assert_yaml_eq_json(r#"{a: 1}"#, r#"{"a":"1"}"#); +} + +#[test] +fn test_flow_map_3() { + assert_yaml_eq_json(r#"{a: 1,}"#, r#"{"a":"1"}"#); +} + +#[test] +fn test_flow_map_4() { + assert_yaml_eq_json(r#"{a: 1,b: 2}"#, r#"{"a":"1", "b":"2"}"#); +} + +#[test] +fn test_flow_map_5() { + assert_yaml_eq_json(r#"{a:{c: 1},b: 2}"#, r#"{"a":{"c":"1"}, "b": "2"}"#); +} + +#[test] +fn test_flow_map_quotes_no_space() { + assert_yaml_eq_json(r#"{"a":1}"#, r#"{"a":"1"}"#); +} + +#[test] +fn test_combined() { + assert_yaml_eq_json("a: {}", r#"{"a":{}}"#); +} + +#[test] +fn test_nl_dquoted() { + assert_yaml_eq_json("\"a \nb\"", r#""a b""#); +} + +#[test] +fn test_nl_quoted() { + assert_yaml_eq_json("'a \nb'", r#""a b""#); +} + +#[test] +fn test_nl_plain() { + assert_yaml_eq_json("a \nb", r#""a b""#); +} + +#[test] +fn test_nl2_dquoted() { + assert_yaml_eq_json("\"a \n \n b\"", r#""a\nb""#); +} + +#[test] +fn test_nl_slash_dquoted() { + assert_yaml_eq_json("\"a \\\n \n b\"", r#""a \nb""#); +} + +#[test] +fn test_nl_slash_middle_dquoted() { + assert_yaml_eq_json("\"a \\ \n \n b\"", r#""a \nb""#); +} + +#[test] +fn test_slash_dquoted() { + assert_yaml_eq_json("\"a \\\n b\"", r#""a b""#); +} + +#[test] +fn test_slash_dquoted_nospace() { + assert_yaml_eq_json("\"a\\\n b\"", r#""ab""#); +} + +#[test] +fn test_slash_middle_dquoted() { + assert_yaml_eq_json("\"a \\ \nb\"", r#""a b""#); +} + +#[test] +fn test_nl2_quoted() { + assert_yaml_eq_json("'a \n \n b'", r#""a\nb""#); +} + +#[test] +fn test_nl2_plain() { + assert_yaml_eq_json("a \n \n b", r#""a\nb""#); +} + +#[test] +fn test_literal() { + assert_yaml_eq_json( + "a: |\n hello\n world\n", + r#"{"a": "hello\nworld\n"}"#); +} + +#[test] +fn test_literal_with_whitespace() { + assert_yaml_eq_json( + "a: | \n hello\n world\n", + r#"{"a": "hello\nworld\n"}"#); +} + +#[test] +fn test_map_and_scalar() { + assert_yaml_eq_json( + "a:\n b:\n hello\n world\n c: end", + r#"{"a": {"b": "hello world", "c": "end"}}"#); +} + +#[test] +fn yaml_words_in_list() { + assert_yaml_eq_json("- a\n b\n", r#"["a b"]"#); +} + +#[test] +fn yaml_literal_in_list() { + assert_yaml_eq_json("- |\n val\n", r#"["val\n"]"#); +} + +#[test] +fn yaml_literal_with_empty_line_in_a_list() { + assert_yaml_eq_json("- |\n val\n\n line2", r#"["val\n\nline2\n"]"#); +} + +#[test] +fn yaml_literal_with_empty_line_and_bad_newlines_in_a_list() { + assert_yaml_eq_json("- |\r\n val\r\n\r\n line2", r#"["val\n\nline2\n"]"#); +} + +#[test] +fn yaml_words_with_space() { + assert_yaml_eq_json(" a\nb", r#""a b""#); +} + +#[test] +fn indented_map() { + assert_yaml_eq_json(" a: 1\n b: 2\n", r#"{"a": "1", "b": "2"}"#); +} + +#[test] +fn map_map() { + assert_yaml_eq_json("a: {\n b: {}}", + r#"{"a": {"b": {}}}"#); +} + +#[test] +fn test_alias() { + assert_yaml_eq_json("- &a hello\n- *a", r#"["hello", "hello"]"#); +} + +#[test] +#[should_panic(expected="Alias can't be preceded by tag (remove `!test`)")] +fn test_tag_alias() { + assert_yaml_eq_json("- &a hello\n- !test *a", r#"["hello", "hello"]"#); +} + +#[test] +#[should_panic(expected="Alias can't be preceded by anchor (remove `&x`)")] +fn test_anchor_alias() { + assert_yaml_eq_json("- &a hello\n- &x *a", r#"["hello", "hello"]"#); +} + +#[test] +fn test_unpack() { + assert_yaml_eq_json("- !*Unpack [[hello]]\n", r#"["hello"]"#); +} + +#[test] +fn test_multiple_alias_merge() { + assert_yaml_eq_json("- &a {hello: world, foo: bar}\n- &b {foo: 123}\n- <<: [*a, *b]", + r#"[{"hello": "world", "foo": "bar"}, {"foo": "123"}, {"hello": "world", "foo": "bar"}]"#); +} + +#[test] +#[should_panic] +fn wrong_escape_incrorrect() { + assert_yaml_eq_json(r#"a: "a\.b""#, r#"{"a": "a\\.b"}"#); +} + +#[test] +fn wrong_escape_raw_incorrect() { + assert_yaml_eq_json(r#"a: a\.b"#, r#"{"a": "a\\.b"}"#); +} + +#[test] +fn wrong_escape_correct() { + assert_yaml_eq_json(r#"a: "a\\.b""#, r#"{"a": "a\\.b"}"#); +} + +#[test] +fn wrong_escape_raw_correct() { + assert_yaml_eq_json(r#"a: "a\\.b""#, r#"{"a": "a\\.b"}"#); +} + +#[test] +fn yaml_tag_null_in_map() { + assert_yaml_eq_json("x: \n a: !Tag\n b: x", + r#"{"x": {"a": null, "b": "x"}}"#); +} + +#[test] +fn yaml_anchor_tag() { + assert_yaml_eq_json("x: &x !Tag y", r#"{"x": "y"}"#); +} + +#[test] +fn yaml_tag_anchor() { + assert_yaml_eq_json("x: !Tag &x y", r#"{"x": "y"}"#); +} + +fn assert_yaml_eq_json_incl(a: &'static str, inc_data: &'static str, + b: &'static str) +{ + let mut opt = Options::default(); + opt.allow_include(|pos, incl, err, opt| { + // any include is the same in example + match *incl { + Include::File { filename } => { + parse(Rc::new(filename.to_string()), inc_data, + |doc| { process(&opt, doc, err) }, + ).map_err(|e| err.add_error(e)) + .unwrap_or_else(|_| Ast::void(pos)) + } + Include::Sequence { .. } => { + let inc1 = parse("inc1.yaml".to_string().into(), inc_data, + |doc| { process(&opt, doc, err) }, + ).map_err(|e| err.add_error(e)) + .unwrap_or_else(|_| Ast::void(pos)); + let inc2 = parse("inc2.yaml".to_string().into(), inc_data, + |doc| { process(&opt, doc, err) }, + ).map_err(|e| err.add_error(e)) + .unwrap_or_else(|_| Ast::void(pos)); + Ast::Seq(pos.clone(), Tag::NonSpecific, + vec![inc1, inc2]) + } + Include::Mapping { .. } => { + let inc1 = parse("inc1.yaml".to_string().into(), inc_data, + |doc| { process(&opt, doc, err) }, + ).map_err(|e| err.add_error(e)) + .unwrap_or_else(|_| Ast::void(pos)); + let inc2 = parse("inc2.yaml".to_string().into(), inc_data, + |doc| { process(&opt, doc, err) }, + ).map_err(|e| err.add_error(e)) + .unwrap_or_else(|_| Ast::void(pos)); + Ast::Map(pos.clone(), Tag::NonSpecific, vec![ + ("inc1".into(), inc1), + ("inc2".into(), inc2), + ].into_iter().collect()) + } + _ => unimplemented!(), + } + }); + let err = ErrorCollector::new(); + let ast = parse(Rc::new("".to_string()), a, + |doc| { process(&opt, doc, &err) }, + ).map_err(|e| err.into_fatal(e)).unwrap(); + err.into_result(()).unwrap(); + let mut de = Deserializer::new(&ast); + let mut buf = Vec::with_capacity(100); + transcode(&mut de, &mut Serializer::new(&mut buf)).unwrap(); + let aj: Value = from_slice(&buf).unwrap(); + let bj: Value = from_str(b).unwrap(); + assert_eq!(aj, bj); +} + +#[test] +fn test_incl_one() { + assert_yaml_eq_json_incl( + "x: !*Include 'y.yaml'", + "y: 1", + r#"{"x": {"y": "1"}}"#); +} + +#[test] +fn test_incl_unpack() { + assert_yaml_eq_json_incl( + "- !*Unpack [!*Include 'y.yaml']\n\ + - !*Unpack [!*Include 'y.yaml']", + "[7, 8]", + r#"["7", "8", "7", "8"]"#); +} + +#[test] +fn test_incl_merge() { + assert_yaml_eq_json_incl( + "x: 7\n<<: !*Include 'y.yaml'", + "y: 1", + r#"{"x": "7", "y": "1"}"#); +} + +#[test] +fn test_incl_list() { + assert_yaml_eq_json_incl( + "x: !*IncludeSeq '*.yaml'", + "y: 1", + r#"{"x": [{"y": "1"}, {"y": "1"}]}"#); +} + +#[test] +fn test_incl_map() { + assert_yaml_eq_json_incl( + "x: !*IncludeMap '(*).yaml'", + "y: 1", + r#"{"x": {"inc1": {"y": "1"}, "inc2": {"y": "1"}}}"#); +} + +#[test] +fn test_doc_start() { + assert_yaml_eq_json("---\nx: 1", r#"{"x": "1"}"#); +} + +#[test] +fn test_doc_end_map() { + assert_yaml_eq_json("x: 1\n...", r#"{"x": "1"}"#); +} + +#[test] +fn test_doc_end_map_early() { + assert_yaml_eq_json("x:\n...", r#"{"x": null}"#); +} + +#[test] +fn test_doc_end_list() { + assert_yaml_eq_json("- x\n...", r#"["x"]"#); +} + +#[test] +fn test_doc_both() { + assert_yaml_eq_json("---\nx: 1\n...", r#"{"x": "1"}"#); +} + +#[test] +fn test_doc_end_trailing() { + assert_yaml_eq_json("x: 1\n...\nhell@", r#"{"x": "1"}"#); +} + +#[test] +fn test_doc_end_trailing2() { + assert_yaml_eq_json("- t\n...\nhell@", r#"["t"]"#); +} + diff --git a/src/test_util.rs b/src/test_util.rs deleted file mode 100644 index 1ae5399..0000000 --- a/src/test_util.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::rc::Rc; -use std::path::PathBuf; -use std::collections::BTreeMap; -use rustc_serialize::Decodable; - -use decode::YamlDecoder; -use parser::parse; -use ast::process; -use errors::ErrorCollector; - -use {Options}; - -pub fn decode(data: &str) -> T { - let err = ErrorCollector::new(); - let ast = parse( - Rc::new("".to_string()), - data, - |doc| { process(&Options::default(), doc, &err) } - ).map_err(|e| err.into_fatal(e)).unwrap(); - Decodable::decode(&mut YamlDecoder::new(ast, &err)) - .map_err(|e| err.into_fatal(e)) - .unwrap() -} diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 1d0eeb0..6e0c2fb 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -88,6 +88,7 @@ impl Display for Pos { } } +#[derive(Debug)] pub struct Token<'tok> { pub kind: TokenType, pub start: Pos, diff --git a/src/validate.rs b/src/validate.rs index 1a0b051..b6539c0 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -6,7 +6,7 @@ //! AST, but it works on the AST level, so it must put something that decoder //! is able to decode in the result. -use std::fmt::{Display}; +use std::fmt::{Display, Debug}; use std::path::{PathBuf, Path, Component}; use std::collections::{BTreeMap, HashSet}; @@ -22,7 +22,7 @@ use super::ast::ScalarKind::{Quoted, Plain}; /// The trait every validator implements -pub trait Validator { +pub trait Validator: Debug { fn validate(&self, ast: Ast, err: &ErrorCollector) -> Ast; fn default(&self, pos: Pos) -> Option; } @@ -39,8 +39,8 @@ pub trait Validator { /// But some of the scalars might have better validators, for example /// `Numeric` has minimum and maximum value as well as decodes human-friendly /// unit values +#[derive(Debug)] pub struct Scalar { - descr: Option, optional: bool, default: Option, min_length: Option, @@ -50,7 +50,6 @@ pub struct Scalar { impl Scalar { pub fn new() -> Scalar { Scalar { - descr: None, optional: false, default: None, min_length: None, @@ -117,8 +116,8 @@ impl Validator for Scalar { /// /// Similar to `Scalar` but validates that value is a number and also allows /// limit the range of the value. +#[derive(Debug)] pub struct Numeric { - descr: Option, optional: bool, default: Option, min: Option, @@ -128,7 +127,6 @@ pub struct Numeric { impl Numeric { pub fn new() -> Numeric { Numeric { - descr: None, optional: false, default: None, min: None, @@ -203,8 +201,8 @@ impl Validator for Numeric { /// Directory validator /// /// Similar to `Scalar` but also allows to force absolute or relative paths +#[derive(Debug)] pub struct Directory { - descr: Option, optional: bool, default: Option, absolute: Option, @@ -213,7 +211,6 @@ pub struct Directory { impl Directory { pub fn new() -> Directory { Directory { - descr: None, optional: false, default: None, absolute: None, @@ -299,8 +296,8 @@ impl Validator for Directory { /// the structure. This feature is useful to upgrade scalar value to /// a structure maintaining backwards compatiblity as well as for configuring /// common case more easily. +#[derive(Debug)] pub struct Structure<'a> { - descr: Option, members: Vec<(String, Box)>, optional: bool, from_scalar: Option BTreeMap>, @@ -309,7 +306,6 @@ pub struct Structure<'a> { impl<'a> Structure<'a> { pub fn new() -> Structure<'a> { Structure { - descr: None, members: Vec::new(), optional: false, from_scalar: None, @@ -417,8 +413,8 @@ impl<'a> Validator for Structure<'a> { /// one enum field is supported. Structure enums `enum T { a { x: u8 }` are /// equivalent to an option with that struct as single value /// `struct A { x: u8 }; enum T { a(A) }` +#[derive(Debug)] pub struct Enum<'a> { - descr: Option, options: Vec<(String, Box)>, optional: bool, default_tag: Option, @@ -429,7 +425,6 @@ pub struct Enum<'a> { impl<'a> Enum<'a> { pub fn new() -> Enum<'a> { Enum { - descr: None, options: Vec::new(), optional: false, default_tag: None, @@ -539,8 +534,8 @@ impl<'a> Validator for Enum<'a> { /// /// This type has type for a key and value and also can be converted /// from scalar as shortcut. +#[derive(Debug)] pub struct Mapping<'a> { - descr: Option, key_element: Box, value_element: Box, from_scalar: Option BTreeMap>, @@ -551,7 +546,6 @@ impl<'a> Mapping<'a> { -> Mapping<'a> { Mapping { - descr: None, key_element: Box::new(key), value_element: Box::new(val), from_scalar: None, @@ -607,8 +601,8 @@ impl<'a> Validator for Mapping<'a> { /// /// This validator can also parse a scalar and convert it into a list in /// application-specific way. +#[derive(Debug)] pub struct Sequence<'a> { - descr: Option, element: Box, from_scalar: Option Vec>, min_length: usize, @@ -617,7 +611,6 @@ pub struct Sequence<'a> { impl<'a> Sequence<'a> { pub fn new(el: V) -> Sequence<'a> { Sequence { - descr: None, element: Box::new(el), from_scalar: None, min_length: 0, @@ -679,7 +672,8 @@ impl<'a> Validator for Sequence<'a> { /// Skips the validation of this value /// /// It's useful to accept any value (of any type) at some place, or to -/// rely on `Decodable::decode` for doing validation. +/// rely on `Deserialize::deserialize` for doing validation. +#[derive(Debug)] pub struct Anything; impl Validator for Anything { @@ -694,6 +688,7 @@ impl Validator for Anything { /// Only expect null at this place /// /// This is mostly useful for enums, i.e. `!SomeTag null` +#[derive(Debug)] pub struct Nothing; impl Validator for Nothing { @@ -713,25 +708,29 @@ impl Validator for Nothing { #[cfg(test)] mod test { + use std::fmt; use std::rc::Rc; use std::path::PathBuf; - use rustc_serialize::Decodable; use std::collections::BTreeMap; use std::collections::HashMap; + use std::error::Error as StdError; + + use serde::Deserialize; use {Options}; - use super::super::decode::YamlDecoder; - use super::super::ast::{process, Ast as A}; - use super::super::ast::Tag::{NonSpecific}; - use super::super::ast::ScalarKind::{Plain}; - use super::super::parser::parse; - use super::super::sky::{parse_string, ErrorList}; - use super::{Validator, Structure, Scalar, Numeric, Mapping, Sequence}; - use super::{Enum, Nothing, Directory, Anything}; - use super::super::errors::ErrorCollector; + use de::Deserializer; + use ast::{process, Ast as A}; + use ast::Tag::{NonSpecific}; + use ast::ScalarKind::{Plain}; + use parser::parse; + use {parse_string, ErrorList}; + use validate::{Validator, Structure, Scalar, Numeric, Mapping, Sequence}; + use validate::{Enum, Nothing, Directory, Anything}; + use errors::{Error, ErrorCollector}; use self::TestEnum::*; + use super::Pos; - #[derive(Clone, Debug, PartialEq, Eq, RustcDecodable)] + #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] struct TestStruct { intkey: usize, strkey: String, @@ -810,7 +809,7 @@ mod test { }); } - #[derive(Clone, Debug, PartialEq, Eq, RustcDecodable)] + #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] struct TestDash { some_key: usize, } @@ -846,7 +845,7 @@ mod test { |doc| { process(&Options::default(), doc, &err) } ).map_err(|e| err.into_fatal(e)).unwrap(); let ast = str_val.validate(ast, &err); - match Decodable::decode(&mut YamlDecoder::new(ast, &err)) { + match Deserialize::deserialize(&mut Deserializer::new(&ast)) { Ok(val) => { (val, err.unwrap().errors().map(|x| x.to_string()).collect()) } @@ -874,7 +873,7 @@ mod test { .to_string()))); } - #[derive(Clone, Debug, PartialEq, Eq, RustcDecodable)] + #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] struct TestOpt { some_key: Option, } @@ -907,7 +906,7 @@ mod test { }); } - fn parse_map(body: &str) -> T { + fn parse_map<'x, T: Deserialize<'x>>(body: &str) -> T { fn parse_default(ast: A) -> BTreeMap { match ast { A::Scalar(pos, _, style, value) => { @@ -976,9 +975,7 @@ mod test { assert_eq!(res, m); } - fn parse_complex_map(body: &str) - -> T - { + fn parse_complex_map<'x, T: Deserialize<'x>>(body: &str) -> T { let validator = Mapping::new( Scalar::new(), Structure::new() @@ -1022,7 +1019,7 @@ mod test { fn parse_seq(body: &str) -> Vec { fn split(ast: A) -> Vec { match ast { - A::Scalar(pos, _, style, value) => { + A::Scalar(pos, _, _style, value) => { value .split(" ") .map(|v| { @@ -1044,7 +1041,7 @@ mod test { { fn split(ast: A) -> Vec { match ast { - A::Scalar(pos, _, style, value) => { + A::Scalar(pos, _, _style, value) => { value .split(" ") .map(|v| { @@ -1077,14 +1074,14 @@ mod test { #[test] fn test_seq_empty() { - let m = Vec::new(); + let m = Vec::::new(); let res: Vec = parse_seq("[]"); assert_eq!(res, m); } #[test] fn test_seq_null() { - let m = Vec::new(); + let m = Vec::::new(); let res: Vec = parse_seq(""); assert_eq!(res, m); } @@ -1105,7 +1102,7 @@ mod test { #[test] fn test_seq_min_length_zero() { - let m = Vec::new(); + let m = Vec::::new(); let res: Vec = parse_seq_min_length("[]", 0).unwrap(); assert_eq!(res, m); @@ -1129,7 +1126,7 @@ mod test { assert_eq!(res, m); } - #[derive(PartialEq, Eq, RustcDecodable, Debug)] + #[derive(PartialEq, Eq, Deserialize, Debug)] enum TestEnum { Alpha, Beta, @@ -1230,7 +1227,7 @@ mod test { }))); } - #[derive(Clone, PartialEq, Eq, RustcDecodable)] + #[derive(Clone, PartialEq, Eq, Deserialize)] struct TestPath { path: PathBuf, } @@ -1275,12 +1272,21 @@ mod test { } #[test] + #[cfg(unix)] fn test_path_abs_abs() { assert!(parse_path("path: /root/dir", Some(true)) == TestPath { path: PathBuf::from("/root/dir"), }); } + #[test] + #[cfg(windows)] + fn test_path_abs_abs() { + assert!(parse_path(r#"path: c:\\root\dir"#, Some(true)) == TestPath { + path: PathBuf::from(r#"c:\\root\dir"#), + }); + } + #[test] #[should_panic(expected = "must be absolute")] fn test_path_rel_abs() { @@ -1298,6 +1304,7 @@ mod test { } #[test] + #[cfg(unix)] #[should_panic(expected = "must not be absolute")] fn test_path_abs_rel() { assert!(parse_path("path: /root/dir", Some(false)) == TestPath { @@ -1305,6 +1312,15 @@ mod test { }); } + #[test] + #[cfg(windows)] + #[should_panic(expected = "must not be absolute")] + fn test_path_abs_rel() { + assert!(parse_path(r#"path: c:\\root\dir"#, Some(false)) == TestPath { + path: PathBuf::from(r#":\\root\dir"#), + }); + } + #[test] fn test_path_rel_rel() { assert!(parse_path("path: root/dir", Some(false)) == TestPath { @@ -1337,7 +1353,7 @@ mod test { )); } - #[derive(PartialEq, Eq, RustcDecodable, Debug)] + #[derive(PartialEq, Eq, Deserialize, Debug)] struct EnumOpt { val: Option, } @@ -1360,7 +1376,7 @@ mod test { EnumOpt { val: Some(Epsilon(None)) }); } - #[derive(PartialEq, Eq, RustcDecodable, Debug)] + #[derive(PartialEq, Eq, Deserialize, Debug)] struct Parsed { value: String, } @@ -1397,7 +1413,7 @@ mod test { Parsed { value: "test".to_string() }); } - #[derive(PartialEq, Eq, RustcDecodable, Debug)] + #[derive(PartialEq, Eq, Deserialize, Debug)] enum TestEnumDef { Alpha, Beta, @@ -1436,4 +1452,71 @@ mod test { fn test_enum_def_tag() { assert_eq!(parse_enum_def("!Alpha"), TestEnumDef::Alpha); } + + #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] + struct Version; + + #[derive(Debug)] + struct VersionError(&'static str); + + impl StdError for VersionError { + fn description(&self) -> &str { "Version Error" } + fn cause(&self) -> Option<&StdError> { None } + } + + impl fmt::Display for VersionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.description(), self.0) + } + } + + impl Version { + fn new() -> Version { + Version {} + } + } + + impl Validator for Version { + fn default(&self, _: Pos) -> Option { + None + } + + fn validate(&self, ast: A, err: &ErrorCollector) -> A { + match ast { + A::Scalar(pos, tag, kind, version) => { + if !version.starts_with("v") { + err.add_error(Error::custom_at( + &pos, + VersionError("Version must start with 'v'"))) + } + A::Scalar(pos, tag, kind, version) + }, + ast => { + err.add_error(Error::validation_error( + &ast.pos(), format!("Version must be a scalar value"))); + ast + }, + } + } + } + + fn parse_version(body: &str) -> Result { + let validator = Version::new(); + parse_string("", body, &validator, &Options::default()) + } + + #[test] + fn test_custom_error() { + let err = parse_version("0.0.1").unwrap_err(); + let error = err.errors().nth(0).unwrap(); + assert_eq!( + format!("{}", error), + ":1:1: Version Error: Version must start with 'v'"); + match error.downcast_ref::() { + Some(&VersionError(msg)) => { + assert_eq!(msg, "Version must start with 'v'") + }, + e => panic!("Custom error must be VersionError but was: {:?}", e), + } + } } diff --git a/vagga.yaml b/vagga.yaml index 167d227..efded9c 100644 --- a/vagga.yaml +++ b/vagga.yaml @@ -2,19 +2,25 @@ containers: doc: setup: - - !Alpine v3.4 + - !Alpine v3.8 - !Repo edge/main - - !Install [make, py-sphinx] + - !Repo edge/community + - !Install [make, py3-sphinx] build: setup: - - !Ubuntu xenial - - !Install [make, wget, ca-certificates, build-essential] + - !Ubuntu bionic + - !Install [make, ca-certificates, build-essential, vim] - !TarInstall - url: "https://static.rust-lang.org/dist/rust-1.17.0-x86_64-unknown-linux-gnu.tar.gz" + url: "https://static.rust-lang.org/dist/rust-1.27.2-x86_64-unknown-linux-gnu.tar.gz" script: "./install.sh --prefix=/usr \ --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" + - &bulk !Tar + url: "https://github.com/tailhook/bulk/releases/download/v0.4.12/bulk-v0.4.12.tar.gz" + sha256: 7deeb4895b3909afea46194ef01bafdeb30ff89fc4a7b6497172ba117734040e + path: / + - !EnsureDir /cargo environ: HOME: /work/target @@ -31,6 +37,7 @@ commands: cargo: !Command container: build + symlink-name: cargo run: [cargo] doc: !Command @@ -40,5 +47,10 @@ commands: work-dir: doc epilog: | ------------------------------------------------------------------------ - Docs are built in doc/_build/html/index.html - run: [make, html] + xdg-open doc/_build/html/index.html + run: [make, html, SPHINXBUILD=sphinx-build-3] + + _bulk: !Command + description: Run `bulk` command (for version bookkeeping) + container: build + run: [bulk]