From f586b52675cf5d13a337a8220d562a152ef0df41 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 1 Sep 2020 22:47:15 +0200 Subject: [PATCH] Better introduction examples. --- README.md | 226 +++++++++++++------------------------------------ src/lib.rs | 243 +++++++++++++++-------------------------------------- 2 files changed, 125 insertions(+), 344 deletions(-) diff --git a/README.md b/README.md index 86405f0..2441848 100644 --- a/README.md +++ b/README.md @@ -11,208 +11,96 @@ `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your binaries, you still have the error backtrace. -`chainerror` has no dependencies! - -`chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. -It encapsulates all types, which have `Display + Debug` and can store the error cause internally. - -Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. - -Debug information is worth it! - -### Features +Having nested function returning errors, the output doesn't tell where the error originates from. -`display-cause` -: turn on printing a backtrace of the errors in `Display` - -## Tutorial +```rust +use std::path::PathBuf; -Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) +type BoxedError = Box; +fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = std::fs::read_to_string(&path)?; + // do stuff, return other errors + Ok(()) +} -## Examples +fn process_config_file() -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = read_config_file("foo.txt".into())?; + // do stuff, return other errors + Ok(()) +} -examples/example.rs: -```rust -// […] fn main() { - if let Err(e) = func1() { - eprintln!("\nDebug Error {{:?}}:\n{:?}", e); - eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); - // […] - } + if let Err(e) = process_config_file() { + eprintln!("Error:\n{:?}", e); + } } ``` +This gives the output: ```console -$ cargo run -q --example example -Debug Error {:?}: -examples/example.rs:46:13: func1 error calling func2 -Caused by: -examples/example.rs:21:13: Func2Error(func2 error: calling func3) -Caused by: -examples/example.rs:14:18: Error reading 'foo.txt' -Caused by: -Kind(NotFound) - -Alternative Debug Error {:#?}: -ChainError { - occurrence: Some( - "examples/example.rs:46:13", - ), - kind: func1 error calling func2, - source: Some( - ChainError { - occurrence: Some( - "examples/example.rs:21:13", - ), - kind: Func2Error(func2 error: calling func3), - source: Some( - ChainError { - occurrence: Some( - "examples/example.rs:14:18", - ), - kind: "Error reading \'foo.txt\'", - source: Some( - Kind( - NotFound, - ), - ), - }, - ), - }, - ), -} +Error: +Os { code: 2, kind: NotFound, message: "No such file or directory" } ``` +and you have no idea where it comes from. + + +With `chainerror`, you can supply a context and get a nice error backtrace: ```rust use chainerror::prelude::v1::*; -use std::error::Error; -use std::io; -use std::result::Result; +use std::path::PathBuf; -fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; +type BoxedError = Box; +fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; + // do stuff, return other errors Ok(()) } -fn func2() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().context(format!("Error reading '{}'", filename))?; +fn process_config_file() -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = read_config_file("foo.txt".into()).context("read the config file")?; + // do stuff, return other errors Ok(()) } -fn func1() -> Result<(), Box> { - func2().context("func1 error")?; - Ok(()) +fn main() { + if let Err(e) = process_config_file() { + eprintln!("Error:\n{:?}", e); + } } +``` -if let Err(e) = func1() { - #[cfg(not(windows))] - assert_eq!( - format!("\n{:?}\n", e), - r#" -src/lib.rs:21:13: func1 error +with the output: +```console +Error: +examples/simple.rs:14:51: read the config file Caused by: -src/lib.rs:16:18: Error reading 'foo.txt' +examples/simple.rs:7:47: Reading file: "foo.txt" Caused by: -Kind(NotFound) -"# - ); -} +Os { code: 2, kind: NotFound, message: "No such file or directory" } ``` +`chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. +It encapsulates all types, which have `Display + Debug` and can store the error cause internally. -```rust -use chainerror::prelude::v1::*; -use std::error::Error; -use std::io; -use std::result::Result; - -fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) -} - -fn func3() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().context(format!("Error reading '{}'", filename))?; - Ok(()) -} - -derive_str_context!(Func2Error); - -fn func2() -> ChainResult<(), Func2Error> { - func3().context(Func2Error("func2 error: calling func3".into()))?; - Ok(()) -} - -enum Func1Error { - Func2, - IO(String), -} - -impl ::std::fmt::Display for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match self { - Func1Error::Func2 => write!(f, "func1 error calling func2"), - Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), - } - } -} +Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. -impl ::std::fmt::Debug for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self) - } -} +`chainerror` has no dependencies! -fn func1() -> ChainResult<(), Func1Error> { - func2().context(Func1Error::Func2)?; - let filename = String::from("bar.txt"); - do_some_io().context(Func1Error::IO(filename))?; - Ok(()) -} +Debug information is worth it! -if let Err(e) = func1() { - assert!(match e.kind() { - Func1Error::Func2 => { - eprintln!("Main Error Report: func1 error calling func2"); - true - } - Func1Error::IO(filename) => { - eprintln!("Main Error Report: func1 error reading '{}'", filename); - false - } - }); - - assert!(e.find_chain_cause::().is_some()); - - if let Some(e) = e.find_chain_cause::() { - eprintln!("\nError reported by Func2Error: {}", e) - } +### Features - assert!(e.root_cause().is_some()); +`display-cause` +: turn on printing a backtrace of the errors in `Display` - if let Some(e) = e.root_cause() { - let io_error = e.downcast_ref::().unwrap(); - eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); - } +## Tutorial - #[cfg(not(windows))] - assert_eq!( - format!("\n{:?}\n", e), - r#" -src/lib.rs:48:13: func1 error calling func2 -Caused by: -src/lib.rs:23:13: Func2Error(func2 error: calling func3) -Caused by: -src/lib.rs:16:18: Error reading 'foo.txt' -Caused by: -Kind(NotFound) -"# - ); -} -``` +Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) ## License diff --git a/src/lib.rs b/src/lib.rs index bd4edd6..48a0f3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,217 +1,110 @@ //! `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your //! binaries, you still have the error backtrace. //! -//! `chainerror` has no dependencies! -//! -//! `chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. -//! It encapsulates all types, which have `Display + Debug` and can store the error cause internally. -//! -//! Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. -//! -//! Debug information is worth it! -//! -//! ## Features -//! -//! `display-cause` -//! : turn on printing a backtrace of the errors in `Display` -//! -//! # Tutorial -//! -//! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) -//! -//! # Examples -//! -//! examples/example.rs: -//! ```rust,ignore -//! // […] -//! fn main() { -//! if let Err(e) = func1() { -//! eprintln!("\nDebug Error {{:?}}:\n{:?}", e); -//! eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); -//! // […] -//! } -//! } -//! ``` -//! -//! ```console -//! $ cargo run -q --example example -//! Debug Error {:?}: -//! examples/example.rs:46:13: func1 error calling func2 -//! Caused by: -//! examples/example.rs:21:13: Func2Error(func2 error: calling func3) -//! Caused by: -//! examples/example.rs:14:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! -//! Alternative Debug Error {:#?}: -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:46:13", -//! ), -//! kind: func1 error calling func2, -//! source: Some( -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:21:13", -//! ), -//! kind: Func2Error(func2 error: calling func3), -//! source: Some( -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:14:18", -//! ), -//! kind: "Error reading \'foo.txt\'", -//! source: Some( -//! Kind( -//! NotFound, -//! ), -//! ), -//! }, -//! ), -//! }, -//! ), -//! } -//! ``` +//! Having nested function returning errors, the output doesn't tell where the error originates from. //! //! ```rust -//! use chainerror::prelude::v1::*; -//! use std::error::Error; -//! use std::io; -//! use std::result::Result; +//! use std::path::PathBuf; //! -//! fn do_some_io() -> Result<(), Box> { -//! Err(io::Error::from(io::ErrorKind::NotFound))?; +//! type BoxedError = Box; +//! fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { +//! // do stuff, return other errors +//! let _buf = std::fs::read_to_string(&path)?; +//! // do stuff, return other errors //! Ok(()) //! } //! -//! fn func2() -> Result<(), Box> { -//! let filename = "foo.txt"; -//! do_some_io().context(format!("Error reading '{}'", filename))?; +//! fn process_config_file() -> Result<(), BoxedError> { +//! // do stuff, return other errors +//! let _buf = read_config_file("foo.txt".into())?; +//! // do stuff, return other errors //! Ok(()) //! } //! -//! fn func1() -> Result<(), Box> { -//! func2().context("func1 error")?; -//! Ok(()) +//! fn main() { +//! if let Err(e) = process_config_file() { +//! eprintln!("Error:\n{:?}", e); +//! } //! } +//! ``` //! -//! if let Err(e) = func1() { -//! #[cfg(not(windows))] -//! assert_eq!( -//! format!("\n{:?}\n", e), -//! r#" -//! src/lib.rs:21:13: func1 error -//! Caused by: -//! src/lib.rs:16:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! "# -//! ); -//! } -//! # else { -//! # unreachable!(); -//! # } +//! This gives the output: +//! ```console +//! Error: +//! Os { code: 2, kind: NotFound, message: "No such file or directory" } //! ``` +//! and you have no idea where it comes from. +//! //! +//! With `chainerror`, you can supply a context and get a nice error backtrace: //! //! ```rust //! use chainerror::prelude::v1::*; -//! use std::error::Error; -//! use std::io; -//! use std::result::Result; -//! -//! fn do_some_io() -> Result<(), Box> { -//! Err(io::Error::from(io::ErrorKind::NotFound))?; -//! Ok(()) -//! } +//! use std::path::PathBuf; //! -//! fn func3() -> Result<(), Box> { -//! let filename = "foo.txt"; -//! do_some_io().context(format!("Error reading '{}'", filename))?; +//! type BoxedError = Box; +//! fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { +//! // do stuff, return other errors +//! let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; +//! // do stuff, return other errors //! Ok(()) //! } //! -//! derive_str_context!(Func2Error); -//! -//! fn func2() -> ChainResult<(), Func2Error> { -//! func3().context(Func2Error("func2 error: calling func3".into()))?; +//! fn process_config_file() -> Result<(), BoxedError> { +//! // do stuff, return other errors +//! let _buf = read_config_file("foo.txt".into()).context("read the config file")?; +//! // do stuff, return other errors //! Ok(()) //! } //! -//! enum Func1Error { -//! Func2, -//! IO(String), -//! } -//! -//! impl ::std::fmt::Display for Func1Error { -//! fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -//! match self { -//! Func1Error::Func2 => write!(f, "func1 error calling func2"), -//! Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), -//! } +//! fn main() { +//! if let Err(e) = process_config_file() { +//! eprintln!("Error:\n{:?}", e); +//! # assert_eq!( +//! # format!("{:?}\n", e), +//! # "\ +//! # src/lib.rs:16:51: read the config file\n\ +//! # Caused by:\n\ +//! # src/lib.rs:9:47: Reading file: \"foo.txt\"\n\ +//! # Caused by:\n\ +//! # Os { code: 2, kind: NotFound, message: \"No such file or directory\" }\n\ +//! # ", +//! # ); //! } //! } +//! ``` //! -//! impl ::std::fmt::Debug for Func1Error { -//! fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -//! write!(f, "{}", self) -//! } -//! } +//! with the output: +//! ```console +//! Error: +//! examples/simple.rs:14:51: read the config file +//! Caused by: +//! examples/simple.rs:7:47: Reading file: "foo.txt" +//! Caused by: +//! Os { code: 2, kind: NotFound, message: "No such file or directory" } +//! ``` //! -//! fn func1() -> ChainResult<(), Func1Error> { -//! func2().context(Func1Error::Func2)?; -//! let filename = String::from("bar.txt"); -//! do_some_io().context(Func1Error::IO(filename))?; -//! Ok(()) -//! } +//! `chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. +//! It encapsulates all types, which have `Display + Debug` and can store the error cause internally. //! -//! if let Err(e) = func1() { -//! assert!(match e.kind() { -//! Func1Error::Func2 => { -//! eprintln!("Main Error Report: func1 error calling func2"); -//! true -//! } -//! Func1Error::IO(filename) => { -//! eprintln!("Main Error Report: func1 error reading '{}'", filename); -//! false -//! } -//! }); +//! Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. //! -//! assert!(e.find_chain_cause::().is_some()); +//! `chainerror` has no dependencies! //! -//! if let Some(e) = e.find_chain_cause::() { -//! eprintln!("\nError reported by Func2Error: {}", e) -//! } +//! Debug information is worth it! //! -//! assert!(e.root_cause().is_some()); +//! ## Features //! -//! if let Some(e) = e.root_cause() { -//! let io_error = e.downcast_ref::().unwrap(); -//! eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); -//! } +//! `display-cause` +//! : turn on printing a backtrace of the errors in `Display` //! -//! #[cfg(not(windows))] -//! assert_eq!( -//! format!("\n{:?}\n", e), -//! r#" -//! src/lib.rs:48:13: func1 error calling func2 -//! Caused by: -//! src/lib.rs:23:13: Func2Error(func2 error: calling func3) -//! Caused by: -//! src/lib.rs:16:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! "# -//! ); -//! } -//! # else { -//! # unreachable!(); -//! # } -//! ``` +//! # Tutorial +//! +//! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) #![deny(clippy::all)] #![deny(clippy::integer_arithmetic)] +#![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId;