|
197 | 197 | //! }
|
198 | 198 | //! ```
|
199 | 199 | //!
|
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 | +//! |
204 | 257 | //!
|
205 | 258 |
|
206 | 259 |
|
@@ -516,6 +569,11 @@ macro_rules! quick_error {
|
516 | 569 | $name $item: $imode [$( $var:$typ ),*]
|
517 | 570 | {$( $funcs )*});
|
518 | 571 | )*
|
| 572 | + $( |
| 573 | + quick_error!(FIND_CONTEXT_IMPL |
| 574 | + $name $item: $imode [$( $var:$typ ),*] |
| 575 | + {$( $funcs )*}); |
| 576 | + )* |
519 | 577 | };
|
520 | 578 | (FIND_DISPLAY_IMPL $name:ident $item:ident: $imode:tt
|
521 | 579 | { display($self_:tt) -> ($( $exprs:tt )*) $( $tail:tt )*}
|
@@ -586,6 +644,7 @@ macro_rules! quick_error {
|
586 | 644 | ) => {
|
587 | 645 | None
|
588 | 646 | };
|
| 647 | + // ----------------------------- FROM IMPL -------------------------- |
589 | 648 | (FIND_FROM_IMPL $name:ident $item:ident: $imode:tt
|
590 | 649 | [$( $var:ident: $typ:ty ),*]
|
591 | 650 | { from() $( $tail:tt )*}
|
@@ -656,6 +715,94 @@ macro_rules! quick_error {
|
656 | 715 | { }
|
657 | 716 | ) => {
|
658 | 717 | };
|
| 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 -------------------------- |
659 | 806 | (ITEM_BODY $(#[$imeta:meta])* $item:ident: UNIT
|
660 | 807 | ) => { };
|
661 | 808 | (ITEM_BODY $(#[$imeta:meta])* $item:ident: TUPLE
|
@@ -704,18 +851,44 @@ macro_rules! quick_error {
|
704 | 851 | => { quick_error!(ERROR_CHECK TUPLE $($tail)*); };
|
705 | 852 | (ERROR_CHECK STRUCT from($fvar:ident: $ftyp:ty) -> {$( $v:ident: $e:expr ),*} $( $tail:tt )*)
|
706 | 853 | => { 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 | + |
707 | 862 | (ERROR_CHECK $imode:tt ) => {};
|
708 | 863 | // Utility functions
|
709 | 864 | (IDENT $ident:ident) => { $ident }
|
710 | 865 | }
|
711 | 866 |
|
712 | 867 |
|
| 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 | + |
713 | 883 | #[cfg(test)]
|
714 | 884 | mod test {
|
715 |
| - use std::num::ParseFloatError; |
| 885 | + use std::num::{ParseFloatError, ParseIntError}; |
716 | 886 | use std::str::Utf8Error;
|
717 | 887 | use std::string::FromUtf8Error;
|
718 | 888 | use std::error::Error;
|
| 889 | + use std::path::{Path, PathBuf}; |
| 890 | + |
| 891 | + use super::ResultExt; |
719 | 892 |
|
720 | 893 | quick_error! {
|
721 | 894 | #[derive(Debug)]
|
@@ -884,4 +1057,74 @@ mod test {
|
884 | 1057 | assert_eq!(err.description(), descr);
|
885 | 1058 | assert!(err.cause().is_none());
|
886 | 1059 | }
|
| 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 | + } |
887 | 1130 | }
|
0 commit comments