diff --git a/Cargo.toml b/Cargo.toml index 2a04957..5d7c2c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ serde = "1.0.55" [dev-dependencies] bstr = { version = "1.7.0", default-features = false, features = ["alloc", "serde"] } +eyre = "~0.6.12" serde = { version = "1.0.55", features = ["derive"] } [profile.release] diff --git a/examples/tutorial-error-05.rs b/examples/tutorial-error-05.rs new file mode 100644 index 0000000..4edc2f5 --- /dev/null +++ b/examples/tutorial-error-05.rs @@ -0,0 +1,18 @@ +use eyre::Result; +use std::{io, process}; + +fn main() { + if let Err(err) = run() { + println!("{:?}", err); + process::exit(1); + } +} + +fn run() -> Result<()> { + let mut rdr = csv::Reader::from_reader(io::stdin()); + for result in rdr.records() { + let record = result?; + println!("{:?}", record); + } + Ok(()) +} diff --git a/examples/tutorial-perf-alloc-01.rs b/examples/tutorial-perf-alloc-01.rs index 4099534..307ae24 100644 --- a/examples/tutorial-perf-alloc-01.rs +++ b/examples/tutorial-perf-alloc-01.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut count = 0; @@ -19,7 +20,7 @@ fn main() { println!("{}", count); } Err(err) => { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-perf-alloc-02.rs b/examples/tutorial-perf-alloc-02.rs index fc7a8e5..cd343ec 100644 --- a/examples/tutorial-perf-alloc-02.rs +++ b/examples/tutorial-perf-alloc-02.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut count = 0; @@ -19,7 +20,7 @@ fn main() { println!("{}", count); } Err(err) => { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-perf-alloc-03.rs b/examples/tutorial-perf-alloc-03.rs index 4bbf167..6b5f66a 100644 --- a/examples/tutorial-perf-alloc-03.rs +++ b/examples/tutorial-perf-alloc-03.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut record = csv::ByteRecord::new(); @@ -19,7 +20,7 @@ fn main() { println!("{}", count); } Err(err) => { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-perf-core-01.rs b/examples/tutorial-perf-core-01.rs index 220918c..2fefe7b 100644 --- a/examples/tutorial-perf-core-01.rs +++ b/examples/tutorial-perf-core-01.rs @@ -1,3 +1,4 @@ +use eyre::Report; use std::io::{self, Read}; use std::process; @@ -63,7 +64,7 @@ fn main() { // Read the entire contents of stdin up front. let mut data = vec![]; if let Err(err) = io::stdin().read_to_end(&mut data) { - println!("{}", err); + println!("{:?}", Report::from(err)); process::exit(1); } match run(&data) { diff --git a/examples/tutorial-perf-serde-01.rs b/examples/tutorial-perf-serde-01.rs index 6578152..2b18c11 100644 --- a/examples/tutorial-perf-serde-01.rs +++ b/examples/tutorial-perf-serde-01.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; use serde::Deserialize; @@ -15,7 +16,7 @@ struct Record { longitude: f64, } -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut count = 0; @@ -34,7 +35,7 @@ fn main() { println!("{}", count); } Err(err) => { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-perf-serde-02.rs b/examples/tutorial-perf-serde-02.rs index b510ee9..229691d 100644 --- a/examples/tutorial-perf-serde-02.rs +++ b/examples/tutorial-perf-serde-02.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] +use eyre::Result; use serde::Deserialize; -use std::{error::Error, io, process}; +use std::{io, process}; #[derive(Debug, Deserialize)] #[serde(rename_all = "PascalCase")] @@ -14,7 +15,7 @@ struct Record<'a> { longitude: f64, } -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut raw_record = csv::StringRecord::new(); let headers = rdr.headers()?.clone(); @@ -35,7 +36,7 @@ fn main() { println!("{}", count); } Err(err) => { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-perf-serde-03.rs b/examples/tutorial-perf-serde-03.rs index 1b9b5fa..c99f20b 100644 --- a/examples/tutorial-perf-serde-03.rs +++ b/examples/tutorial-perf-serde-03.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; use serde::Deserialize; @@ -15,7 +16,7 @@ struct Record<'a> { longitude: f64, } -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut raw_record = csv::ByteRecord::new(); let headers = rdr.byte_headers()?.clone(); @@ -36,7 +37,7 @@ fn main() { println!("{}", count); } Err(err) => { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-pipeline-pop-01.rs b/examples/tutorial-pipeline-pop-01.rs index 19ad986..c90f733 100644 --- a/examples/tutorial-pipeline-pop-01.rs +++ b/examples/tutorial-pipeline-pop-01.rs @@ -1,4 +1,5 @@ -use std::{env, error::Error, io, process}; +use eyre::{eyre, Result}; +use std::{env, io, process}; use serde::{Deserialize, Serialize}; @@ -14,11 +15,11 @@ struct Record { longitude: f64, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { // Get the query from the positional arguments. // If one doesn't exist or isn't an integer, return an error. let minimum_pop: u64 = match env::args().nth(1) { - None => return Err(From::from("expected 1 argument, but got none")), + None => return Err(eyre!("expected 1 argument, but got none")), Some(arg) => arg.parse()?, }; @@ -53,7 +54,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-pipeline-search-01.rs b/examples/tutorial-pipeline-search-01.rs index d2cf5c1..59c4ac9 100644 --- a/examples/tutorial-pipeline-search-01.rs +++ b/examples/tutorial-pipeline-search-01.rs @@ -1,10 +1,11 @@ -use std::{env, error::Error, io, process}; +use eyre::{eyre, Result}; +use std::{env, io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { // Get the query from the positional arguments. // If one doesn't exist, return an error. let query = match env::args().nth(1) { - None => return Err(From::from("expected 1 argument, but got none")), + None => return Err(eyre!("expected 1 argument, but got none")), Some(query) => query, }; @@ -31,7 +32,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-pipeline-search-02.rs b/examples/tutorial-pipeline-search-02.rs index 76c1045..a1659d4 100644 --- a/examples/tutorial-pipeline-search-02.rs +++ b/examples/tutorial-pipeline-search-02.rs @@ -1,8 +1,9 @@ -use std::{env, error::Error, io, process}; +use eyre::{eyre, Result}; +use std::{env, io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let query = match env::args().nth(1) { - None => return Err(From::from("expected 1 argument, but got none")), + None => return Err(eyre!("expected 1 argument, but got none")), Some(query) => query, }; @@ -26,7 +27,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-01.rs b/examples/tutorial-read-01.rs index 6d75f79..77d63b0 100644 --- a/examples/tutorial-read-01.rs +++ b/examples/tutorial-read-01.rs @@ -1,6 +1,7 @@ -use std::{env, error::Error, ffi::OsString, fs::File, process}; +use eyre::{eyre, Result}; +use std::{env, ffi::OsString, fs::File, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let file_path = get_first_arg()?; let file = File::open(file_path)?; let mut rdr = csv::Reader::from_reader(file); @@ -13,16 +14,16 @@ fn run() -> Result<(), Box> { /// Returns the first positional argument sent to this process. If there are no /// positional arguments, then this returns an error. -fn get_first_arg() -> Result> { +fn get_first_arg() -> Result { match env::args_os().nth(1) { - None => Err(From::from("expected 1 argument, but got none")), + None => Err(eyre!("expected 1 argument, but got none")), Some(file_path) => Ok(file_path), } } fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-delimiter-01.rs b/examples/tutorial-read-delimiter-01.rs index 8a5536b..078f782 100644 --- a/examples/tutorial-read-delimiter-01.rs +++ b/examples/tutorial-read-delimiter-01.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::ReaderBuilder::new() .has_headers(false) .delimiter(b';') @@ -18,7 +19,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-headers-01.rs b/examples/tutorial-read-headers-01.rs index afd27da..61a3bb6 100644 --- a/examples/tutorial-read-headers-01.rs +++ b/examples/tutorial-read-headers-01.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::ReaderBuilder::new().has_headers(false).from_reader(io::stdin()); for result in rdr.records() { @@ -12,7 +13,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-headers-02.rs b/examples/tutorial-read-headers-02.rs index 951473a..f3f9dfa 100644 --- a/examples/tutorial-read-headers-02.rs +++ b/examples/tutorial-read-headers-02.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); { // We nest this call in its own scope because of lifetimes. @@ -20,7 +21,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-serde-01.rs b/examples/tutorial-read-serde-01.rs index afc23f1..9df292a 100644 --- a/examples/tutorial-read-serde-01.rs +++ b/examples/tutorial-read-serde-01.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.records() { let record = result?; @@ -27,7 +28,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-serde-02.rs b/examples/tutorial-read-serde-02.rs index 2cdcd91..1c93fce 100644 --- a/examples/tutorial-read-serde-02.rs +++ b/examples/tutorial-read-serde-02.rs @@ -1,10 +1,11 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; // This introduces a type alias so that we can conveniently reference our // record type. type Record = (String, String, Option, f64, f64); -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); // Instead of creating an iterator with the `records` method, we create // an iterator with the `deserialize` method. @@ -18,7 +19,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-serde-03.rs b/examples/tutorial-read-serde-03.rs index 022e246..1840d7e 100644 --- a/examples/tutorial-read-serde-03.rs +++ b/examples/tutorial-read-serde-03.rs @@ -1,11 +1,12 @@ +use eyre::Result; use std::collections::HashMap; -use std::{error::Error, io, process}; +use std::{io, process}; // This introduces a type alias so that we can conveniently reference our // record type. type Record = HashMap; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { let record: Record = result?; @@ -16,7 +17,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-serde-04.rs b/examples/tutorial-read-serde-04.rs index 796040c..1fac5b2 100644 --- a/examples/tutorial-read-serde-04.rs +++ b/examples/tutorial-read-serde-04.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; // This lets us write `#[derive(Deserialize)]`. use serde::Deserialize; @@ -19,7 +20,7 @@ struct Record { state: String, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { let record: Record = result?; @@ -32,7 +33,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-serde-invalid-01.rs b/examples/tutorial-read-serde-invalid-01.rs index 3ea836d..e2c4069 100644 --- a/examples/tutorial-read-serde-invalid-01.rs +++ b/examples/tutorial-read-serde-invalid-01.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; use serde::Deserialize; @@ -13,7 +14,7 @@ struct Record { state: String, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { let record: Record = result?; @@ -24,7 +25,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-read-serde-invalid-02.rs b/examples/tutorial-read-serde-invalid-02.rs index b4426cf..7752df7 100644 --- a/examples/tutorial-read-serde-invalid-02.rs +++ b/examples/tutorial-read-serde-invalid-02.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; use serde::Deserialize; #[derive(Debug, Deserialize)] @@ -13,7 +14,7 @@ struct Record { state: String, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { let record: Record = result?; @@ -24,7 +25,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-write-01.rs b/examples/tutorial-write-01.rs index e72c826..a0a594c 100644 --- a/examples/tutorial-write-01.rs +++ b/examples/tutorial-write-01.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); // Since we're writing records manually, we must explicitly write our // header record. A header record is written the same way that other @@ -30,7 +31,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-write-02.rs b/examples/tutorial-write-02.rs index 692a123..249b6d7 100644 --- a/examples/tutorial-write-02.rs +++ b/examples/tutorial-write-02.rs @@ -1,6 +1,7 @@ -use std::{env, error::Error, ffi::OsString, process}; +use eyre::{eyre, Result}; +use std::{env, ffi::OsString, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let file_path = get_first_arg()?; let mut wtr = csv::Writer::from_path(file_path)?; @@ -27,16 +28,16 @@ fn run() -> Result<(), Box> { /// Returns the first positional argument sent to this process. If there are no /// positional arguments, then this returns an error. -fn get_first_arg() -> Result> { +fn get_first_arg() -> Result { match env::args_os().nth(1) { - None => Err(From::from("expected 1 argument, but got none")), + None => Err(eyre!("expected 1 argument, but got none")), Some(file_path) => Ok(file_path), } } fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-write-delimiter-01.rs b/examples/tutorial-write-delimiter-01.rs index a8f3ceb..b437f15 100644 --- a/examples/tutorial-write-delimiter-01.rs +++ b/examples/tutorial-write-delimiter-01.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut wtr = csv::WriterBuilder::new() .delimiter(b'\t') .quote_style(csv::QuoteStyle::NonNumeric) @@ -29,7 +30,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-write-serde-01.rs b/examples/tutorial-write-serde-01.rs index c6c4a97..7bf09bd 100644 --- a/examples/tutorial-write-serde-01.rs +++ b/examples/tutorial-write-serde-01.rs @@ -1,6 +1,7 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); // We still need to write headers manually. @@ -34,7 +35,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/examples/tutorial-write-serde-02.rs b/examples/tutorial-write-serde-02.rs index 8d298fd..f9cb8b7 100644 --- a/examples/tutorial-write-serde-02.rs +++ b/examples/tutorial-write-serde-02.rs @@ -1,4 +1,5 @@ -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; use serde::Serialize; @@ -13,7 +14,7 @@ struct Record<'a> { longitude: f64, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); wtr.serialize(Record { @@ -44,7 +45,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } diff --git a/src/error.rs b/src/error.rs index 1dfe304..c530aa4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -133,24 +133,35 @@ impl From for io::Error { } } -impl StdError for Error {} +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self.kind() { + ErrorKind::Io(err) => err.source(), + ErrorKind::Utf8 { err, .. } => Some(err), + ErrorKind::UnequalLengths { .. } => None, + ErrorKind::Seek => None, + ErrorKind::Serialize(_) => None, + ErrorKind::Deserialize { err, .. } => Some(err), + _ => unreachable!(), + } + } +} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self.0 { ErrorKind::Io(ref err) => err.fmt(f), ErrorKind::Utf8 { pos: None, ref err } => { - write!(f, "CSV parse error: field {}: {}", err.field(), err) + write!(f, "CSV parse error: field {}.", err.field()) } ErrorKind::Utf8 { pos: Some(ref pos), ref err } => write!( f, "CSV parse error: record {} \ - (line {}, field: {}, byte: {}): {}", + (line {}, field: {}, byte: {}).", pos.record(), pos.line(), err.field(), pos.byte(), - err ), ErrorKind::UnequalLengths { pos: None, expected_len, len } => { write!( @@ -185,17 +196,16 @@ impl fmt::Display for Error { ErrorKind::Serialize(ref err) => { write!(f, "CSV write error: {}", err) } - ErrorKind::Deserialize { pos: None, ref err } => { - write!(f, "CSV deserialize error: {}", err) + ErrorKind::Deserialize { pos: None, .. } => { + write!(f, "CSV deserialize error") } - ErrorKind::Deserialize { pos: Some(ref pos), ref err } => write!( + ErrorKind::Deserialize { pos: Some(ref pos), .. } => write!( f, "CSV deserialize error: record {} \ - (line: {}, byte: {}): {}", + (line: {}, byte: {}).", pos.record(), pos.line(), pos.byte(), - err ), _ => unreachable!(), } diff --git a/src/tutorial.rs b/src/tutorial.rs index 8b96c09..531945f 100644 --- a/src/tutorial.rs +++ b/src/tutorial.rs @@ -310,7 +310,7 @@ foo,bar quux,baz,foobar $ ./target/debug/csvtutor < invalid StringRecord { position: Some(Position { byte: 16, line: 2, record: 1 }), fields: ["foo", "bar"] } -error reading CSV from : CSV error: record 2 (line: 3, byte: 24): found record with 3 fields, but the previous record has 2 fields +error reading CSV from : CSV error: record 2 (line: 3, byte: 24) ``` The second step for moving to recoverable errors is to put our CSV record loop @@ -352,7 +352,7 @@ error." A `Box` is hard to inspect if we cared about the specific err that occurred. But for our purposes, all we need to do is gracefully print an error message and exit the program. -The third and final step is to replace our explicit `match` expression with a +The third step is to replace our explicit `match` expression with a special Rust language feature: the question mark. ```no_run @@ -398,6 +398,35 @@ CSV transformations, then using methods like `expect` and panicking when an error occurs is a perfectly reasonable thing to do. Nevertheless, this tutorial will endeavor to show idiomatic code. +One final point that we'll address is pretty printing the errors. `eyre` is a +crate that allows us to produce a nicely formatted error report of the error, +we just need to wrap it in an `eyre::Report` before printing it and use the +[`Debug`] representation. Any kind of error can be converted to an +`eyre::Report`. `eyre` also provides a `Result` in which the error is an +`eyre::Report`, which makes using the `?` very convenient. + +```no_run +//tutorial-error-05.rs +use eyre::Result; +use std::{io, process}; + +fn main() { + if let Err(err) = run() { + println!("{:?}", err); + process::exit(1); + } +} + +fn run() -> Result<()> { + let mut rdr = csv::Reader::from_reader(io::stdin()); + for result in rdr.records() { + let record = result?; + println!("{:?}", record); + } + Ok(()) +} +``` + # Reading CSV Now that we've got you setup and covered basic error handling, it's time to do @@ -411,15 +440,15 @@ path argument instead of stdin. ```no_run //tutorial-read-01.rs +use eyre::{eyre, Result}; use std::{ env, - error::Error, ffi::OsString, fs::File, process, }; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let file_path = get_first_arg()?; let file = File::open(file_path)?; let mut rdr = csv::Reader::from_reader(file); @@ -432,16 +461,16 @@ fn run() -> Result<(), Box> { /// Returns the first positional argument sent to this process. If there are no /// positional arguments, then this returns an error. -fn get_first_arg() -> Result> { +fn get_first_arg() -> Result { match env::args_os().nth(1) { - None => Err(From::from("expected 1 argument, but got none")), + None => Err(eyre!("expected 1 argument, but got none")), Some(file_path) => Ok(file_path), } } fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -521,9 +550,10 @@ produces terser examples.) ```no_run //tutorial-read-headers-01.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::ReaderBuilder::new() .has_headers(false) .from_reader(io::stdin()); @@ -536,7 +566,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -560,9 +590,10 @@ method like so: ```no_run //tutorial-read-headers-02.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); { // We nest this call in its own scope because of lifetimes. @@ -582,7 +613,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -639,9 +670,10 @@ as seen in the following example: ```no_run //tutorial-read-delimiter-01.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::ReaderBuilder::new() .has_headers(false) .delimiter(b';') @@ -659,7 +691,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -725,9 +757,10 @@ a lot of manual work. This next example shows how. ```no_run //tutorial-read-serde-01.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.records() { let record = result?; @@ -753,7 +786,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -766,13 +799,14 @@ type: `(String, String, Option, f64, f64)`. ```no_run //tutorial-read-serde-02.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # // This introduces a type alias so that we can conveniently reference our // record type. type Record = (String, String, Option, f64, f64); -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); // Instead of creating an iterator with the `records` method, we create // an iterator with the `deserialize` method. @@ -786,7 +820,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -817,14 +851,15 @@ a new `use` statement that imports `HashMap` from the standard library: ```no_run //tutorial-read-serde-03.rs +use eyre::Result; use std::collections::HashMap; -# use std::{error::Error, io, process}; +# use std::{io, process}; // This introduces a type alias so that we can conveniently reference our // record type. type Record = HashMap; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { let record: Record = result?; @@ -835,7 +870,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -875,7 +910,8 @@ how. Don't miss the new Serde imports! ```no_run //tutorial-read-serde-04.rs # #![allow(dead_code)] -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; // This lets us write `#[derive(Deserialize)]`. use serde::Deserialize; @@ -895,7 +931,7 @@ struct Record { state: String, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { let record: Record = result?; @@ -908,7 +944,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -1012,7 +1048,8 @@ Let's start by running our program from the previous section: ```no_run //tutorial-read-serde-invalid-01.rs # #![allow(dead_code)] -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # # use serde::Deserialize; # @@ -1026,7 +1063,7 @@ struct Record { state: String, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { let record: Record = result?; @@ -1037,7 +1074,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -1080,7 +1117,8 @@ to a `None` value, as shown in this next example: ```no_run //tutorial-read-serde-invalid-02.rs # #![allow(dead_code)] -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # # use serde::Deserialize; #[derive(Debug, Deserialize)] @@ -1094,7 +1132,7 @@ struct Record { state: String, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { let record: Record = result?; @@ -1105,7 +1143,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -1146,9 +1184,10 @@ Let's start with the most basic example: writing a few CSV records to `stdout`. ```no_run //tutorial-write-01.rs -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); // Since we're writing records manually, we must explicitly write our // header record. A header record is written the same way that other @@ -1166,7 +1205,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -1253,14 +1292,14 @@ of `stdout`: ```no_run //tutorial-write-02.rs +use eyre::{eyre, Result}; use std::{ env, - error::Error, ffi::OsString, process, }; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let file_path = get_first_arg()?; let mut wtr = csv::Writer::from_path(file_path)?; @@ -1275,16 +1314,16 @@ fn run() -> Result<(), Box> { /// Returns the first positional argument sent to this process. If there are no /// positional arguments, then this returns an error. -fn get_first_arg() -> Result> { +fn get_first_arg() -> Result { match env::args_os().nth(1) { - None => Err(From::from("expected 1 argument, but got none")), + None => Err(eyre!("expected 1 argument, but got none")), Some(file_path) => Ok(file_path), } } fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -1316,9 +1355,10 @@ Here's an example: ```no_run //tutorial-write-delimiter-01.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut wtr = csv::WriterBuilder::new() .delimiter(b'\t') .quote_style(csv::QuoteStyle::NonNumeric) @@ -1335,7 +1375,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -1370,9 +1410,10 @@ As with reading, let's start by seeing how we can serialize a Rust tuple. ```no_run //tutorial-write-serde-01.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); // We still need to write headers manually. @@ -1394,7 +1435,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -1438,7 +1479,8 @@ shown in the example: ```no_run //tutorial-write-serde-02.rs -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; use serde::Serialize; @@ -1453,7 +1495,7 @@ struct Record<'a> { longitude: f64, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); wtr.serialize(Record { @@ -1484,7 +1526,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -1565,13 +1607,14 @@ rows with a field that matches the query. ```no_run //tutorial-pipeline-search-01.rs -use std::{env, error::Error, io, process}; +use eyre::{eyre, Result}; +use std::{env, io, process}; -fn run() -> Result<(), Box> { +fn run() -> Result<()> { // Get the query from the positional arguments. // If one doesn't exist, return an error. let query = match env::args().nth(1) { - None => return Err(From::from("expected 1 argument, but got none")), + None => return Err(eyre!("expected 1 argument, but got none")), Some(query) => query, }; @@ -1598,7 +1641,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -1682,11 +1725,12 @@ change: ```no_run //tutorial-pipeline-search-02.rs -# use std::{env, error::Error, io, process}; +# use eyre::{eyre, Result}; +# use std::{env, io, process}; # -fn run() -> Result<(), Box> { +fn run() -> Result<()> { let query = match env::args().nth(1) { - None => return Err(From::from("expected 1 argument, but got none")), + None => return Err(eyre!("expected 1 argument, but got none")), Some(query) => query, }; @@ -1710,7 +1754,7 @@ fn run() -> Result<(), Box> { # # fn main() { # if let Err(err) = run() { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -1751,7 +1795,8 @@ Now here's the code: ```no_run //tutorial-pipeline-pop-01.rs -# use std::{env, error::Error, io, process}; +# use eyre::{eyre, Result}; +# use std::{env, io, process}; use serde::{Deserialize, Serialize}; @@ -1767,11 +1812,11 @@ struct Record { longitude: f64, } -fn run() -> Result<(), Box> { +fn run() -> Result<()> { // Get the query from the positional arguments. // If one doesn't exist or isn't an integer, return an error. let minimum_pop: u64 = match env::args().nth(1) { - None => return Err(From::from("expected 1 argument, but got none")), + None => return Err(eyre!("expected 1 argument, but got none")), Some(arg) => arg.parse()?, }; @@ -1806,7 +1851,7 @@ fn run() -> Result<(), Box> { fn main() { if let Err(err) = run() { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -1881,9 +1926,10 @@ adapting a previous example to count the number of records in ```no_run //tutorial-perf-alloc-01.rs -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut count = 0; @@ -1902,7 +1948,7 @@ fn main() { println!("{}", count); } Err(err) => { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -1937,9 +1983,10 @@ shown in the next example: ```no_run //tutorial-perf-alloc-02.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut count = 0; @@ -1958,7 +2005,7 @@ fn run() -> Result> { # println!("{}", count); # } # Err(err) => { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -2018,9 +2065,10 @@ method. ```no_run //tutorial-perf-alloc-03.rs -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut record = csv::ByteRecord::new(); @@ -2039,7 +2087,7 @@ fn run() -> Result> { # println!("{}", count); # } # Err(err) => { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -2098,7 +2146,8 @@ example using Serde in a previous section: ```no_run //tutorial-perf-serde-01.rs # #![allow(dead_code)] -use std::{error::Error, io, process}; +use eyre::Result; +use std::{io, process}; use serde::Deserialize; @@ -2114,7 +2163,7 @@ struct Record { longitude: f64, } -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut count = 0; @@ -2133,7 +2182,7 @@ fn main() { println!("{}", count); } Err(err) => { - println!("{}", err); + println!("{:?}", err); process::exit(1); } } @@ -2166,7 +2215,8 @@ like: ```no_run //tutorial-perf-serde-02.rs # #![allow(dead_code)] -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # use serde::Deserialize; # #[derive(Debug, Deserialize)] @@ -2181,7 +2231,7 @@ struct Record<'a> { longitude: f64, } -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut raw_record = csv::StringRecord::new(); let headers = rdr.headers()?.clone(); @@ -2202,7 +2252,7 @@ fn run() -> Result> { # println!("{}", count); # } # Err(err) => { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -2251,7 +2301,8 @@ of `StringRecord`: ```no_run //tutorial-perf-serde-03.rs # #![allow(dead_code)] -# use std::{error::Error, io, process}; +# use eyre::Result; +# use std::{io, process}; # # use serde::Deserialize; # @@ -2267,7 +2318,7 @@ struct Record<'a> { longitude: f64, } -fn run() -> Result> { +fn run() -> Result { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut raw_record = csv::ByteRecord::new(); let headers = rdr.byte_headers()?.clone(); @@ -2288,7 +2339,7 @@ fn run() -> Result> { # println!("{}", count); # } # Err(err) => { -# println!("{}", err); +# println!("{:?}", err); # process::exit(1); # } # } @@ -2346,6 +2397,7 @@ access to I/O, which would be harder without the standard library.) ```no_run //tutorial-perf-core-01.rs +use eyre::Report; use std::io::{self, Read}; use std::process; @@ -2411,7 +2463,7 @@ fn main() { // Read the entire contents of stdin up front. let mut data = vec![]; if let Err(err) = io::stdin().read_to_end(&mut data) { - println!("{}", err); + println!("{:?}", Report::from(err)); process::exit(1); } match run(&data) { diff --git a/tests/tests.rs b/tests/tests.rs index ba73954..ac9fd71 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -146,6 +146,25 @@ quux,baz,foobar assert!(out.stdout_failed().contains("CSV error:")); } +#[test] +fn tutorial_error_05() { + let mut cmd = cmd_for_example("tutorial-error-05"); + let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); + assert_eq!(out.stdout().lines().count(), 100); +} + +#[test] +fn tutorial_error_05_errored() { + let data = "\ +header1,header2 +foo,bar +quux,baz,foobar +"; + let mut cmd = cmd_for_example("tutorial-error-05"); + let out = cmd_output_with(&mut cmd, data.as_bytes()); + assert!(out.stdout_failed().contains("CSV error:")); +} + #[test] fn tutorial_read_01() { let mut cmd = cmd_for_example("tutorial-read-01");