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 | 260 ++++++++++++++----------------------------------- src/lib.rs | 277 ++++++++++++++++------------------------------------- 2 files changed, 159 insertions(+), 378 deletions(-) diff --git a/README.md b/README.md index 86405f0..2441848 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,86 @@ `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! +Having nested function returning errors, the output doesn't tell where the error originates from. + +```rust +use std::path::PathBuf; + +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 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 main() { + if let Err(e) = process_config_file() { + eprintln!("Error:\n{:?}", e); + } +} +``` + +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::path::PathBuf; + +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 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 main() { + if let Err(e) = process_config_file() { + eprintln!("Error:\n{:?}", e); + } +} +``` + +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" } +``` `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. +`chainerror` has no dependencies! + Debug information is worth it! ### Features @@ -29,191 +102,6 @@ Debug information is worth it! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) -## Examples - -examples/example.rs: -```rust -// […] -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, - ), - ), - }, - ), - }, - ), -} -``` - -```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 func2() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().context(format!("Error reading '{}'", filename))?; - Ok(()) -} - -fn func1() -> Result<(), Box> { - func2().context("func1 error")?; - Ok(()) -} - -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) -"# - ); -} -``` - - -```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), - } - } -} - -impl ::std::fmt::Debug for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self) - } -} - -fn func1() -> ChainResult<(), Func1Error> { - func2().context(Func1Error::Func2)?; - let filename = String::from("bar.txt"); - do_some_io().context(Func1Error::IO(filename))?; - Ok(()) -} - -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) - } - - assert!(e.root_cause().is_some()); - - if let Some(e) = e.root_cause() { - let io_error = e.downcast_ref::().unwrap(); - eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); - } - - #[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) -"# - ); -} -``` - ## License Licensed under either of diff --git a/src/lib.rs b/src/lib.rs index bd4edd6..48a0f3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,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! +//! Having nested function returning errors, the output doesn't tell where the error originates from. +//! +//! ```rust +//! use std::path::PathBuf; +//! +//! 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 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 main() { +//! if let Err(e) = process_config_file() { +//! eprintln!("Error:\n{:?}", e); +//! } +//! } +//! ``` +//! +//! 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::path::PathBuf; +//! +//! 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 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 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\ +//! # ", +//! # ); +//! } +//! } +//! ``` +//! +//! 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" } +//! ``` //! //! `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. //! +//! `chainerror` has no dependencies! +//! //! Debug information is worth it! //! //! ## Features @@ -18,200 +101,10 @@ //! # 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, -//! ), -//! ), -//! }, -//! ), -//! }, -//! ), -//! } -//! ``` -//! -//! ```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 func2() -> Result<(), Box> { -//! let filename = "foo.txt"; -//! do_some_io().context(format!("Error reading '{}'", filename))?; -//! Ok(()) -//! } -//! -//! fn func1() -> Result<(), Box> { -//! func2().context("func1 error")?; -//! Ok(()) -//! } -//! -//! 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!(); -//! # } -//! ``` -//! -//! -//! ```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), -//! } -//! } -//! } -//! -//! impl ::std::fmt::Debug for Func1Error { -//! fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -//! write!(f, "{}", self) -//! } -//! } -//! -//! fn func1() -> ChainResult<(), Func1Error> { -//! func2().context(Func1Error::Func2)?; -//! let filename = String::from("bar.txt"); -//! do_some_io().context(Func1Error::IO(filename))?; -//! Ok(()) -//! } -//! -//! 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) -//! } -//! -//! assert!(e.root_cause().is_some()); -//! -//! if let Some(e) = e.root_cause() { -//! let io_error = e.downcast_ref::().unwrap(); -//! eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); -//! } -//! -//! #[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!(); -//! # } -//! ``` #![deny(clippy::all)] #![deny(clippy::integer_arithmetic)] +#![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId;