From 21beef26de816a7a0716ab553464b4d2cef21777 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Tue, 15 Aug 2017 17:06:02 +0300 Subject: [PATCH 01/51] Deserializing unit structs implemented --- src/de.rs | 18 ++++++++++++++++-- src/decode.rs | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/de.rs b/src/de.rs index f273c53..011395b 100644 --- a/src/de.rs +++ b/src/de.rs @@ -217,7 +217,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 +228,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 @@ -464,6 +464,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 { diff --git a/src/decode.rs b/src/decode.rs index 7c1ae51..266fb85 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -581,6 +581,20 @@ mod test { assert_eq!(decode::>("~"), None); } + #[test] + fn decode_nothing() { + #[derive(RustcDecodable, 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::<()>("~"), ()); + } + #[derive(Clone, Debug, PartialEq, Eq, RustcDecodable)] struct TestStruct { a: usize, From db3f06f97ab7d05d531800118e97760a56eca12e Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Tue, 15 Aug 2017 17:24:49 +0300 Subject: [PATCH 02/51] Implements decoding mappings --- src/de.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/de.rs b/src/de.rs index 011395b..e466f3b 100644 --- a/src/de.rs +++ b/src/de.rs @@ -2,10 +2,11 @@ use std::marker::PhantomData; use std::ops::{Neg, AddAssign, MulAssign}; use std::str::FromStr; use std::slice; +use std::collections::btree_map; -use serde::de::{self, Deserialize, DeserializeSeed, Visitor, SeqAccess, - MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; +use serde::de::{self, Deserialize, DeserializeSeed, Visitor, SeqAccess}; +use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; use ast::Ast; use ast::Ast as A; @@ -19,7 +20,10 @@ pub struct Deserializer<'a> { path: String, } + struct ListVisitor<'a, 'b: 'a>(slice::Iter<'b, Ast>, &'a mut Deserializer<'b>); +struct MapVisitor<'a, 'b: 'a>(btree_map::Iter<'b, String, Ast>, + &'a mut Deserializer<'b>); impl<'de> Deserializer<'de> { // By convention, `Deserializer` constructors are named like `from_xyz`. @@ -295,7 +299,18 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { fn deserialize_map(mut 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(), self)); + self.ast = ast; + return result; + } + ref node => { + return Err(Error::decode_error(&node.pos(), &self.path, + format!("mapping expected got {}", node))) + } + } } // Structs look just like maps in JSON. @@ -396,6 +411,45 @@ 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> + { + unimplemented!(); + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'de> + { + unimplemented!(); + } + fn next_entry_seed( + &mut self, + kseed: K, + vseed: V, + ) -> Result> + where + K: DeserializeSeed<'de>, + V: DeserializeSeed<'de>, + { + match self.0.next() { + Some((key, value)) => { + let key = kseed.deserialize( + key.clone().into_deserializer())?; + self.1.ast = value; + let value = vseed.deserialize(&mut *self.1)?; + Ok(Some((key, value))) + } + None => { + return Ok(None); + } + } + } +} + #[cfg(test)] mod test { use std::rc::Rc; From e05b59d0ccb4d55cc5773c242b84b59a8f2ce869 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Tue, 15 Aug 2017 17:40:51 +0300 Subject: [PATCH 03/51] Deserializes struct values --- src/de.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/de.rs b/src/de.rs index e466f3b..1e10075 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,7 +1,9 @@ +use std::mem::replace; use std::marker::PhantomData; use std::ops::{Neg, AddAssign, MulAssign}; use std::str::FromStr; use std::slice; +use std::iter::Peekable; use std::collections::btree_map; @@ -22,7 +24,7 @@ pub struct Deserializer<'a> { struct ListVisitor<'a, 'b: 'a>(slice::Iter<'b, Ast>, &'a mut Deserializer<'b>); -struct MapVisitor<'a, 'b: 'a>(btree_map::Iter<'b, String, Ast>, +struct MapVisitor<'a, 'b: 'a>(Peekable>, &'a mut Deserializer<'b>); impl<'de> Deserializer<'de> { @@ -302,7 +304,8 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { match *self.ast { A::Map(_, _, ref map) => { let ast = self.ast; - let result = visitor.visit_map(MapVisitor(map.iter(), self)); + let result = visitor.visit_map( + MapVisitor(map.iter().peekable(), self)); self.ast = ast; return result; } @@ -322,12 +325,18 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { fn deserialize_struct( self, _name: &'static str, - _fields: &'static [&'static str], + fields: &'static [&'static str], visitor: V ) -> Result where V: Visitor<'de> { - unimplemented!(); + /* + let oldf = replace(&mut self.fields, Some(fields)); + let result = visitor.visit_struct(self); + self.fields = oldf; + return result; + */ + self.deserialize_map(visitor) } fn deserialize_enum( @@ -417,14 +426,24 @@ impl<'de, 'a, 'b: 'a> MapAccess<'de> for MapVisitor<'a, 'b> { fn next_key_seed(&mut self, seed: T) -> Result> where T: DeserializeSeed<'de> { - unimplemented!(); + match self.0.peek() { + Some(&(key, _)) => { + Ok(Some(seed.deserialize( + key.clone().into_deserializer())?)) + } + None => { + return Ok(None); + } + } } fn next_value_seed(&mut self, seed: V) -> Result where V: DeserializeSeed<'de> { - unimplemented!(); + let (_, value) = self.0.next().unwrap(); + self.1.ast = value; + seed.deserialize(&mut *self.1) } fn next_entry_seed( &mut self, From d679dfbb29ad6c78f125b7728f7360afcfee05ce Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Tue, 15 Aug 2017 18:23:42 +0300 Subject: [PATCH 04/51] Implements deserialisation of enums --- src/de.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/src/de.rs b/src/de.rs index 1e10075..79409ac 100644 --- a/src/de.rs +++ b/src/de.rs @@ -10,8 +10,7 @@ use std::collections::btree_map; use serde::de::{self, Deserialize, DeserializeSeed, Visitor, SeqAccess}; use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; -use ast::Ast; -use ast::Ast as A; +use ast::{Ast, Ast as A, Tag}; use errors::{Error, ErrorCollector}; type Result = ::std::result::Result; @@ -26,6 +25,8 @@ pub struct Deserializer<'a> { struct ListVisitor<'a, 'b: 'a>(slice::Iter<'b, Ast>, &'a mut Deserializer<'b>); struct MapVisitor<'a, 'b: 'a>(Peekable>, &'a mut Deserializer<'b>); +struct EnumVisitor<'a, 'b: 'a>(&'a mut Deserializer<'b>); +struct VariantVisitor<'a, 'b: 'a>(&'a mut Deserializer<'b>); impl<'de> Deserializer<'de> { // By convention, `Deserializer` constructors are named like `from_xyz`. @@ -330,12 +331,6 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - /* - let oldf = replace(&mut self.fields, Some(fields)); - let result = visitor.visit_struct(self); - self.fields = oldf; - return result; - */ self.deserialize_map(visitor) } @@ -347,7 +342,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - unimplemented!(); + visitor.visit_enum(EnumVisitor(self)) } // An identifier in Serde is the type that identifies a field of a struct or @@ -360,7 +355,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 @@ -469,6 +477,59 @@ impl<'de, 'a, 'b: 'a> MapAccess<'de> for MapVisitor<'a, 'b> { } } +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 val = seed.deserialize(&mut *self.0)?; + 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; @@ -726,7 +787,7 @@ 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'"); } From f67f51990f74e8a9f51fb378c3b679b4af7cd1c6 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Tue, 15 Aug 2017 18:25:46 +0300 Subject: [PATCH 05/51] Fixes final test --- src/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de.rs b/src/de.rs index 79409ac..b9bc0c8 100644 --- a/src/de.rs +++ b/src/de.rs @@ -787,7 +787,7 @@ mod test { } #[test] - #[should_panic(expected = "sequence expected, got Scalar")] + #[should_panic(expected = "sequence expected got Scalar")] fn test_struct_items_tag() { decode::("items:\n 'hello'"); } From f84217f1c2e2f795a2d8c9faf0ab746da7b46638 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Tue, 15 Aug 2017 18:39:55 +0300 Subject: [PATCH 06/51] vagga.yaml: upgrades rust and other things --- vagga.yaml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/vagga.yaml b/vagga.yaml index 167d227..b058cae 100644 --- a/vagga.yaml +++ b/vagga.yaml @@ -2,19 +2,24 @@ containers: doc: setup: - - !Alpine v3.4 - - !Repo edge/main - - !Install [make, py-sphinx] + - !Alpine v3.6 + - !Install [make, py3-sphinx] build: setup: - !Ubuntu xenial - - !Install [make, wget, ca-certificates, build-essential] + - !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.19.0-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.9/bulk-v0.4.9.tar.gz" + sha256: 23471a9986274bb4b7098c03e2eb7e1204171869b72c45385fcee1c64db2d111 + path: / + - !Sh cargo install --root=/usr cargo-outdated + - !EnsureDir /cargo environ: HOME: /work/target @@ -31,6 +36,7 @@ commands: cargo: !Command container: build + symlink-name: cargo run: [cargo] doc: !Command @@ -41,4 +47,4 @@ commands: epilog: | ------------------------------------------------------------------------ Docs are built in doc/_build/html/index.html - run: [make, html] + run: [make, html, SPHINXBUILD=sphinx-build-3] From 21dae116ff7172d924d98f4689625a9aa6fca683 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Tue, 15 Aug 2017 20:54:26 +0300 Subject: [PATCH 07/51] Removes old decode module (rustc_serialize) and fixes all tests --- Cargo.toml | 5 +- src/de.rs | 47 +- src/decode.rs | 784 ---------------------------------- src/emit.rs | 7 +- src/json.rs | 2 +- src/lib.rs | 14 +- src/parser.rs | 2 +- src/sky.rs | 16 +- src/special_cases/duration.rs | 32 -- src/special_cases/mod.rs | 51 --- src/special_cases/regex.rs | 31 -- src/test_errors.rs | 37 +- src/test_util.rs | 11 +- src/validate.rs | 46 +- 14 files changed, 106 insertions(+), 979 deletions(-) delete mode 100644 src/decode.rs delete mode 100644 src/special_cases/duration.rs delete mode 100644 src/special_cases/mod.rs delete mode 100644 src/special_cases/regex.rs diff --git a/Cargo.toml b/Cargo.toml index bdb5e22..d67d701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,16 @@ version = "0.2.3" 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" humannum = "0.1.0" serde = "1.0.10" +serde_json = {version="1.0.0", optional=true} + +[features] +json = ["serde_json"] [dev-dependencies] serde_derive = "1.0.10" diff --git a/src/de.rs b/src/de.rs index b9bc0c8..246e46f 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,13 +1,12 @@ +use std::collections::BTreeMap; +use std::collections::btree_map; +use std::iter::Peekable; use std::mem::replace; -use std::marker::PhantomData; -use std::ops::{Neg, AddAssign, MulAssign}; -use std::str::FromStr; use std::slice; -use std::iter::Peekable; -use std::collections::btree_map; +use std::str::FromStr; -use serde::de::{self, Deserialize, DeserializeSeed, Visitor, SeqAccess}; +use serde::de::{self, DeserializeSeed, Visitor, SeqAccess}; use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; use ast::{Ast, Ast as A, Tag}; @@ -15,8 +14,15 @@ use errors::{Error, ErrorCollector}; type Result = ::std::result::Result; +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Mode { + Normal, + Enum, +} + pub struct Deserializer<'a> { ast: &'a Ast, + mode: Mode, err: ErrorCollector, path: String, } @@ -36,6 +42,7 @@ impl<'de> Deserializer<'de> { pub fn new<'x>(ast: &'x Ast, err: &ErrorCollector) -> Deserializer<'x> { Deserializer { ast: &ast, + mode: Mode::Normal, err: err.clone(), path: "".to_string(), } @@ -51,7 +58,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 @@ -211,7 +223,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() } _ => { @@ -264,6 +279,9 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { 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))) @@ -310,6 +328,10 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { 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))) @@ -342,7 +364,10 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - visitor.visit_enum(EnumVisitor(self)) + 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 @@ -388,7 +413,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ) -> Result where V: Visitor<'de> { - unimplemented!(); + self.deserialize_unit(visitor) } } @@ -483,7 +508,9 @@ impl<'de, 'a, 'b: 'a> EnumAccess<'de> for EnumVisitor<'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))) } } diff --git a/src/decode.rs b/src/decode.rs deleted file mode 100644 index 266fb85..0000000 --- a/src/decode.rs +++ /dev/null @@ -1,784 +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); - } - - #[test] - fn decode_nothing() { - #[derive(RustcDecodable, 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::<()>("~"), ()); - } - - #[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/json.rs b/src/json.rs index 7546d25..4ee06b2 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use std::collections::BTreeMap; -use rustc_serialize::json::{ToJson, Json}; +use serde_json::Value; use rustc_serialize::json::Json as J; use super::ast::Ast; diff --git a/src/lib.rs b/src/lib.rs index 79e3446..48e2369 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,11 @@ //! //! ```rust,ignore # for some reason this crashes compiler //! extern crate quire; -//! extern crate rustc_serialize; +//! #[macor_use] extern crate serde_derive; //! use quire::{parse_config, Options}; //! use quire::validate::{Structure, Scalar}; //! -//! #[derive(RustcDecodable)] +//! #[derive(Deserialize)] //! struct Config { //! item1: String, //! item2: Option, @@ -32,7 +32,6 @@ extern crate serde; -extern crate rustc_serialize; extern crate regex; extern crate humantime; extern crate humannum; @@ -45,21 +44,18 @@ 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; +//mod emit; pub mod ast; -mod decode; mod de; pub mod validate; mod sky; -mod special_cases; +#[cfg(feature="json")] mod json; #[cfg(test)] mod test_errors; #[cfg(test)] mod test_util; diff --git a/src/parser.rs b/src/parser.rs index ffa13e7..11722c7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -620,7 +620,7 @@ fn _parse_node<'x>(tokiter: &mut TokenIter<'x>, aliases: &mut Aliases<'x>) indent = true; } let anchor = maybe_parse_anchor(tokiter); - let mut tag = maybe_parse_tag(tokiter); + let tag = maybe_parse_tag(tokiter); tok = tokiter.peek(0); if !indent && tok.kind == T::Indent { // Otherwise indent is after tag tokiter.next(); diff --git a/src/sky.rs b/src/sky.rs index b8e05b5..651ea88 100644 --- a/src/sky.rs +++ b/src/sky.rs @@ -2,19 +2,19 @@ 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::{Error, 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 { @@ -30,13 +30,13 @@ pub fn parse_config>( |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, &err)) .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 +45,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, &err)) .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..891d077 100644 --- a/src/test_errors.rs +++ b/src/test_errors.rs @@ -1,45 +1,46 @@ 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, &err)) + .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("missing field `value`\n".to_string())); } #[test] diff --git a/src/test_util.rs b/src/test_util.rs index 1ae5399..d5169a9 100644 --- a/src/test_util.rs +++ b/src/test_util.rs @@ -1,23 +1,22 @@ use std::rc::Rc; -use std::path::PathBuf; -use std::collections::BTreeMap; -use rustc_serialize::Decodable; -use decode::YamlDecoder; +use serde::Deserialize; + +use de::Deserializer; use parser::parse; use ast::process; use errors::ErrorCollector; use {Options}; -pub fn decode(data: &str) -> T { +pub fn decode<'x, T: Deserialize<'x>>(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)) + Deserialize::deserialize(&mut Deserializer::new(&ast, &err)) .map_err(|e| err.into_fatal(e)) .unwrap() } diff --git a/src/validate.rs b/src/validate.rs index 1a0b051..05b8400 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -679,7 +679,7 @@ 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. pub struct Anything; impl Validator for Anything { @@ -715,23 +715,23 @@ impl Validator for Nothing { mod test { use std::rc::Rc; use std::path::PathBuf; - use rustc_serialize::Decodable; use std::collections::BTreeMap; use std::collections::HashMap; + 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::ErrorCollector; use self::TestEnum::*; - #[derive(Clone, Debug, PartialEq, Eq, RustcDecodable)] + #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] struct TestStruct { intkey: usize, strkey: String, @@ -810,7 +810,7 @@ mod test { }); } - #[derive(Clone, Debug, PartialEq, Eq, RustcDecodable)] + #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] struct TestDash { some_key: usize, } @@ -846,7 +846,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, &err)) { Ok(val) => { (val, err.unwrap().errors().map(|x| x.to_string()).collect()) } @@ -874,7 +874,7 @@ mod test { .to_string()))); } - #[derive(Clone, Debug, PartialEq, Eq, RustcDecodable)] + #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] struct TestOpt { some_key: Option, } @@ -907,7 +907,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 +976,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() @@ -1129,7 +1127,7 @@ mod test { assert_eq!(res, m); } - #[derive(PartialEq, Eq, RustcDecodable, Debug)] + #[derive(PartialEq, Eq, Deserialize, Debug)] enum TestEnum { Alpha, Beta, @@ -1230,7 +1228,7 @@ mod test { }))); } - #[derive(Clone, PartialEq, Eq, RustcDecodable)] + #[derive(Clone, PartialEq, Eq, Deserialize)] struct TestPath { path: PathBuf, } @@ -1337,7 +1335,7 @@ mod test { )); } - #[derive(PartialEq, Eq, RustcDecodable, Debug)] + #[derive(PartialEq, Eq, Deserialize, Debug)] struct EnumOpt { val: Option, } @@ -1360,7 +1358,7 @@ mod test { EnumOpt { val: Some(Epsilon(None)) }); } - #[derive(PartialEq, Eq, RustcDecodable, Debug)] + #[derive(PartialEq, Eq, Deserialize, Debug)] struct Parsed { value: String, } @@ -1397,7 +1395,7 @@ mod test { Parsed { value: "test".to_string() }); } - #[derive(PartialEq, Eq, RustcDecodable, Debug)] + #[derive(PartialEq, Eq, Deserialize, Debug)] enum TestEnumDef { Alpha, Beta, From d50c2cd08c44f0f65cae7be54fb987920877ec97 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Tue, 15 Aug 2017 21:08:55 +0300 Subject: [PATCH 08/51] Fixes tests for paths on windows --- src/validate.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/validate.rs b/src/validate.rs index 05b8400..e6699c4 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1273,12 +1273,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() { @@ -1296,6 +1305,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 { @@ -1303,6 +1313,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 { From 0671734129e7b2536c67a3211154599154084ddf Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 16 Aug 2017 15:51:49 +0300 Subject: [PATCH 09/51] Adds `duration` and `regex` helper modules --- Cargo.toml | 4 +++- src/duration.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 8 ++++--- src/regex.rs | 53 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 src/duration.rs create mode 100644 src/regex.rs diff --git a/Cargo.toml b/Cargo.toml index d67d701..f7e1add 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,15 +11,17 @@ authors = ["paul@colomiets.name"] [dependencies] quick-error = "1.0.0" -regex = "0.1.80" humantime = "1.0.0" num-traits = "0.1.36" humannum = "0.1.0" serde = "1.0.10" serde_json = {version="1.0.0", optional=true} +regex = {version="0.2.2", optional=true} [features] json = ["serde_json"] +regex_expressions = ["regex"] +default = ["regex_expressions"] [dev-dependencies] serde_derive = "1.0.10" diff --git a/src/duration.rs b/src/duration.rs new file mode 100644 index 0000000..b227016 --- /dev/null +++ b/src/duration.rs @@ -0,0 +1,61 @@ +//! A module that parses human-friendly duration +//! +//! # Example +//! +//! ```rust +//! +//! # extern crate quire; +//! # extern crate serde; +//! # #[macro_use] extern crate serde_derive; +//! # use std::time::Duration; +//! +//! #[derive(Serialize)] +//! struct SomeStruct { +//! #[serde(with="quire::duration")] +//! timeout: Duration, +//! } +//! +//! # fn main() {} +//! +//! ``` + +use std::fmt; +use std::time::Duration; + +use humantime::parse_duration; +use serde::ser::{Serializer, Serialize}; +use serde::de::{Deserializer, Error, Visitor}; + +struct DurationVisitor; + +pub fn serialize(duration: &Duration, s: S) -> Result + where S: Serializer +{ + // TODO(tailhook) make nicers serialization + let secs = duration.as_secs(); + let nanos = duration.subsec_nanos(); + if nanos > 0 { + format!("{}s {}ns", secs, nanos).serialize(s) + } else { + format!("{}s", secs).serialize(s) + } +} + +impl<'a> Visitor<'a> for DurationVisitor { + type Value = Duration; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("duration (like `1h 12m 35sec`)") + } + + fn visit_str(self, val: &str) -> Result + where E: Error + { + parse_duration(val).map_err(E::custom) + } +} + +pub fn deserialize<'x, D>(d: D) -> Result + where D: Deserializer<'x> +{ + d.deserialize_str(DurationVisitor) +} diff --git a/src/lib.rs b/src/lib.rs index 48e2369..b69ad23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,12 +32,12 @@ extern crate serde; -extern crate regex; extern crate humantime; extern crate humannum; extern crate num_traits; #[macro_use] extern crate quick_error; #[cfg(test)] #[macro_use] extern crate serde_derive; +#[cfg(feature="regex_expressions")] extern crate regex as re; pub use sky::{parse_config, parse_string}; pub use options::{Options, Include}; @@ -52,10 +52,12 @@ mod tokenizer; mod options; mod parser; //mod emit; -pub mod ast; mod de; -pub mod validate; mod sky; +pub mod ast; +pub mod validate; +pub mod duration; +#[cfg(feature="regex_expressions")] pub mod regex; #[cfg(feature="json")] mod json; #[cfg(test)] mod test_errors; #[cfg(test)] mod test_util; diff --git a/src/regex.rs b/src/regex.rs new file mode 100644 index 0000000..80d2c42 --- /dev/null +++ b/src/regex.rs @@ -0,0 +1,53 @@ +//! A module that parses regex from string +//! +//! # Example +//! +//! ```rust +//! +//! # extern crate quire; +//! # extern crate serde; +//! # extern crate regex; +//! # #[macro_use] extern crate serde_derive; +//! +//! #[derive(Serialize)] +//! struct SomeStruct { +//! #[serde(with="quire::regex")] +//! regex: regex::Regex, +//! } +//! +//! # fn main() {} +//! +//! ``` + +use std::fmt; +use re::Regex; + +use serde::ser::{Serializer, Serialize}; +use serde::de::{Deserializer, Error, Visitor}; + +struct RegexVisitor; + +pub fn serialize(re: &Regex, s: S) -> Result + where S: Serializer +{ + re.as_str().serialize(s) +} + +impl<'a> Visitor<'a> for RegexVisitor { + type Value = Regex; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("regular expression") + } + + fn visit_str(self, val: &str) -> Result + where E: Error + { + Regex::new(val).map_err(E::custom) + } +} + +pub fn deserialize<'x, D>(d: D) -> Result + where D: Deserializer<'x> +{ + d.deserialize_str(RegexVisitor) +} From 32241e09e6425b82368375d4d28b563636e5d71e Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 16 Aug 2017 16:03:48 +0300 Subject: [PATCH 10/51] Fixes `deserialize_str` (fixes `regex` and `duration` modules) --- src/de.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/de.rs b/src/de.rs index 246e46f..d2b530f 100644 --- a/src/de.rs +++ b/src/de.rs @@ -186,12 +186,6 @@ 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) => { @@ -205,6 +199,12 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { 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 @@ -562,6 +562,7 @@ mod test { use std::rc::Rc; use std::path::PathBuf; use std::collections::BTreeMap; + use std::time::Duration; use serde::Deserialize; @@ -596,6 +597,17 @@ mod test { assert_eq!(decode::("false"), false); } + #[test] + fn decode_duration() { + #[derive(Deserialize, PartialEq, Debug)] + struct Dur { + #[serde(with="::duration")] + dur: Duration, + } + assert_eq!(decode::("dur: 1m 15s"), + Dur { dur: Duration::new(75, 0) }); + } + #[test] fn decode_i8() { assert_eq!(decode::("1"), 1); From b46758882594a95404d6096233593eb987996822 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 16 Aug 2017 16:04:14 +0300 Subject: [PATCH 11/51] Also test build without modules on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b208245..53f6e7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,5 @@ rust: - nightly script: - cargo build --verbose +- cargo test --no-default-features - cargo test --verbose From a314885fbd2dfd16b9cec1fea89b942f221b4178 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 16 Aug 2017 16:30:01 +0300 Subject: [PATCH 12/51] More options for booleans supported --- src/de.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/de.rs b/src/de.rs index d2b530f..47f709c 100644 --- a/src/de.rs +++ b/src/de.rs @@ -86,8 +86,8 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { let value = match *self.ast { A::Scalar(ref pos, _, _, ref val) => { match &val[..] { - "true" => true, - "false" => false, + "true"|"yes"|"on"|"y"|"1" => true, + "false"|"no"|"off"|"n"|"0" => false, _ => { return Err(Error::decode_error(pos, &self.path, format!("bad boolean {:?}", val))); @@ -594,7 +594,15 @@ mod test { #[test] fn decode_bool() { assert_eq!(decode::("true"), true); + assert_eq!(decode::("yes"), true); + assert_eq!(decode::("y"), true); + assert_eq!(decode::("on"), true); + assert_eq!(decode::("1"), true); assert_eq!(decode::("false"), false); + assert_eq!(decode::("no"), false); + assert_eq!(decode::("n"), false); + assert_eq!(decode::("off"), false); + assert_eq!(decode::("0"), false); } #[test] From 6452d89885c533fb357f892c70da5f9ce65ea900 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 16 Aug 2017 17:00:04 +0300 Subject: [PATCH 13/51] Renames json tests to transcode tests, fixes them --- Cargo.toml | 4 +- src/json.rs | 534 ------------------------------------------ src/lib.rs | 11 +- src/test_transcode.rs | 492 ++++++++++++++++++++++++++++++++++++++ src/validate.rs | 6 +- 5 files changed, 504 insertions(+), 543 deletions(-) delete mode 100644 src/json.rs create mode 100644 src/test_transcode.rs diff --git a/Cargo.toml b/Cargo.toml index f7e1add..2457455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,13 +15,13 @@ humantime = "1.0.0" num-traits = "0.1.36" humannum = "0.1.0" serde = "1.0.10" -serde_json = {version="1.0.0", optional=true} regex = {version="0.2.2", optional=true} [features] -json = ["serde_json"] regex_expressions = ["regex"] default = ["regex_expressions"] [dev-dependencies] serde_derive = "1.0.10" +serde_json = "1.0.2" +serde-transcode = "1.0.0" diff --git a/src/json.rs b/src/json.rs deleted file mode 100644 index 4ee06b2..0000000 --- a/src/json.rs +++ /dev/null @@ -1,534 +0,0 @@ -use std::str::FromStr; - -use std::collections::BTreeMap; -use serde_json::Value; -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 b69ad23..d7bf7fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,13 +31,15 @@ #![allow(unused_variables)] -extern crate serde; -extern crate humantime; extern crate humannum; +extern crate humantime; extern crate num_traits; -#[macro_use] extern crate quick_error; -#[cfg(test)] #[macro_use] extern crate serde_derive; +extern crate serde; #[cfg(feature="regex_expressions")] extern crate regex as re; +#[cfg(test)] #[macro_use] extern crate serde_derive; +#[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}; @@ -61,3 +63,4 @@ pub mod duration; #[cfg(feature="json")] mod json; #[cfg(test)] mod test_errors; #[cfg(test)] mod test_util; +#[cfg(test)] mod test_transcode; diff --git a/src/test_transcode.rs b/src/test_transcode.rs new file mode 100644 index 0000000..b08ed07 --- /dev/null +++ b/src/test_transcode.rs @@ -0,0 +1,492 @@ +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; +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, &err); + 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_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 mut de = Deserializer::new(&ast, &err); + 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_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/validate.rs b/src/validate.rs index e6699c4..2a21207 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1075,14 +1075,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); } @@ -1103,7 +1103,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); From 048896be480c1badc925a87a90a7ae5fa5126bf4 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 16 Aug 2017 17:24:27 +0300 Subject: [PATCH 14/51] Shows path in errors again --- src/de.rs | 65 +++++++++++++++++++++++----------------------- src/test_errors.rs | 3 ++- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/de.rs b/src/de.rs index 47f709c..5ac2dd1 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,6 +1,7 @@ +use std::fmt::Write; use std::collections::BTreeMap; use std::collections::btree_map; -use std::iter::Peekable; +use std::iter::{Peekable, Enumerate}; use std::mem::replace; use std::slice; use std::str::FromStr; @@ -28,9 +29,9 @@ pub struct Deserializer<'a> { } -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>, - &'a mut Deserializer<'b>); + usize, &'a mut Deserializer<'b>); struct EnumVisitor<'a, 'b: 'a>(&'a mut Deserializer<'b>); struct VariantVisitor<'a, 'b: 'a>(&'a mut Deserializer<'b>); @@ -47,6 +48,15 @@ impl<'de> Deserializer<'de> { path: "".to_string(), } } + + fn map_err(&self, result: Result) -> Result { + match result { + Err(Error::Custom(e)) => { + Err(Error::decode_error(&self.ast.pos(), &self.path, e)) + } + result => result, + } + } } impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { @@ -275,7 +285,9 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { 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; } @@ -324,7 +336,8 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { A::Map(_, _, ref map) => { let ast = self.ast; let result = visitor.visit_map( - MapVisitor(map.iter().peekable(), self)); + MapVisitor(map.iter().peekable(), self.path.len(), self)); + let result = self.map_err(result); self.ast = ast; return result; } @@ -442,9 +455,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); @@ -461,8 +478,9 @@ impl<'de, 'a, 'b: 'a> MapAccess<'de> for MapVisitor<'a, 'b> { { match self.0.peek() { Some(&(key, _)) => { - Ok(Some(seed.deserialize( - key.clone().into_deserializer())?)) + write!(&mut self.2.path, ".{}", key).unwrap(); + let result = seed.deserialize(key.clone().into_deserializer()); + Ok(Some(self.2.map_err(result)?)) } None => { return Ok(None); @@ -475,30 +493,11 @@ impl<'de, 'a, 'b: 'a> MapAccess<'de> for MapVisitor<'a, 'b> { V: DeserializeSeed<'de> { let (_, value) = self.0.next().unwrap(); - self.1.ast = value; - seed.deserialize(&mut *self.1) - } - fn next_entry_seed( - &mut self, - kseed: K, - vseed: V, - ) -> Result> - where - K: DeserializeSeed<'de>, - V: DeserializeSeed<'de>, - { - match self.0.next() { - Some((key, value)) => { - let key = kseed.deserialize( - key.clone().into_deserializer())?; - self.1.ast = value; - let value = vseed.deserialize(&mut *self.1)?; - Ok(Some((key, value))) - } - None => { - return Ok(None); - } - } + 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; } } diff --git a/src/test_errors.rs b/src/test_errors.rs index 891d077..02865e4 100644 --- a/src/test_errors.rs +++ b/src/test_errors.rs @@ -40,7 +40,8 @@ fn decode_struct(data: &str) -> Result { #[test] fn test_path() { assert_eq!(decode_struct("list:\n- {}"), - Err("missing field `value`\n".to_string())); + Err(":2:3: Decode error at .list[0]: \ + missing field `value`\n".to_string())); } #[test] From 62e883af8e32819cf7ccb296b4e11d95f02f98ba Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 16 Aug 2017 18:26:45 +0300 Subject: [PATCH 15/51] Hides `Error` type under a newtype --- Cargo.toml | 2 +- src/de.rs | 13 ++++--------- src/errors.rs | 45 ++++++++++++++++++++++++++++----------------- src/sky.rs | 8 +++++--- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2457455..896604b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ version = "0.2.3" authors = ["paul@colomiets.name"] [dependencies] -quick-error = "1.0.0" +quick-error = "1.2.0" humantime = "1.0.0" num-traits = "0.1.36" humannum = "0.1.0" diff --git a/src/de.rs b/src/de.rs index 5ac2dd1..a66700c 100644 --- a/src/de.rs +++ b/src/de.rs @@ -11,7 +11,7 @@ use serde::de::{self, DeserializeSeed, Visitor, SeqAccess}; use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; use ast::{Ast, Ast as A, Tag}; -use errors::{Error, ErrorCollector}; +use errors::{Error, add_info, ErrorCollector}; type Result = ::std::result::Result; @@ -50,12 +50,7 @@ impl<'de> Deserializer<'de> { } fn map_err(&self, result: Result) -> Result { - match result { - Err(Error::Custom(e)) => { - Err(Error::decode_error(&self.ast.pos(), &self.path, e)) - } - result => result, - } + add_info(&self.ast.pos(), &self.path, result) } } @@ -395,10 +390,10 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { { match *self.ast.tag() { Tag::GlobalTag(_) => unimplemented!(), - Tag::LocalTag(ref val) => Ok(visitor.visit_str(val)?), + Tag::LocalTag(ref val) => Ok(visitor.visit_str::(val)?), Tag::NonSpecific => match *self.ast { A::Scalar(_, _, _, ref val) => { - Ok(visitor.visit_string(val.replace("-", "_"))?) + Ok(visitor.visit_string::(val.replace("-", "_"))?) } ref node => { return Err(Error::decode_error(&node.pos(), &self.path, diff --git a/src/errors.rs b/src/errors.rs index b4ba211..9fee49c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -16,7 +16,7 @@ quick_error! { /// 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) } @@ -51,36 +51,36 @@ quick_error! { impl ::serde::de::Error for Error { fn custom(msg: T) -> Self { - return Error::Custom(format!("{}", msg)); + ErrorEnum::Custom(format!("{}", msg)).into() } } impl Error { - pub fn parse_error(pos: &Pos, message: String) -> Error { - return Error::ParseError( + pub(crate) fn parse_error(pos: &Pos, message: String) -> Error { + 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( + pub(crate) fn tokenizer_error((pos, err): (Pos, tokenizer::Error)) -> Error { + 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( + pub(crate) fn validation_error(pos: &Pos, message: String) -> Error { + 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( + pub(crate) fn decode_error(pos: &Pos, path: &String, message: String) -> Error { + 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( + pub(crate) fn preprocess_error(pos: &Pos, message: String) -> Error { + ErrorEnum::PreprocessError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), - message); + message).into() } } @@ -162,3 +162,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::Custom(e))) => { + Err(Error::decode_error(pos, path, e)) + } + result => result, + } +} diff --git a/src/sky.rs b/src/sky.rs index 651ea88..cbe4c38 100644 --- a/src/sky.rs +++ b/src/sky.rs @@ -10,7 +10,7 @@ use super::errors::ErrorCollector; use super::parser::parse; use super::validate::Validator; use {Options}; -use errors::{Error, ErrorList}; +use errors::{ErrorEnum, ErrorList}; /// Parse configuration from a file @@ -21,10 +21,12 @@ pub fn parse_config<'x, T: Deserialize<'x>, P: AsRef>( 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) } From 9e795bca7e706209df390a8c64e9e9c0963c80a5 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 16 Aug 2017 20:09:07 +0300 Subject: [PATCH 16/51] Exposed error constructors again (for use in include handlers) --- src/errors.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 9fee49c..bcc6e38 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -2,7 +2,7 @@ use std::io; use std::fmt; use std::rc::Rc; use std::slice::Iter; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::cell::RefCell; use super::tokenizer::{self, Pos}; @@ -56,28 +56,31 @@ impl ::serde::de::Error for Error { } impl Error { - pub(crate) fn parse_error(pos: &Pos, message: String) -> 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 { ErrorEnum::ParseError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), message).into() } - pub(crate) fn tokenizer_error((pos, err): (Pos, tokenizer::Error)) -> Error { + pub fn tokenizer_error((pos, err): (Pos, tokenizer::Error)) -> Error { ErrorEnum::TokenizerError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), err).into() } - pub(crate) fn validation_error(pos: &Pos, message: String) -> Error { + pub fn validation_error(pos: &Pos, message: String) -> Error { ErrorEnum::ValidationError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), message).into() } - pub(crate) fn decode_error(pos: &Pos, path: &String, message: String) -> Error { + pub fn decode_error(pos: &Pos, path: &String, message: String) -> Error { ErrorEnum::DecodeError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), path.clone(), message).into() } - pub(crate) fn preprocess_error(pos: &Pos, message: String) -> Error { + pub fn preprocess_error(pos: &Pos, message: String) -> Error { ErrorEnum::PreprocessError( ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), message).into() From 237a0d47a99a37174d0e8490e6b2d60553811240 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 17 Aug 2017 06:57:28 +0300 Subject: [PATCH 17/51] Revert "More options for booleans supported" This was my misunderstanding of tests in another project. These extra bools weren't supported earlier and there is no good reason to introduce them now. At least right now. This reverts commit a314885fbd2dfd16b9cec1fea89b942f221b4178. --- src/de.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/de.rs b/src/de.rs index a66700c..5d9b2f4 100644 --- a/src/de.rs +++ b/src/de.rs @@ -91,8 +91,8 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { let value = match *self.ast { A::Scalar(ref pos, _, _, ref val) => { match &val[..] { - "true"|"yes"|"on"|"y"|"1" => true, - "false"|"no"|"off"|"n"|"0" => false, + "true" => true, + "false" => false, _ => { return Err(Error::decode_error(pos, &self.path, format!("bad boolean {:?}", val))); @@ -588,15 +588,7 @@ mod test { #[test] fn decode_bool() { assert_eq!(decode::("true"), true); - assert_eq!(decode::("yes"), true); - assert_eq!(decode::("y"), true); - assert_eq!(decode::("on"), true); - assert_eq!(decode::("1"), true); assert_eq!(decode::("false"), false); - assert_eq!(decode::("no"), false); - assert_eq!(decode::("n"), false); - assert_eq!(decode::("off"), false); - assert_eq!(decode::("0"), false); } #[test] From 2b630bfbb77369070818f41c42c19f1326a800f1 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 17 Aug 2017 07:34:50 +0300 Subject: [PATCH 18/51] Tweaks error messages --- src/de.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/de.rs b/src/de.rs index 5d9b2f4..adbb6a3 100644 --- a/src/de.rs +++ b/src/de.rs @@ -183,7 +183,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?) @@ -198,7 +198,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { } 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) @@ -291,7 +291,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { } ref node => { return Err(Error::decode_error(&node.pos(), &self.path, - format!("sequence expected got {}", node))) + format!("sequence expected, got {}", node))) } } } @@ -342,7 +342,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { } ref node => { return Err(Error::decode_error(&node.pos(), &self.path, - format!("mapping expected got {}", node))) + format!("mapping expected, got {}", node))) } } } @@ -398,7 +398,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { ref node => { return Err(Error::decode_error(&node.pos(), &self.path, format!("identifier (string, or tag) \ - expected got {}", node))) + expected, got {}", node))) } }, } @@ -660,7 +660,7 @@ mod test { } #[test] - #[should_panic(expected="sequence expected got Scalar")] + #[should_panic(expected="sequence expected, got Scalar")] fn decode_list_error() { decode::>("test"); } @@ -820,7 +820,7 @@ mod test { } #[test] - #[should_panic(expected = "sequence expected got Scalar")] + #[should_panic(expected = "sequence expected, got Scalar")] fn test_struct_items_tag() { decode::("items:\n 'hello'"); } From d1d88b62a630f095aeb928333b7975a0c69a0a40 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 24 Aug 2017 00:59:48 +0300 Subject: [PATCH 19/51] Deserializing newtype structs is implemented --- src/de.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/de.rs b/src/de.rs index adbb6a3..45cb4ca 100644 --- a/src/de.rs +++ b/src/de.rs @@ -268,7 +268,7 @@ 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 @@ -825,4 +825,12 @@ mod test { decode::("items:\n 'hello'"); } + #[derive(PartialEq, Eq, Deserialize, Debug)] + struct NewType(u8); + + #[test] + fn test_newtype() { + assert_eq!(decode::("12"), NewType(12)); + } + } From 5dd2f3f13eb86628d3fc98bdc58d4df50ebe0837 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 24 Aug 2017 01:45:33 +0300 Subject: [PATCH 20/51] Fixes deserializing non-string mapping keys --- src/de.rs | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 244 insertions(+), 3 deletions(-) diff --git a/src/de.rs b/src/de.rs index 45cb4ca..514f815 100644 --- a/src/de.rs +++ b/src/de.rs @@ -7,7 +7,7 @@ use std::slice; use std::str::FromStr; -use serde::de::{self, DeserializeSeed, Visitor, SeqAccess}; +use serde::de::{self, DeserializeSeed, Visitor, SeqAccess, Error as DeError}; use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; use ast::{Ast, Ast as A, Tag}; @@ -34,6 +34,7 @@ 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`. @@ -54,6 +55,239 @@ impl<'de> Deserializer<'de> { } } +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, + _ => { + return Err(Error::custom(format!("bad boolean {:?}", self.0))); + } + }; + 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(|| Error::custom("single character expected"))?; + if chars.next().is_some() { + return Err(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(Error::custom("sequence can't be mapping key in quire")) + } + + fn deserialize_tuple( + self, + _len: usize, + _visitor: V + ) -> Result + where V: Visitor<'a> + { + Err(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(Error::custom("tuple struct can't be mapping key in quire")) + } + + fn deserialize_map(self, visitor: V) -> Result + where V: Visitor<'a> + { + Err(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(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(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> { type Error = Error; @@ -474,7 +708,7 @@ impl<'de, 'a, 'b: 'a> MapAccess<'de> for MapVisitor<'a, 'b> { match self.0.peek() { Some(&(key, _)) => { write!(&mut self.2.path, ".{}", key).unwrap(); - let result = seed.deserialize(key.clone().into_deserializer()); + let result = seed.deserialize(KeyDeserializer(key.clone())); Ok(Some(self.2.map_err(result)?)) } None => { @@ -555,7 +789,7 @@ impl<'de, 'a, 'b: 'a> VariantAccess<'de> for VariantVisitor<'a, 'b> { 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; @@ -833,4 +1067,11 @@ mod test { 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::>()); + } } From db0ff2e217b5ad933c59b24473e6a1b11d3b3bbc Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Mon, 4 Sep 2017 23:47:56 +0300 Subject: [PATCH 21/51] Upgrades rust to 1.20, adds packaging stuff --- .travis.yml | 41 +++++++++++++++++++++++++++++++++-------- bulk.yaml | 15 +++++++++++++++ doc/conf.py | 4 ++-- vagga.yaml | 7 ++++++- 4 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 bulk.yaml diff --git a/.travis.yml b/.travis.yml index 53f6e7e..94efa46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,34 @@ +sudo: false +dist: trusty language: rust -rust: -- stable -- beta -- nightly -script: -- cargo build --verbose -- cargo test --no-default-features -- 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/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/conf.py b/doc/conf.py index 029ed57..fcba638 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '2.0' +version = '0.2' # The full version, including alpha/beta/rc tags. -release = '2.0-beta1' +release = '0.2.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/vagga.yaml b/vagga.yaml index b058cae..4a06495 100644 --- a/vagga.yaml +++ b/vagga.yaml @@ -11,7 +11,7 @@ containers: - !Install [make, ca-certificates, build-essential, vim] - !TarInstall - url: "https://static.rust-lang.org/dist/rust-1.19.0-x86_64-unknown-linux-gnu.tar.gz" + url: "https://static.rust-lang.org/dist/rust-1.20.0-x86_64-unknown-linux-gnu.tar.gz" script: "./install.sh --prefix=/usr \ --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" - &bulk !Tar @@ -48,3 +48,8 @@ commands: ------------------------------------------------------------------------ Docs are built in 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] From 7d8c9954db7285f62fa65ade35004f802ea0ace5 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Mon, 4 Sep 2017 21:07:13 +0000 Subject: [PATCH 22/51] Version bumped to v0.3.0 --- Cargo.toml | 2 +- doc/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 896604b..d9da2df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT/Apache-2.0" readme = "README.rst" keywords = ["config", "yaml", "parser"] homepage = "http://github.com/tailhook/rust-quire" -version = "0.2.3" +version = "0.3.0" authors = ["paul@colomiets.name"] [dependencies] diff --git a/doc/conf.py b/doc/conf.py index fcba638..651c586 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '0.2' +version = '0.3' # The full version, including alpha/beta/rc tags. -release = '0.2.3' +release = '0.3.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From b897eda93ce26ae867d717670cf0800b186984ac Mon Sep 17 00:00:00 2001 From: Alexander Koval Date: Thu, 21 Sep 2017 00:04:34 +0300 Subject: [PATCH 23/51] Custom errors for validators --- src/errors.rs | 49 ++++++++++++++++++++++++++++----- src/validate.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 8 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index bcc6e38..40235ab 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,7 @@ use std::io; use std::fmt; use std::rc::Rc; +use std::error::Error as StdError; use std::slice::Iter; use std::path::{Path, PathBuf}; use std::cell::RefCell; @@ -42,16 +43,26 @@ 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 { - ErrorEnum::Custom(format!("{}", msg)).into() + fn custom(msg: T) -> Self { + ErrorEnum::SerdeError(format!("{}", msg)).into() } } @@ -85,6 +96,30 @@ impl Error { ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset), message).into() } + + pub fn custom_error(err: T) + -> Error + { + ErrorEnum::CustomError(None, Box::new(err)).into() + } + + pub fn custom_error_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, + } + } } /// List of errors that were encountered during configuration file parsing @@ -170,8 +205,8 @@ pub fn add_info(pos: &Pos, path: &String, result: Result) -> Result { match result { - Err(Error(ErrorEnum::Custom(e))) => { - Err(Error::decode_error(pos, path, e)) + Err(Error(ErrorEnum::SerdeError(e))) => { + Err(Error::decode_error(pos, path, format!("{}", e))) } result => result, } diff --git a/src/validate.rs b/src/validate.rs index 2a21207..bb3584a 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -713,10 +713,13 @@ impl Validator for Nothing { #[cfg(test)] mod test { + use std::fmt; use std::rc::Rc; use std::path::PathBuf; use std::collections::BTreeMap; use std::collections::HashMap; + use std::error::Error as StdError; + use serde::Deserialize; use {Options}; @@ -728,8 +731,9 @@ mod test { use {parse_string, ErrorList}; use validate::{Validator, Structure, Scalar, Numeric, Mapping, Sequence}; use validate::{Enum, Nothing, Directory, Anything}; - use errors::ErrorCollector; + use errors::{Error, ErrorCollector}; use self::TestEnum::*; + use super::Pos; #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] struct TestStruct { @@ -1453,4 +1457,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: 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_error_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), + } + } } From 1240b1707a4e40c3fb25e73133ef12974a442b06 Mon Sep 17 00:00:00 2001 From: Alexander Koval Date: Thu, 21 Sep 2017 18:42:37 +0300 Subject: [PATCH 24/51] Refactor Error's constructors names --- src/de.rs | 25 ++++++++++++++----------- src/errors.rs | 4 ++-- src/validate.rs | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/de.rs b/src/de.rs index 514f815..e0f44c3 100644 --- a/src/de.rs +++ b/src/de.rs @@ -7,7 +7,7 @@ use std::slice; use std::str::FromStr; -use serde::de::{self, DeserializeSeed, Visitor, SeqAccess, Error as DeError}; +use serde::de::{self, DeserializeSeed, Visitor, SeqAccess}; use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; use ast::{Ast, Ast as A, Tag}; @@ -70,7 +70,8 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { "true" => true, "false" => false, _ => { - return Err(Error::custom(format!("bad boolean {:?}", self.0))); + let e: Error = de::Error::custom(format!("bad boolean {:?}", self.0)); + return Err(e); } }; visitor.visit_bool(value) @@ -140,10 +141,12 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { where V: Visitor<'a> { let mut chars = self.0.chars(); - let val = chars.next() - .ok_or_else(|| Error::custom("single character expected"))?; + let val = (chars.next() + .ok_or_else(|| { + de::Error::custom("single character expected") + }) as Result<_>)?; if chars.next().is_some() { - return Err(Error::custom("single character expected")) + return Err(de::Error::custom("single character expected")) } visitor.visit_char(val) } @@ -216,7 +219,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { fn deserialize_seq(self, _visitor: V) -> Result where V: Visitor<'a> { - Err(Error::custom("sequence can't be mapping key in quire")) + Err(de::Error::custom("sequence can't be mapping key in quire")) } fn deserialize_tuple( @@ -226,7 +229,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { ) -> Result where V: Visitor<'a> { - Err(Error::custom("tuple can't be mapping key in quire")) + Err(de::Error::custom("tuple can't be mapping key in quire")) } // Tuple structs look just like sequences in JSON. @@ -238,13 +241,13 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { ) -> Result where V: Visitor<'a> { - Err(Error::custom("tuple struct can't be mapping key in quire")) + 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(Error::custom("mapping can't be mapping key in quire")) + Err(de::Error::custom("mapping can't be mapping key in quire")) } fn deserialize_struct( self, @@ -254,7 +257,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { ) -> Result where V: Visitor<'a> { - Err(Error::custom("struct can't be mapping key in quire")) + Err(de::Error::custom("struct can't be mapping key in quire")) } fn deserialize_enum( @@ -266,7 +269,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { where V: Visitor<'a> { // TODO(tailhook) some support might work - Err(Error::custom("enum can't be mapping key in quire")) + Err(de::Error::custom("enum can't be mapping key in quire")) } fn deserialize_identifier( diff --git a/src/errors.rs b/src/errors.rs index 40235ab..fd6d286 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -97,13 +97,13 @@ impl Error { message).into() } - pub fn custom_error(err: T) + pub fn custom(err: T) -> Error { ErrorEnum::CustomError(None, Box::new(err)).into() } - pub fn custom_error_at(pos: &Pos, err: T) + pub fn custom_at(pos: &Pos, err: T) -> Error { ErrorEnum::CustomError( diff --git a/src/validate.rs b/src/validate.rs index bb3584a..6af11a3 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1490,7 +1490,7 @@ mod test { match ast { A::Scalar(pos, tag, kind, version) => { if !version.starts_with("v") { - err.add_error(Error::custom_error_at( + err.add_error(Error::custom_at( &pos, VersionError("Version must start with 'v'"))) } From 3e32f14704426624c1fc56c40d5bf435ab9968ca Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 21 Sep 2017 18:58:47 +0300 Subject: [PATCH 25/51] Adds "config" category to Cargo.toml --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index d9da2df..662abe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ license = "MIT/Apache-2.0" readme = "README.rst" keywords = ["config", "yaml", "parser"] homepage = "http://github.com/tailhook/rust-quire" +categories = ["config"] version = "0.3.0" authors = ["paul@colomiets.name"] From c2e792df32a3db564d8eef515e5f5b1ff175d5c1 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 21 Sep 2017 19:00:16 +0300 Subject: [PATCH 26/51] Converted README.rst -> README.md --- Cargo.toml | 2 +- README.rst => README.md | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) rename README.rst => README.md (59%) diff --git a/Cargo.toml b/Cargo.toml index 662abe2..225647b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ 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" categories = ["config"] diff --git a/README.rst b/README.md similarity index 59% rename from README.rst rename to README.md index 7bf61c9..e706db6 100644 --- a/README.rst +++ b/README.md @@ -1,18 +1,18 @@ -========== 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). -.. _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 From d43953d8e42fa8b7a91876d1e6c3491c1b6657aa Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 21 Sep 2017 16:06:59 +0000 Subject: [PATCH 27/51] Version bumped to v0.3.1 --- Cargo.toml | 2 +- doc/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 225647b..f16d897 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" keywords = ["config", "yaml", "parser"] homepage = "http://github.com/tailhook/rust-quire" categories = ["config"] -version = "0.3.0" +version = "0.3.1" authors = ["paul@colomiets.name"] [dependencies] diff --git a/doc/conf.py b/doc/conf.py index 651c586..77d1d62 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -51,7 +51,7 @@ # The short X.Y version. version = '0.3' # The full version, including alpha/beta/rc tags. -release = '0.3.0' +release = '0.3.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 8a6d8a34de97f8bd92c9dbd00aa7864093d1975e Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Sat, 16 Dec 2017 02:04:20 +0200 Subject: [PATCH 28/51] vagga.yaml: Upgrade rust to v1.22.1, remove cargo-outdated from container --- vagga.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vagga.yaml b/vagga.yaml index 4a06495..a5adc38 100644 --- a/vagga.yaml +++ b/vagga.yaml @@ -11,14 +11,13 @@ containers: - !Install [make, ca-certificates, build-essential, vim] - !TarInstall - url: "https://static.rust-lang.org/dist/rust-1.20.0-x86_64-unknown-linux-gnu.tar.gz" + url: "https://static.rust-lang.org/dist/rust-1.22.1-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.9/bulk-v0.4.9.tar.gz" sha256: 23471a9986274bb4b7098c03e2eb7e1204171869b72c45385fcee1c64db2d111 path: / - - !Sh cargo install --root=/usr cargo-outdated - !EnsureDir /cargo environ: HOME: /work/target From a76753881e1cc847cfbb2c6349e049003ca4db74 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Sat, 16 Dec 2017 02:45:00 +0200 Subject: [PATCH 29/51] Eliminate few warnings --- src/de.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/de.rs b/src/de.rs index e0f44c3..158f0a9 100644 --- a/src/de.rs +++ b/src/de.rs @@ -511,7 +511,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { // 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 { @@ -561,7 +561,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { unimplemented!(); } - fn deserialize_map(mut self, visitor: V) -> Result + fn deserialize_map(self, visitor: V) -> Result where V: Visitor<'de> { match *self.ast { From a412bcb5d41cf2117d8d7d04eccf78502fe6ac04 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Sat, 16 Dec 2017 22:19:27 +0200 Subject: [PATCH 30/51] [breaking] Add support of IncludeSeq and IncludeMap --- doc/user.rst | 83 +++++++++++++++++++++++++++++++++++++++++++ src/ast.rs | 8 +++++ src/options.rs | 17 ++++++--- src/test_transcode.rs | 45 ++++++++++++++++++++++- 4 files changed, 147 insertions(+), 6 deletions(-) diff --git a/doc/user.rst b/doc/user.rst index 35f38d4..fbf3bc4 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -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/src/ast.rs b/src/ast.rs index ad6a7a0..4bf39c9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -179,6 +179,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); diff --git a/src/options.rs b/src/options.rs index 83620e9..2ba798c 100644 --- a/src/options.rs +++ b/src/options.rs @@ -13,11 +13,18 @@ pub enum Include<'a> { // 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 diff --git a/src/test_transcode.rs b/src/test_transcode.rs index b08ed07..b1c22e1 100644 --- a/src/test_transcode.rs +++ b/src/test_transcode.rs @@ -5,7 +5,7 @@ use serde_json::ser::Serializer; use serde_json::de::{from_str, from_slice}; use serde_transcode::transcode; -use ast::Ast; +use ast::{Ast, Tag}; use ast::process; use de::Deserializer; use errors::ErrorCollector; @@ -415,6 +415,33 @@ fn assert_yaml_eq_json_incl(a: &'static str, inc_data: &'static str, ).map_err(|e| err.add_error(e)) .unwrap_or_else(|_| Ast::void(pos)) } + Include::Sequence { pattern } => { + 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 { pattern } => { + 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(); @@ -455,6 +482,22 @@ fn test_incl_merge() { 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"}"#); From 265511a41b081a6c0e85cbdf71ad34eb30b64814 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 9 Feb 2018 10:24:26 +0200 Subject: [PATCH 31/51] Allow tag and anchor in any order --- src/parser.rs | 5 ++++- src/test_transcode.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 11722c7..492b1b9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -619,8 +619,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 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(); diff --git a/src/test_transcode.rs b/src/test_transcode.rs index b1c22e1..a0aa372 100644 --- a/src/test_transcode.rs +++ b/src/test_transcode.rs @@ -402,6 +402,16 @@ fn yaml_tag_null_in_map() { 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) { From 9e75f86f4299294a9bcd55d0d6311ee3fe0f9f86 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 11 Apr 2018 22:32:55 +0300 Subject: [PATCH 32/51] Fix empty lines in literals when CRLF newlines are used --- src/parser.rs | 2 +- src/test_transcode.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 492b1b9..adbc764 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -194,7 +194,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; diff --git a/src/test_transcode.rs b/src/test_transcode.rs index a0aa372..9333076 100644 --- a/src/test_transcode.rs +++ b/src/test_transcode.rs @@ -343,6 +343,11 @@ 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""#); From 221cbdba2b8994e935e235df03beb50a4d408fea Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 12 Apr 2018 00:09:47 +0300 Subject: [PATCH 33/51] Better error reporting for nonimplemented things --- src/parser.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index adbc764..4e00878 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(); @@ -211,11 +220,15 @@ 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!(), } From c1d8464e75a3cab3009e870577a1f87e617fab47 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 25 May 2018 11:02:55 +0300 Subject: [PATCH 34/51] [breaking] Remove regex module (use serde_regex), remove json module --- Cargo.toml | 5 ----- src/lib.rs | 3 --- src/regex.rs | 53 ---------------------------------------------------- 3 files changed, 61 deletions(-) delete mode 100644 src/regex.rs diff --git a/Cargo.toml b/Cargo.toml index f16d897..97af42c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,6 @@ humantime = "1.0.0" num-traits = "0.1.36" humannum = "0.1.0" serde = "1.0.10" -regex = {version="0.2.2", optional=true} - -[features] -regex_expressions = ["regex"] -default = ["regex_expressions"] [dev-dependencies] serde_derive = "1.0.10" diff --git a/src/lib.rs b/src/lib.rs index d7bf7fc..d253fbd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,6 @@ extern crate humannum; extern crate humantime; extern crate num_traits; extern crate serde; -#[cfg(feature="regex_expressions")] extern crate regex as re; #[cfg(test)] #[macro_use] extern crate serde_derive; #[cfg(test)] extern crate serde_json; #[cfg(test)] extern crate serde_transcode; @@ -59,8 +58,6 @@ mod sky; pub mod ast; pub mod validate; pub mod duration; -#[cfg(feature="regex_expressions")] pub mod regex; -#[cfg(feature="json")] mod json; #[cfg(test)] mod test_errors; #[cfg(test)] mod test_util; #[cfg(test)] mod test_transcode; diff --git a/src/regex.rs b/src/regex.rs deleted file mode 100644 index 80d2c42..0000000 --- a/src/regex.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! A module that parses regex from string -//! -//! # Example -//! -//! ```rust -//! -//! # extern crate quire; -//! # extern crate serde; -//! # extern crate regex; -//! # #[macro_use] extern crate serde_derive; -//! -//! #[derive(Serialize)] -//! struct SomeStruct { -//! #[serde(with="quire::regex")] -//! regex: regex::Regex, -//! } -//! -//! # fn main() {} -//! -//! ``` - -use std::fmt; -use re::Regex; - -use serde::ser::{Serializer, Serialize}; -use serde::de::{Deserializer, Error, Visitor}; - -struct RegexVisitor; - -pub fn serialize(re: &Regex, s: S) -> Result - where S: Serializer -{ - re.as_str().serialize(s) -} - -impl<'a> Visitor<'a> for RegexVisitor { - type Value = Regex; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("regular expression") - } - - fn visit_str(self, val: &str) -> Result - where E: Error - { - Regex::new(val).map_err(E::custom) - } -} - -pub fn deserialize<'x, D>(d: D) -> Result - where D: Deserializer<'x> -{ - d.deserialize_str(RegexVisitor) -} From 9d247ed3dc70de1ce028188a2b2078c6d2f07515 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 25 May 2018 12:45:01 +0300 Subject: [PATCH 35/51] Fix most warnings --- src/de.rs | 22 +++++++++++----------- src/lib.rs | 3 --- src/validate.rs | 14 -------------- 3 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/de.rs b/src/de.rs index 158f0a9..f79d6ce 100644 --- a/src/de.rs +++ b/src/de.rs @@ -190,7 +190,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { } } - fn deserialize_unit(self, visitor: V) -> Result + fn deserialize_unit(self, _visitor: V) -> Result where V: Visitor<'a> { unimplemented!(); @@ -199,7 +199,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { fn deserialize_unit_struct( self, _name: &'static str, - visitor: V + _visitor: V ) -> Result where V: Visitor<'a> { @@ -244,7 +244,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer { Err(de::Error::custom("tuple struct can't be mapping key in quire")) } - fn deserialize_map(self, visitor: V) -> Result + fn deserialize_map(self, _visitor: V) -> Result where V: Visitor<'a> { Err(de::Error::custom("mapping can't be mapping key in quire")) @@ -430,7 +430,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { where V: Visitor<'de> { let val = match *self.ast { - A::Scalar(ref pos, _, _, ref val) => { + A::Scalar(_, _, _, ref val) => { val } ref node => { @@ -542,7 +542,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> { @@ -554,7 +554,7 @@ 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> { @@ -593,7 +593,7 @@ impl<'de: 'a, 'a, 'b> de::Deserializer<'de> for &'a mut Deserializer<'b> { fn deserialize_struct( self, _name: &'static str, - fields: &'static [&'static str], + _fields: &'static [&'static str], visitor: V ) -> Result where V: Visitor<'de> @@ -769,8 +769,8 @@ impl<'de, 'a, 'b: 'a> VariantAccess<'de> for VariantVisitor<'a, 'b> { } fn tuple_variant( self, - len: usize, - visitor: V + _len: usize, + _visitor: V ) -> Result where V: Visitor<'de> @@ -779,8 +779,8 @@ impl<'de, 'a, 'b: 'a> VariantAccess<'de> for VariantVisitor<'a, 'b> { } fn struct_variant( self, - fields: &'static [&'static str], - visitor: V + _fields: &'static [&'static str], + _visitor: V ) -> Result where V: Visitor<'de> diff --git a/src/lib.rs b/src/lib.rs index d253fbd..85f442b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,9 +26,6 @@ //! //! ``` //! -//#![warn(missing_docs)] -#![allow(dead_code)] -#![allow(unused_variables)] extern crate humannum; diff --git a/src/validate.rs b/src/validate.rs index 6af11a3..6eb8df8 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -40,7 +40,6 @@ pub trait Validator { /// `Numeric` has minimum and maximum value as well as decodes human-friendly /// unit values pub struct Scalar { - descr: Option, optional: bool, default: Option, min_length: Option, @@ -50,7 +49,6 @@ pub struct Scalar { impl Scalar { pub fn new() -> Scalar { Scalar { - descr: None, optional: false, default: None, min_length: None, @@ -118,7 +116,6 @@ impl Validator for Scalar { /// Similar to `Scalar` but validates that value is a number and also allows /// limit the range of the value. pub struct Numeric { - descr: Option, optional: bool, default: Option, min: Option, @@ -128,7 +125,6 @@ pub struct Numeric { impl Numeric { pub fn new() -> Numeric { Numeric { - descr: None, optional: false, default: None, min: None, @@ -204,7 +200,6 @@ impl Validator for Numeric { /// /// Similar to `Scalar` but also allows to force absolute or relative paths pub struct Directory { - descr: Option, optional: bool, default: Option, absolute: Option, @@ -213,7 +208,6 @@ pub struct Directory { impl Directory { pub fn new() -> Directory { Directory { - descr: None, optional: false, default: None, absolute: None, @@ -300,7 +294,6 @@ impl Validator for Directory { /// a structure maintaining backwards compatiblity as well as for configuring /// common case more easily. pub struct Structure<'a> { - descr: Option, members: Vec<(String, Box)>, optional: bool, from_scalar: Option BTreeMap>, @@ -309,7 +302,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, @@ -418,7 +410,6 @@ impl<'a> Validator for Structure<'a> { /// equivalent to an option with that struct as single value /// `struct A { x: u8 }; enum T { a(A) }` pub struct Enum<'a> { - descr: Option, options: Vec<(String, Box)>, optional: bool, default_tag: Option, @@ -429,7 +420,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, @@ -540,7 +530,6 @@ impl<'a> Validator for Enum<'a> { /// This type has type for a key and value and also can be converted /// from scalar as shortcut. pub struct Mapping<'a> { - descr: Option, key_element: Box, value_element: Box, from_scalar: Option BTreeMap>, @@ -551,7 +540,6 @@ impl<'a> Mapping<'a> { -> Mapping<'a> { Mapping { - descr: None, key_element: Box::new(key), value_element: Box::new(val), from_scalar: None, @@ -608,7 +596,6 @@ impl<'a> Validator for Mapping<'a> { /// This validator can also parse a scalar and convert it into a list in /// application-specific way. pub struct Sequence<'a> { - descr: Option, element: Box, from_scalar: Option Vec>, min_length: usize, @@ -617,7 +604,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, From ceb6b03fa8871cda13a14db0b2324f276fca9f4a Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 25 May 2018 13:33:32 +0300 Subject: [PATCH 36/51] Remove unused attributes of Deserializer --- src/ast.rs | 4 +--- src/de.rs | 12 +++--------- src/sky.rs | 4 ++-- src/test_errors.rs | 2 +- src/test_transcode.rs | 4 ++-- src/test_util.rs | 2 +- src/validate.rs | 2 +- 7 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 4bf39c9..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, } @@ -379,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 f79d6ce..7516be9 100644 --- a/src/de.rs +++ b/src/de.rs @@ -11,7 +11,7 @@ use serde::de::{self, DeserializeSeed, Visitor, SeqAccess}; use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; use ast::{Ast, Ast as A, Tag}; -use errors::{Error, add_info, ErrorCollector}; +use errors::{Error, add_info}; type Result = ::std::result::Result; @@ -24,7 +24,6 @@ pub enum Mode { pub struct Deserializer<'a> { ast: &'a Ast, mode: Mode, - err: ErrorCollector, path: String, } @@ -37,15 +36,10 @@ 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, mode: Mode::Normal, - err: err.clone(), path: "".to_string(), } } @@ -811,7 +805,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() } diff --git a/src/sky.rs b/src/sky.rs index cbe4c38..32eadde 100644 --- a/src/sky.rs +++ b/src/sky.rs @@ -32,7 +32,7 @@ pub fn parse_config<'x, T: Deserialize<'x>, P: AsRef>( |doc| { ast::process(options, doc, &err) } ).map_err(|e| err.into_fatal(e))?; let ast = validator.validate(ast, &err); - let res = Deserialize::deserialize(&mut Deserializer::new(&ast, &err)) + let res = Deserialize::deserialize(&mut Deserializer::new(&ast)) .map_err(|e| err.into_fatal(e))?; return err.into_result(res); } @@ -47,7 +47,7 @@ pub fn parse_string<'x, T: Deserialize<'x>>(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 = Deserialize::deserialize(&mut Deserializer::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/test_errors.rs b/src/test_errors.rs index 02865e4..f4f7841 100644 --- a/src/test_errors.rs +++ b/src/test_errors.rs @@ -26,7 +26,7 @@ fn decode<'x, T: Deserialize<'x>>(data: &str) -> Result { data, |doc| { process(&Options::default(), doc, &err) } ).map_err(|e| err.into_fatal(e).to_string())?; - T::deserialize(&mut Deserializer::new(&ast, &err)) + 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()) diff --git a/src/test_transcode.rs b/src/test_transcode.rs index 9333076..b871877 100644 --- a/src/test_transcode.rs +++ b/src/test_transcode.rs @@ -19,7 +19,7 @@ fn assert_yaml_eq_json(a: &'static str, b: &'static str) { |doc| { process(&Options::default(), doc, &err) }, ).map_err(|e| err.into_fatal(e)).unwrap(); err.into_result(()).unwrap(); - let mut de = Deserializer::new(&ast, &err); + 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(); @@ -464,7 +464,7 @@ fn assert_yaml_eq_json_incl(a: &'static str, inc_data: &'static str, |doc| { process(&opt, doc, &err) }, ).map_err(|e| err.into_fatal(e)).unwrap(); err.into_result(()).unwrap(); - let mut de = Deserializer::new(&ast, &err); + 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(); diff --git a/src/test_util.rs b/src/test_util.rs index d5169a9..ce78140 100644 --- a/src/test_util.rs +++ b/src/test_util.rs @@ -16,7 +16,7 @@ pub fn decode<'x, T: Deserialize<'x>>(data: &str) -> T { data, |doc| { process(&Options::default(), doc, &err) } ).map_err(|e| err.into_fatal(e)).unwrap(); - Deserialize::deserialize(&mut Deserializer::new(&ast, &err)) + Deserialize::deserialize(&mut Deserializer::new(&ast)) .map_err(|e| err.into_fatal(e)) .unwrap() } diff --git a/src/validate.rs b/src/validate.rs index 6eb8df8..602fa12 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -836,7 +836,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 Deserialize::deserialize(&mut Deserializer::new(&ast, &err)) { + match Deserialize::deserialize(&mut Deserializer::new(&ast)) { Ok(val) => { (val, err.unwrap().errors().map(|x| x.to_string()).collect()) } From 743857959700e54b044872e542277fc2a157d4db Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 25 May 2018 13:36:05 +0300 Subject: [PATCH 37/51] Fix more warnings --- src/lib.rs | 1 - src/test_transcode.rs | 4 ++-- src/test_util.rs | 22 ---------------------- src/validate.rs | 6 +++--- 4 files changed, 5 insertions(+), 28 deletions(-) delete mode 100644 src/test_util.rs diff --git a/src/lib.rs b/src/lib.rs index 85f442b..e83cba1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,5 +56,4 @@ pub mod ast; pub mod validate; pub mod duration; #[cfg(test)] mod test_errors; -#[cfg(test)] mod test_util; #[cfg(test)] mod test_transcode; diff --git a/src/test_transcode.rs b/src/test_transcode.rs index b871877..8947e85 100644 --- a/src/test_transcode.rs +++ b/src/test_transcode.rs @@ -430,7 +430,7 @@ fn assert_yaml_eq_json_incl(a: &'static str, inc_data: &'static str, ).map_err(|e| err.add_error(e)) .unwrap_or_else(|_| Ast::void(pos)) } - Include::Sequence { pattern } => { + Include::Sequence { .. } => { let inc1 = parse("inc1.yaml".to_string().into(), inc_data, |doc| { process(&opt, doc, err) }, ).map_err(|e| err.add_error(e)) @@ -442,7 +442,7 @@ fn assert_yaml_eq_json_incl(a: &'static str, inc_data: &'static str, Ast::Seq(pos.clone(), Tag::NonSpecific, vec![inc1, inc2]) } - Include::Mapping { pattern } => { + Include::Mapping { .. } => { let inc1 = parse("inc1.yaml".to_string().into(), inc_data, |doc| { process(&opt, doc, err) }, ).map_err(|e| err.add_error(e)) diff --git a/src/test_util.rs b/src/test_util.rs deleted file mode 100644 index ce78140..0000000 --- a/src/test_util.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::rc::Rc; - -use serde::Deserialize; - -use de::Deserializer; -use parser::parse; -use ast::process; -use errors::ErrorCollector; - -use {Options}; - -pub fn decode<'x, T: Deserialize<'x>>(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(); - Deserialize::deserialize(&mut Deserializer::new(&ast)) - .map_err(|e| err.into_fatal(e)) - .unwrap() -} diff --git a/src/validate.rs b/src/validate.rs index 602fa12..8fb0c65 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1010,7 +1010,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| { @@ -1032,7 +1032,7 @@ mod test { { fn split(ast: A) -> Vec { match ast { - A::Scalar(pos, _, style, value) => { + A::Scalar(pos, _, _style, value) => { value .split(" ") .map(|v| { @@ -1468,7 +1468,7 @@ mod test { } impl Validator for Version { - fn default(&self, pos: Pos) -> Option { + fn default(&self, _: Pos) -> Option { None } From 3fbcb634910fb8d1a9d72a45bcd1a8090a37cbc3 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 25 May 2018 13:40:37 +0300 Subject: [PATCH 38/51] Remove duration parser (use humantime directly) --- Cargo.toml | 1 - src/duration.rs | 61 ------------------------------------------------- src/lib.rs | 2 -- 3 files changed, 64 deletions(-) delete mode 100644 src/duration.rs diff --git a/Cargo.toml b/Cargo.toml index 97af42c..3efa9b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ authors = ["paul@colomiets.name"] [dependencies] quick-error = "1.2.0" -humantime = "1.0.0" num-traits = "0.1.36" humannum = "0.1.0" serde = "1.0.10" diff --git a/src/duration.rs b/src/duration.rs deleted file mode 100644 index b227016..0000000 --- a/src/duration.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! A module that parses human-friendly duration -//! -//! # Example -//! -//! ```rust -//! -//! # extern crate quire; -//! # extern crate serde; -//! # #[macro_use] extern crate serde_derive; -//! # use std::time::Duration; -//! -//! #[derive(Serialize)] -//! struct SomeStruct { -//! #[serde(with="quire::duration")] -//! timeout: Duration, -//! } -//! -//! # fn main() {} -//! -//! ``` - -use std::fmt; -use std::time::Duration; - -use humantime::parse_duration; -use serde::ser::{Serializer, Serialize}; -use serde::de::{Deserializer, Error, Visitor}; - -struct DurationVisitor; - -pub fn serialize(duration: &Duration, s: S) -> Result - where S: Serializer -{ - // TODO(tailhook) make nicers serialization - let secs = duration.as_secs(); - let nanos = duration.subsec_nanos(); - if nanos > 0 { - format!("{}s {}ns", secs, nanos).serialize(s) - } else { - format!("{}s", secs).serialize(s) - } -} - -impl<'a> Visitor<'a> for DurationVisitor { - type Value = Duration; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("duration (like `1h 12m 35sec`)") - } - - fn visit_str(self, val: &str) -> Result - where E: Error - { - parse_duration(val).map_err(E::custom) - } -} - -pub fn deserialize<'x, D>(d: D) -> Result - where D: Deserializer<'x> -{ - d.deserialize_str(DurationVisitor) -} diff --git a/src/lib.rs b/src/lib.rs index e83cba1..4cc83df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,6 @@ extern crate humannum; -extern crate humantime; extern crate num_traits; extern crate serde; #[cfg(test)] #[macro_use] extern crate serde_derive; @@ -54,6 +53,5 @@ mod de; mod sky; pub mod ast; pub mod validate; -pub mod duration; #[cfg(test)] mod test_errors; #[cfg(test)] mod test_transcode; From d1e7bdf40fcd6bde0ced4d488b1602f26dcf75da Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 25 May 2018 13:48:11 +0300 Subject: [PATCH 39/51] Implement debug for everything --- src/de.rs | 1 + src/errors.rs | 2 +- src/lib.rs | 1 + src/options.rs | 10 ++++++++++ src/parser.rs | 2 ++ src/tokenizer.rs | 1 + src/validate.rs | 13 +++++++++++-- 7 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/de.rs b/src/de.rs index 7516be9..0ee7e9c 100644 --- a/src/de.rs +++ b/src/de.rs @@ -21,6 +21,7 @@ pub enum Mode { Enum, } +#[derive(Debug)] pub struct Deserializer<'a> { ast: &'a Ast, mode: Mode, diff --git a/src/errors.rs b/src/errors.rs index fd6d286..09b2b68 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -160,7 +160,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 { diff --git a/src/lib.rs b/src/lib.rs index 4cc83df..73a4469 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ //! //! ``` //! +#![warn(missing_debug_implementations)] extern crate humannum; diff --git a/src/options.rs b/src/options.rs index 2ba798c..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,6 +9,7 @@ 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 }, @@ -69,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 4e00878..904b01c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -235,8 +235,10 @@ fn plain_value<'a>(tok: &Token<'a>) -> Result { 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>, 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 8fb0c65..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,6 +39,7 @@ 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 { optional: bool, default: Option, @@ -115,6 +116,7 @@ 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 { optional: bool, default: Option, @@ -199,6 +201,7 @@ impl Validator for Numeric { /// Directory validator /// /// Similar to `Scalar` but also allows to force absolute or relative paths +#[derive(Debug)] pub struct Directory { optional: bool, default: Option, @@ -293,6 +296,7 @@ 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> { members: Vec<(String, Box)>, optional: bool, @@ -409,6 +413,7 @@ 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> { options: Vec<(String, Box)>, optional: bool, @@ -529,6 +534,7 @@ 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> { key_element: Box, value_element: Box, @@ -595,6 +601,7 @@ 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> { element: Box, from_scalar: Option Vec>, @@ -666,6 +673,7 @@ impl<'a> Validator for Sequence<'a> { /// /// It's useful to accept any value (of any type) at some place, or to /// rely on `Deserialize::deserialize` for doing validation. +#[derive(Debug)] pub struct Anything; impl Validator for Anything { @@ -680,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 { From bca4800fdb4b70d9069b161f392792e345293c95 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 25 May 2018 13:57:45 +0300 Subject: [PATCH 40/51] Make Error implement Send + Sync --- src/errors.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 09b2b68..709a8cd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -11,6 +11,10 @@ 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 /// @@ -46,7 +50,7 @@ quick_error! { SerdeError(msg: String) { display("{}", msg) } - CustomError(pos: Option, err: Box) { + CustomError(pos: Option, err: Box) { display(x) -> ("{loc}{err}", loc=if let &Some(ref p) = pos { format!("{filename}:{line}:{offset}: ", @@ -97,13 +101,13 @@ impl Error { message).into() } - pub fn custom(err: T) + pub fn custom(err: T) -> Error { ErrorEnum::CustomError(None, Box::new(err)).into() } - pub fn custom_at(pos: &Pos, err: T) + pub fn custom_at(pos: &Pos, err: T) -> Error { ErrorEnum::CustomError( From cbdfe172aac0982584974edc4380dab75ed7fc4e Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 25 May 2018 14:03:35 +0300 Subject: [PATCH 41/51] Fix duration test --- Cargo.toml | 1 + src/de.rs | 2 +- src/lib.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3efa9b6..f8bb4b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ serde = "1.0.10" serde_derive = "1.0.10" serde_json = "1.0.2" serde-transcode = "1.0.0" +serde-humantime = "0.1.1" diff --git a/src/de.rs b/src/de.rs index 0ee7e9c..b665c86 100644 --- a/src/de.rs +++ b/src/de.rs @@ -827,7 +827,7 @@ mod test { fn decode_duration() { #[derive(Deserialize, PartialEq, Debug)] struct Dur { - #[serde(with="::duration")] + #[serde(with="::serde_humantime")] dur: Duration, } assert_eq!(decode::("dur: 1m 15s"), diff --git a/src/lib.rs b/src/lib.rs index 73a4469..d7bcabc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ extern crate humannum; extern crate num_traits; 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; From 1012d425ff8ab5688a86c5502ed4c361c108cb08 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Sun, 27 May 2018 10:41:36 +0000 Subject: [PATCH 42/51] Version bumped to v0.4.0 --- Cargo.toml | 2 +- doc/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f8bb4b6..97446bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" keywords = ["config", "yaml", "parser"] homepage = "http://github.com/tailhook/rust-quire" categories = ["config"] -version = "0.3.1" +version = "0.4.0" authors = ["paul@colomiets.name"] [dependencies] diff --git a/doc/conf.py b/doc/conf.py index 77d1d62..ce2426d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '0.3' +version = '0.4' # The full version, including alpha/beta/rc tags. -release = '0.3.1' +release = '0.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 3caa66d12943aef4fbf661f385e76e295d13db78 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 31 May 2018 13:50:21 +0300 Subject: [PATCH 43/51] Fix typo --- doc/user.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user.rst b/doc/user.rst index fbf3bc4..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 From 19639924b0cb8c3cee96359f4db354fe6f43e72a Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Mon, 23 Jul 2018 15:39:37 +0300 Subject: [PATCH 44/51] vagga.yaml: upgrade containers --- vagga.yaml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vagga.yaml b/vagga.yaml index a5adc38..efded9c 100644 --- a/vagga.yaml +++ b/vagga.yaml @@ -2,21 +2,23 @@ containers: doc: setup: - - !Alpine v3.6 + - !Alpine v3.8 + - !Repo edge/main + - !Repo edge/community - !Install [make, py3-sphinx] build: setup: - - !Ubuntu xenial + - !Ubuntu bionic - !Install [make, ca-certificates, build-essential, vim] - !TarInstall - url: "https://static.rust-lang.org/dist/rust-1.22.1-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.9/bulk-v0.4.9.tar.gz" - sha256: 23471a9986274bb4b7098c03e2eb7e1204171869b72c45385fcee1c64db2d111 + url: "https://github.com/tailhook/bulk/releases/download/v0.4.12/bulk-v0.4.12.tar.gz" + sha256: 7deeb4895b3909afea46194ef01bafdeb30ff89fc4a7b6497172ba117734040e path: / - !EnsureDir /cargo environ: @@ -45,7 +47,7 @@ commands: work-dir: doc epilog: | ------------------------------------------------------------------------ - Docs are built in doc/_build/html/index.html + xdg-open doc/_build/html/index.html run: [make, html, SPHINXBUILD=sphinx-build-3] _bulk: !Command From ea44229abcfe14c991f15a81b811b1141f06bc52 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Mon, 23 Jul 2018 15:46:05 +0300 Subject: [PATCH 45/51] Upgrade dependencies --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 97446bf..2e95114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ authors = ["paul@colomiets.name"] [dependencies] quick-error = "1.2.0" -num-traits = "0.1.36" +num-traits = "0.2.5" humannum = "0.1.0" serde = "1.0.10" From 91ad2848b6924aaeb8bf40a75b21d2ef8dbddcfc Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Mon, 23 Jul 2018 15:53:04 +0300 Subject: [PATCH 46/51] Error on tag or anchor before alias --- src/parser.rs | 10 ++++++++++ src/test_transcode.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/parser.rs b/src/parser.rs index 904b01c..3fd2745 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -677,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/test_transcode.rs b/src/test_transcode.rs index 8947e85..8fbfbe6 100644 --- a/src/test_transcode.rs +++ b/src/test_transcode.rs @@ -369,6 +369,18 @@ 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"]"#); From a6dc2f6e79ccf32cfbdf385d9dd3881657e426ad Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Mon, 23 Jul 2018 12:57:52 +0000 Subject: [PATCH 47/51] Version bumped to v0.4.1 --- Cargo.toml | 2 +- doc/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e95114..7b92f07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" keywords = ["config", "yaml", "parser"] homepage = "http://github.com/tailhook/rust-quire" categories = ["config"] -version = "0.4.0" +version = "0.4.1" authors = ["paul@colomiets.name"] [dependencies] diff --git a/doc/conf.py b/doc/conf.py index ce2426d..2b28917 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -51,7 +51,7 @@ # The short X.Y version. version = '0.4' # The full version, including alpha/beta/rc tags. -release = '0.4.0' +release = '0.4.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 47f14899bb8e421cd608f76546ade95e6e5cc171 Mon Sep 17 00:00:00 2001 From: Geert Stappers Date: Sun, 21 Jul 2019 15:27:39 +0200 Subject: [PATCH 48/51] Document there is more documentation README.md has a pointer or doc/ doc/README.md, new file, tells it sphinx-doc. --- README.md | 1 + doc/README.md | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 doc/README.md diff --git a/README.md b/README.md index e706db6..0910898 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ 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. [1]: http://github.com/tailhook/quire [2]: http://yaml.org 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/ From 7167bfe4dbd84413f506daffbcdcdfb103d59955 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Sun, 21 Jul 2019 18:54:34 +0200 Subject: [PATCH 49/51] Bumped the year in sphinx-doc --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 2b28917..98d8303 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -42,7 +42,7 @@ # 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 From f7b046d49138b1a4f74608b4605e72517b0c1b2c Mon Sep 17 00:00:00 2001 From: Geert Stappers Date: Mon, 22 Jul 2019 17:40:12 +0200 Subject: [PATCH 50/51] Working example for lib.rs There is now a `main()` which makes it a working program. Function `work(cfg)` uses the configuration from the YAML file. Allow that not all fields of the configuration structure are used. Fixed a typo ( `macor` versus `macro` ) --- src/lib.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d7bcabc..5fa9035 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,26 +4,35 @@ //! //! ```rust,ignore # for some reason this crashes compiler //! extern crate quire; -//! #[macor_use] extern crate serde_derive; +//! #[macro_use] extern crate serde_derive; //! use quire::{parse_config, Options}; //! use quire::validate::{Structure, Scalar}; //! //! #[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_debug_implementations)] From a5f81793f047aee171bee2c9a88a9a9d2a33e147 Mon Sep 17 00:00:00 2001 From: Geert Stappers Date: Tue, 6 Aug 2019 20:59:03 +0200 Subject: [PATCH 51/51] Example with item lists new file: examples/item_lists.rs gets compiled new file: examples/journey.yaml gets read Item lists gets printed with {:?} debug formatting. Iteration over a list of items not yet possible. Contains TODO marker to validate an enumeration. --- examples/item_lists.rs | 92 ++++++++++++++++++++++++++++++++++++++++++ examples/journey.yaml | 23 +++++++++++ 2 files changed, 115 insertions(+) create mode 100644 examples/item_lists.rs create mode 100644 examples/journey.yaml 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