|
1 |
| -//! Error-handling support implemented using the `[error-chain][]` crate. |
| 1 | +//! Error-handling support. This emulates libraries like `error-chain` (very |
| 2 | +//! deprecated), `failure` (somewhat deprecated) and `snafu` (currently |
| 3 | +//! recommended, but I'm starting to see a pattern here). |
2 | 4 | //!
|
3 |
| -//! [error-chain]: https://docs.rs/error-chain |
4 |
| -
|
5 |
| -use csv; |
6 |
| -use std::io; |
7 |
| - |
8 |
| -// Declare nicer `Error` and `Result` types. This is a macro that |
9 |
| -// generates a lot of boilerplate code for us. |
10 |
| -error_chain! { |
11 |
| - // Error types from other libraries that we want to just wrap |
12 |
| - // automatically. |
13 |
| - foreign_links { |
14 |
| - Csv(csv::Error); |
15 |
| - Io(io::Error); |
| 5 | +//! I just wanted to see how hard it was to roll an nice error API from scratch |
| 6 | +//! instead of depending on an unstable third-party library. |
| 7 | +
|
| 8 | +use std::{error, fmt, result}; |
| 9 | + |
| 10 | +/// Our error type. We used a boxed dynamic error because we don't care much |
| 11 | +/// about the details and we're only going to print it for the user anyways. |
| 12 | +pub type Error = Box<dyn error::Error + 'static>; |
| 13 | + |
| 14 | +/// Our custom `Result` type. Defaults the `E` parameter to our error type. |
| 15 | +pub type Result<T, E = Error> = result::Result<T, E>; |
| 16 | + |
| 17 | +/// Human-readable context for another error. |
| 18 | +#[derive(Debug)] |
| 19 | +pub struct Context { |
| 20 | + context: String, |
| 21 | + source: Error, |
| 22 | +} |
| 23 | + |
| 24 | +impl fmt::Display for Context { |
| 25 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 26 | + self.context.fmt(f) |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +impl error::Error for Context { |
| 31 | + fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
| 32 | + Some(self.source.as_ref()) |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +/// Extend `Result` with methods that add context to errors. |
| 37 | +pub trait ResultExt<T, E>: Sized { |
| 38 | + /// If this result is an error, wrap that error with `context`. |
| 39 | + fn context<C>(self, context: C) -> Result<T> |
| 40 | + where |
| 41 | + C: Into<String>, |
| 42 | + { |
| 43 | + self.with_context(|_| context.into()) |
16 | 44 | }
|
17 | 45 |
|
18 |
| - errors { |
19 |
| - CannotParseCharacter(specifier: String) { |
20 |
| - description("cannot parse character specifier") |
21 |
| - display("cannot parse character specifier: '{}'", specifier) |
22 |
| - } |
| 46 | + /// If this result is an error, call `build_context` and wrap the error in |
| 47 | + /// that context. |
| 48 | + fn with_context<C, F>(self, build_context: F) -> Result<T> |
| 49 | + where |
| 50 | + C: Into<String>, |
| 51 | + F: FnOnce(&E) -> C; |
| 52 | +} |
| 53 | + |
| 54 | +impl<T, E: error::Error + 'static> ResultExt<T, E> for Result<T, E> { |
| 55 | + fn with_context<C, F>(self, build_context: F) -> Result<T> |
| 56 | + where |
| 57 | + C: Into<String>, |
| 58 | + F: FnOnce(&E) -> C, |
| 59 | + { |
| 60 | + self.map_err(|err| { |
| 61 | + Box::new(Context { |
| 62 | + context: build_context(&err).into(), |
| 63 | + source: Box::new(err), |
| 64 | + }) as Error |
| 65 | + }) |
23 | 66 | }
|
24 | 67 | }
|
| 68 | + |
| 69 | +/// Format a string and return it as an `Error`. We use a macro to do this, |
| 70 | +/// because that's the only way to declare `format!`-like syntax in Rust. |
| 71 | +#[macro_export] |
| 72 | +macro_rules! format_err { |
| 73 | + ($format_str:literal) => ({ |
| 74 | + let err: $crate::errors::Error = format!($format_str).into(); |
| 75 | + err |
| 76 | + }); |
| 77 | + ($format_str:literal, $($arg:expr),*) => ({ |
| 78 | + let err: $crate::errors::Error = format!($format_str, $($arg),*).into(); |
| 79 | + err |
| 80 | + }); |
| 81 | +} |
0 commit comments