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 bcc6e38..fd6d286 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(err: T) + -> Error + { + ErrorEnum::CustomError(None, Box::new(err)).into() + } + + pub fn custom_at(pos: &Pos, err: T) + -> Error + { + ErrorEnum::CustomError( + Some(ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset)), + Box::new(err)).into() + } + + pub fn downcast_ref(&self) -> Option<&T> { + match self.0 { + ErrorEnum::OpenError(_, ref e) => { + (e as &StdError).downcast_ref::() + }, + ErrorEnum::CustomError(_, ref e) => e.downcast_ref::(), + _ => None, + } + } } /// 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..6af11a3 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_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), + } + } }