Skip to content

Commit 4c2e467

Browse files
committed
Merge pull request #24 from tailhook/context2
Add context to errors
2 parents cb7a954 + 62f0ea7 commit 4c2e467

File tree

2 files changed

+296
-5
lines changed

2 files changed

+296
-5
lines changed

examples/context.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#[macro_use(quick_error)] extern crate quick_error;
2+
3+
use std::io::{self, stderr, Read, Write};
4+
use std::fs::File;
5+
use std::env;
6+
use std::num::ParseIntError;
7+
use std::path::{Path, PathBuf};
8+
9+
use quick_error::ResultExt;
10+
11+
quick_error! {
12+
#[derive(Debug)]
13+
pub enum Error {
14+
NoFileName {
15+
description("no file name specified")
16+
}
17+
Io(err: io::Error, path: PathBuf) {
18+
display("could not read file {:?}: {}", path, err)
19+
context(path: &'a Path, err: io::Error)
20+
-> (err, path.to_path_buf())
21+
}
22+
Parse(err: ParseIntError, path: PathBuf) {
23+
display("could not parse file {:?}: {}", path, err)
24+
context(path: &'a Path, err: ParseIntError)
25+
-> (err, path.to_path_buf())
26+
}
27+
}
28+
}
29+
30+
fn parse_file() -> Result<u64, Error> {
31+
let fname = try!(env::args().skip(1).next().ok_or(Error::NoFileName));
32+
let fname = Path::new(&fname);
33+
let mut file = try!(File::open(fname).context(fname));
34+
let mut buf = String::new();
35+
try!(file.read_to_string(&mut buf).context(fname));
36+
Ok(try!(buf.parse().context(fname)))
37+
}
38+
39+
fn main() {
40+
match parse_file() {
41+
Ok(val) => {
42+
println!("Read: {}", val);
43+
}
44+
Err(e) => {
45+
writeln!(&mut stderr(), "Error: {}", e).ok();
46+
}
47+
}
48+
}

src/lib.rs

Lines changed: 248 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,63 @@
197197
//! }
198198
//! ```
199199
//!
200-
//! All forms of `from`, `display`, `description`, `cause` clauses can be
201-
//! combined and put in arbitrary order. Only `from` may be used multiple times
202-
//! in single variant of enumeration. Docstrings are also okay.
203-
//! Empty braces can be omitted as of quick_error 0.1.3.
200+
//! Since quick-error 1.1 we also have a `context` declaration, which is
201+
//! similar to (the longest form of) `from`, but allows adding some context to
202+
//! the error. We need a longer example to demonstrate this:
203+
//!
204+
//! ```rust
205+
//! # #[macro_use] extern crate quick_error;
206+
//! # use std::io;
207+
//! # use std::fs::File;
208+
//! # use std::path::{Path, PathBuf};
209+
//! #
210+
//! use quick_error::ResultExt;
211+
//!
212+
//! quick_error! {
213+
//! #[derive(Debug)]
214+
//! pub enum Error {
215+
//! File(filename: PathBuf, err: io::Error) {
216+
//! context(path: &'a Path, err: io::Error)
217+
//! -> (path.to_path_buf(), err)
218+
//! }
219+
//! }
220+
//! }
221+
//!
222+
//! fn openfile(path: &Path) -> Result<(), Error> {
223+
//! try!(File::open(path).context(path));
224+
//!
225+
//! // If we didn't have context, the line above would be written as;
226+
//! //
227+
//! // try!(File::open(path)
228+
//! // .map_err(|err| Error::File(path.to_path_buf(), err)));
229+
//!
230+
//! Ok(())
231+
//! }
232+
//!
233+
//! # fn main() {
234+
//! # openfile(Path::new("/etc/somefile")).ok();
235+
//! # }
236+
//! ```
237+
//!
238+
//! Each `context(a: A, b: B)` clause implements
239+
//! `From<Context<A, B>> for Error`. Which means multiple `context` clauses
240+
//! are a subject to the normal coherence rules. Unfortunately, we can't
241+
//! provide full support of generics for the context, but you may either use a
242+
//! lifetime `'a` for references or `AsRef<Type>` (the latter means `A:
243+
//! AsRef<Type>`, and `Type` must be concrete). It's also occasionally useful
244+
//! to use a tuple as a type of the first argument.
245+
//!
246+
//! You also need to `use quick_error::ResultExt` extension trait to get
247+
//! working `.context()` method.
248+
//!
249+
//! More info on context in [this article](http://bit.ly/1PsuxDt).
250+
//!
251+
//! All forms of `from`, `display`, `description`, `cause`, and `context`
252+
//! clauses can be combined and put in arbitrary order. Only `from` and
253+
//! `context` can be used multiple times in single variant of enumeration.
254+
//! Docstrings are also okay. Empty braces can be omitted as of quick_error
255+
//! 0.1.3.
256+
//!
204257
//!
205258
206259

@@ -516,6 +569,11 @@ macro_rules! quick_error {
516569
$name $item: $imode [$( $var:$typ ),*]
517570
{$( $funcs )*});
518571
)*
572+
$(
573+
quick_error!(FIND_CONTEXT_IMPL
574+
$name $item: $imode [$( $var:$typ ),*]
575+
{$( $funcs )*});
576+
)*
519577
};
520578
(FIND_DISPLAY_IMPL $name:ident $item:ident: $imode:tt
521579
{ display($self_:tt) -> ($( $exprs:tt )*) $( $tail:tt )*}
@@ -586,6 +644,7 @@ macro_rules! quick_error {
586644
) => {
587645
None
588646
};
647+
// ----------------------------- FROM IMPL --------------------------
589648
(FIND_FROM_IMPL $name:ident $item:ident: $imode:tt
590649
[$( $var:ident: $typ:ty ),*]
591650
{ from() $( $tail:tt )*}
@@ -656,6 +715,94 @@ macro_rules! quick_error {
656715
{ }
657716
) => {
658717
};
718+
// ----------------------------- CONTEXT IMPL --------------------------
719+
(FIND_CONTEXT_IMPL $name:ident $item:ident: TUPLE
720+
[$( $var:ident: $typ:ty ),*]
721+
{ context($cvar:ident: AsRef<$ctyp:ty>, $fvar:ident: $ftyp:ty)
722+
-> ($( $texpr:expr ),*) $( $tail:tt )* }
723+
) => {
724+
impl<T: AsRef<$ctyp>> From<$crate::Context<T, $ftyp>> for $name {
725+
fn from(
726+
$crate::Context($cvar, $fvar): $crate::Context<T, $ftyp>)
727+
-> $name
728+
{
729+
$name::$item($( $texpr ),*)
730+
}
731+
}
732+
quick_error!(FIND_CONTEXT_IMPL
733+
$name $item: TUPLE [$( $var:$typ ),*]
734+
{ $($tail)* });
735+
};
736+
(FIND_CONTEXT_IMPL $name:ident $item:ident: TUPLE
737+
[$( $var:ident: $typ:ty ),*]
738+
{ context($cvar:ident: $ctyp:ty, $fvar:ident: $ftyp:ty)
739+
-> ($( $texpr:expr ),*) $( $tail:tt )* }
740+
) => {
741+
impl<'a> From<$crate::Context<$ctyp, $ftyp>> for $name {
742+
fn from(
743+
$crate::Context($cvar, $fvar): $crate::Context<$ctyp, $ftyp>)
744+
-> $name
745+
{
746+
$name::$item($( $texpr ),*)
747+
}
748+
}
749+
quick_error!(FIND_CONTEXT_IMPL
750+
$name $item: TUPLE [$( $var:$typ ),*]
751+
{ $($tail)* });
752+
};
753+
(FIND_CONTEXT_IMPL $name:ident $item:ident: STRUCT
754+
[$( $var:ident: $typ:ty ),*]
755+
{ context($cvar:ident: AsRef<$ctyp:ty>, $fvar:ident: $ftyp:ty)
756+
-> {$( $tvar:ident: $texpr:expr ),*} $( $tail:tt )* }
757+
) => {
758+
impl<T: AsRef<$ctyp>> From<$crate::Context<T, $ftyp>> for $name {
759+
fn from(
760+
$crate::Context($cvar, $fvar): $crate::Context<$ctyp, $ftyp>)
761+
-> $name
762+
{
763+
$name::$item {
764+
$( $tvar: $texpr ),*
765+
}
766+
}
767+
}
768+
quick_error!(FIND_CONTEXT_IMPL
769+
$name $item: STRUCT [$( $var:$typ ),*]
770+
{ $($tail)* });
771+
};
772+
(FIND_CONTEXT_IMPL $name:ident $item:ident: STRUCT
773+
[$( $var:ident: $typ:ty ),*]
774+
{ context($cvar:ident: $ctyp:ty, $fvar:ident: $ftyp:ty)
775+
-> {$( $tvar:ident: $texpr:expr ),*} $( $tail:tt )* }
776+
) => {
777+
impl<'a> From<$crate::Context<$ctyp, $ftyp>> for $name {
778+
fn from(
779+
$crate::Context($cvar, $fvar): $crate::Context<$ctyp, $ftyp>)
780+
-> $name
781+
{
782+
$name::$item {
783+
$( $tvar: $texpr ),*
784+
}
785+
}
786+
}
787+
quick_error!(FIND_CONTEXT_IMPL
788+
$name $item: STRUCT [$( $var:$typ ),*]
789+
{ $($tail)* });
790+
};
791+
(FIND_CONTEXT_IMPL $name:ident $item:ident: $imode:tt
792+
[$( $var:ident: $typ:ty ),*]
793+
{ $t:tt $( $tail:tt )*}
794+
) => {
795+
quick_error!(FIND_CONTEXT_IMPL
796+
$name $item: $imode [$( $var:$typ ),*]
797+
{$( $tail )*}
798+
);
799+
};
800+
(FIND_CONTEXT_IMPL $name:ident $item:ident: $imode:tt
801+
[$( $var:ident: $typ:ty ),*]
802+
{ }
803+
) => {
804+
};
805+
// ----------------------------- ITEM IMPL --------------------------
659806
(ITEM_BODY $(#[$imeta:meta])* $item:ident: UNIT
660807
) => { };
661808
(ITEM_BODY $(#[$imeta:meta])* $item:ident: TUPLE
@@ -704,18 +851,44 @@ macro_rules! quick_error {
704851
=> { quick_error!(ERROR_CHECK TUPLE $($tail)*); };
705852
(ERROR_CHECK STRUCT from($fvar:ident: $ftyp:ty) -> {$( $v:ident: $e:expr ),*} $( $tail:tt )*)
706853
=> { quick_error!(ERROR_CHECK STRUCT $($tail)*); };
854+
855+
(ERROR_CHECK TUPLE context($cvar:ident: $ctyp:ty, $fvar:ident: $ftyp:ty)
856+
-> ($( $e:expr ),*) $( $tail:tt )*)
857+
=> { quick_error!(ERROR_CHECK TUPLE $($tail)*); };
858+
(ERROR_CHECK STRUCT context($cvar:ident: $ctyp:ty, $fvar:ident: $ftyp:ty)
859+
-> {$( $v:ident: $e:expr ),*} $( $tail:tt )*)
860+
=> { quick_error!(ERROR_CHECK STRUCT $($tail)*); };
861+
707862
(ERROR_CHECK $imode:tt ) => {};
708863
// Utility functions
709864
(IDENT $ident:ident) => { $ident }
710865
}
711866

712867

868+
#[derive(Debug)]
869+
pub struct Context<X, E>(pub X, pub E);
870+
871+
pub trait ResultExt<T, E> {
872+
fn context<X>(self, x: X) -> Result<T, Context<X, E>>;
873+
}
874+
875+
impl<T, E> ResultExt<T, E> for Result<T, E> {
876+
fn context<X>(self, x: X) -> Result<T, Context<X, E>> {
877+
self.map_err(|e| Context(x, e))
878+
}
879+
}
880+
881+
882+
713883
#[cfg(test)]
714884
mod test {
715-
use std::num::ParseFloatError;
885+
use std::num::{ParseFloatError, ParseIntError};
716886
use std::str::Utf8Error;
717887
use std::string::FromUtf8Error;
718888
use std::error::Error;
889+
use std::path::{Path, PathBuf};
890+
891+
use super::ResultExt;
719892

720893
quick_error! {
721894
#[derive(Debug)]
@@ -884,4 +1057,74 @@ mod test {
8841057
assert_eq!(err.description(), descr);
8851058
assert!(err.cause().is_none());
8861059
}
1060+
1061+
quick_error! {
1062+
#[derive(Debug)]
1063+
pub enum ContextErr {
1064+
Float(src: String, err: ParseFloatError) {
1065+
context(s: &'a str, e: ParseFloatError) -> (s.to_string(), e)
1066+
display("Float error {:?}: {}", src, err)
1067+
}
1068+
Int { src: String, err: ParseIntError } {
1069+
context(s: &'a str, e: ParseIntError)
1070+
-> {src: s.to_string(), err: e}
1071+
display("Int error {:?}: {}", src, err)
1072+
}
1073+
Utf8(path: PathBuf, err: Utf8Error) {
1074+
context(p: AsRef<Path>, e: Utf8Error)
1075+
-> (p.as_ref().to_path_buf(), e)
1076+
display("Path error at {:?}: {}", path, err)
1077+
}
1078+
Utf8Str(s: String, err: ::std::io::Error) {
1079+
context(s: AsRef<str>, e: ::std::io::Error)
1080+
-> (s.as_ref().to_string(), e)
1081+
display("Str error {:?}: {}", s, err)
1082+
}
1083+
}
1084+
}
1085+
1086+
#[test]
1087+
fn parse_float_error() {
1088+
fn parse_float(s: &str) -> Result<f32, ContextErr> {
1089+
Ok(try!(s.parse().context(s)))
1090+
}
1091+
assert_eq!(format!("{}", parse_float("12ab").unwrap_err()),
1092+
r#"Float error "12ab": invalid float literal"#);
1093+
}
1094+
1095+
#[test]
1096+
fn parse_int_error() {
1097+
fn parse_int(s: &str) -> Result<i32, ContextErr> {
1098+
Ok(try!(s.parse().context(s)))
1099+
}
1100+
assert_eq!(format!("{}", parse_int("12.5").unwrap_err()),
1101+
r#"Int error "12.5": invalid digit found in string"#);
1102+
}
1103+
1104+
#[test]
1105+
fn debug_context() {
1106+
fn parse_int(s: &str) -> i32 {
1107+
s.parse().context(s).unwrap()
1108+
}
1109+
assert_eq!(parse_int("12"), 12);
1110+
assert_eq!(format!("{:?}", "x".parse::<i32>().context("x")),
1111+
r#"Err(Context("x", ParseIntError { kind: InvalidDigit }))"#);
1112+
}
1113+
1114+
#[test]
1115+
fn path_context() {
1116+
fn parse_utf<P: AsRef<Path>>(s: &[u8], p: P)
1117+
-> Result<(), ContextErr>
1118+
{
1119+
try!(::std::str::from_utf8(s).context(p));
1120+
Ok(())
1121+
}
1122+
assert_eq!(format!("{}", parse_utf(b"a\x80\x80", "/etc").unwrap_err()),
1123+
"Path error at \"/etc\": invalid utf-8: \
1124+
invalid byte near index 1");
1125+
assert_eq!(format!("{}", parse_utf(b"\x80\x80",
1126+
PathBuf::from("/tmp")).unwrap_err()),
1127+
"Path error at \"/tmp\": invalid utf-8: \
1128+
invalid byte near index 0");
1129+
}
8871130
}

0 commit comments

Comments
 (0)