Skip to content

Commit 78bf7f2

Browse files
authored
Merge pull request #11 from anti-social/dev/custom-error
Custom errors for validators
2 parents 7d8c995 + 1240b17 commit 78bf7f2

File tree

3 files changed

+128
-19
lines changed

3 files changed

+128
-19
lines changed

src/de.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::slice;
77
use std::str::FromStr;
88

99

10-
use serde::de::{self, DeserializeSeed, Visitor, SeqAccess, Error as DeError};
10+
use serde::de::{self, DeserializeSeed, Visitor, SeqAccess};
1111
use serde::de::{MapAccess, EnumAccess, VariantAccess, IntoDeserializer};
1212

1313
use ast::{Ast, Ast as A, Tag};
@@ -70,7 +70,8 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer {
7070
"true" => true,
7171
"false" => false,
7272
_ => {
73-
return Err(Error::custom(format!("bad boolean {:?}", self.0)));
73+
let e: Error = de::Error::custom(format!("bad boolean {:?}", self.0));
74+
return Err(e);
7475
}
7576
};
7677
visitor.visit_bool(value)
@@ -140,10 +141,12 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer {
140141
where V: Visitor<'a>
141142
{
142143
let mut chars = self.0.chars();
143-
let val = chars.next()
144-
.ok_or_else(|| Error::custom("single character expected"))?;
144+
let val = (chars.next()
145+
.ok_or_else(|| {
146+
de::Error::custom("single character expected")
147+
}) as Result<_>)?;
145148
if chars.next().is_some() {
146-
return Err(Error::custom("single character expected"))
149+
return Err(de::Error::custom("single character expected"))
147150
}
148151
visitor.visit_char(val)
149152
}
@@ -216,7 +219,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer {
216219
fn deserialize_seq<V>(self, _visitor: V) -> Result<V::Value>
217220
where V: Visitor<'a>
218221
{
219-
Err(Error::custom("sequence can't be mapping key in quire"))
222+
Err(de::Error::custom("sequence can't be mapping key in quire"))
220223
}
221224

222225
fn deserialize_tuple<V>(
@@ -226,7 +229,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer {
226229
) -> Result<V::Value>
227230
where V: Visitor<'a>
228231
{
229-
Err(Error::custom("tuple can't be mapping key in quire"))
232+
Err(de::Error::custom("tuple can't be mapping key in quire"))
230233
}
231234

232235
// Tuple structs look just like sequences in JSON.
@@ -238,13 +241,13 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer {
238241
) -> Result<V::Value>
239242
where V: Visitor<'a>
240243
{
241-
Err(Error::custom("tuple struct can't be mapping key in quire"))
244+
Err(de::Error::custom("tuple struct can't be mapping key in quire"))
242245
}
243246

244247
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
245248
where V: Visitor<'a>
246249
{
247-
Err(Error::custom("mapping can't be mapping key in quire"))
250+
Err(de::Error::custom("mapping can't be mapping key in quire"))
248251
}
249252
fn deserialize_struct<V>(
250253
self,
@@ -254,7 +257,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer {
254257
) -> Result<V::Value>
255258
where V: Visitor<'a>
256259
{
257-
Err(Error::custom("struct can't be mapping key in quire"))
260+
Err(de::Error::custom("struct can't be mapping key in quire"))
258261
}
259262

260263
fn deserialize_enum<V>(
@@ -266,7 +269,7 @@ impl<'a> de::Deserializer<'a> for KeyDeserializer {
266269
where V: Visitor<'a>
267270
{
268271
// TODO(tailhook) some support might work
269-
Err(Error::custom("enum can't be mapping key in quire"))
272+
Err(de::Error::custom("enum can't be mapping key in quire"))
270273
}
271274

272275
fn deserialize_identifier<V>(

src/errors.rs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::io;
22
use std::fmt;
33
use std::rc::Rc;
4+
use std::error::Error as StdError;
45
use std::slice::Iter;
56
use std::path::{Path, PathBuf};
67
use std::cell::RefCell;
@@ -42,16 +43,26 @@ quick_error! {
4243
filename=pos.0, line=pos.1, offset=pos.2,
4344
path=path, text=msg)
4445
}
45-
Custom(message: String) {
46-
display("{}", message)
47-
description(message)
46+
SerdeError(msg: String) {
47+
display("{}", msg)
48+
}
49+
CustomError(pos: Option<ErrorPos>, err: Box<StdError>) {
50+
display(x) -> ("{loc}{err}",
51+
loc=if let &Some(ref p) = pos {
52+
format!("{filename}:{line}:{offset}: ",
53+
filename=p.0, line=p.1, offset=p.2)
54+
} else {
55+
"".to_string()
56+
},
57+
err=err)
58+
cause(&**err)
4859
}
4960
}
5061
}
5162

5263
impl ::serde::de::Error for Error {
53-
fn custom<T: ::std::fmt::Display>(msg: T) -> Self {
54-
ErrorEnum::Custom(format!("{}", msg)).into()
64+
fn custom<T: fmt::Display>(msg: T) -> Self {
65+
ErrorEnum::SerdeError(format!("{}", msg)).into()
5566
}
5667
}
5768

@@ -85,6 +96,30 @@ impl Error {
8596
ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset),
8697
message).into()
8798
}
99+
100+
pub fn custom<T: StdError + 'static>(err: T)
101+
-> Error
102+
{
103+
ErrorEnum::CustomError(None, Box::new(err)).into()
104+
}
105+
106+
pub fn custom_at<T: StdError + 'static>(pos: &Pos, err: T)
107+
-> Error
108+
{
109+
ErrorEnum::CustomError(
110+
Some(ErrorPos((*pos.filename).clone(), pos.line, pos.line_offset)),
111+
Box::new(err)).into()
112+
}
113+
114+
pub fn downcast_ref<T: StdError + 'static>(&self) -> Option<&T> {
115+
match self.0 {
116+
ErrorEnum::OpenError(_, ref e) => {
117+
(e as &StdError).downcast_ref::<T>()
118+
},
119+
ErrorEnum::CustomError(_, ref e) => e.downcast_ref::<T>(),
120+
_ => None,
121+
}
122+
}
88123
}
89124

90125
/// List of errors that were encountered during configuration file parsing
@@ -170,8 +205,8 @@ pub fn add_info<T>(pos: &Pos, path: &String, result: Result<T, Error>)
170205
-> Result<T, Error>
171206
{
172207
match result {
173-
Err(Error(ErrorEnum::Custom(e))) => {
174-
Err(Error::decode_error(pos, path, e))
208+
Err(Error(ErrorEnum::SerdeError(e))) => {
209+
Err(Error::decode_error(pos, path, format!("{}", e)))
175210
}
176211
result => result,
177212
}

src/validate.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,10 +713,13 @@ impl Validator for Nothing {
713713

714714
#[cfg(test)]
715715
mod test {
716+
use std::fmt;
716717
use std::rc::Rc;
717718
use std::path::PathBuf;
718719
use std::collections::BTreeMap;
719720
use std::collections::HashMap;
721+
use std::error::Error as StdError;
722+
720723
use serde::Deserialize;
721724

722725
use {Options};
@@ -728,8 +731,9 @@ mod test {
728731
use {parse_string, ErrorList};
729732
use validate::{Validator, Structure, Scalar, Numeric, Mapping, Sequence};
730733
use validate::{Enum, Nothing, Directory, Anything};
731-
use errors::ErrorCollector;
734+
use errors::{Error, ErrorCollector};
732735
use self::TestEnum::*;
736+
use super::Pos;
733737

734738
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
735739
struct TestStruct {
@@ -1453,4 +1457,71 @@ mod test {
14531457
fn test_enum_def_tag() {
14541458
assert_eq!(parse_enum_def("!Alpha"), TestEnumDef::Alpha);
14551459
}
1460+
1461+
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
1462+
struct Version;
1463+
1464+
#[derive(Debug)]
1465+
struct VersionError(&'static str);
1466+
1467+
impl StdError for VersionError {
1468+
fn description(&self) -> &str { "Version Error" }
1469+
fn cause(&self) -> Option<&StdError> { None }
1470+
}
1471+
1472+
impl fmt::Display for VersionError {
1473+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1474+
write!(f, "{}: {}", self.description(), self.0)
1475+
}
1476+
}
1477+
1478+
impl Version {
1479+
fn new() -> Version {
1480+
Version {}
1481+
}
1482+
}
1483+
1484+
impl Validator for Version {
1485+
fn default(&self, pos: Pos) -> Option<A> {
1486+
None
1487+
}
1488+
1489+
fn validate(&self, ast: A, err: &ErrorCollector) -> A {
1490+
match ast {
1491+
A::Scalar(pos, tag, kind, version) => {
1492+
if !version.starts_with("v") {
1493+
err.add_error(Error::custom_at(
1494+
&pos,
1495+
VersionError("Version must start with 'v'")))
1496+
}
1497+
A::Scalar(pos, tag, kind, version)
1498+
},
1499+
ast => {
1500+
err.add_error(Error::validation_error(
1501+
&ast.pos(), format!("Version must be a scalar value")));
1502+
ast
1503+
},
1504+
}
1505+
}
1506+
}
1507+
1508+
fn parse_version(body: &str) -> Result<Version, ErrorList> {
1509+
let validator = Version::new();
1510+
parse_string("<inline text>", body, &validator, &Options::default())
1511+
}
1512+
1513+
#[test]
1514+
fn test_custom_error() {
1515+
let err = parse_version("0.0.1").unwrap_err();
1516+
let error = err.errors().nth(0).unwrap();
1517+
assert_eq!(
1518+
format!("{}", error),
1519+
"<inline text>:1:1: Version Error: Version must start with 'v'");
1520+
match error.downcast_ref::<VersionError>() {
1521+
Some(&VersionError(msg)) => {
1522+
assert_eq!(msg, "Version must start with 'v'")
1523+
},
1524+
e => panic!("Custom error must be VersionError but was: {:?}", e),
1525+
}
1526+
}
14561527
}

0 commit comments

Comments
 (0)