typederror/
lib.rs

1//! A wrapper around `anyhow` but with a "primary" error type.
2//!
3//! ## Motivation
4//!
5//! This library aims to be the glue between `anyhow` and `thiserror`.
6//! It allows you to define a primary error type for variants that the caller
7//! should match on, while still capturing any other errors that may have
8//! occurred along the way.
9//!
10//! ### Documenting the error type of a function
11//!
12//! If you simply return an `anyhow::Error`, the caller has no idea what
13//! kind of error to expect. They would need to read your code to determine
14//! what the possible error types are.
15//!
16//! By using `TError`, you can specify the primary error type that the caller
17//! should match on. This has the effect of documenting the primary error type
18//! for your function.
19//!
20//! ```ignore
21//! fn my_fallible_function() -> typederror::Result<(), MyError> {
22//!     // Do something that might fail.
23//!     let s = std::fs::read_to_string("file.txt").map_err(|e| MyError::IoError(e))?;
24//!     // NOTE: if `MyError` implements `From<std::io::Error>`,
25//!     // you can do `std::fs::read_to_string("file.txt").terror()?` instead.
26//!     some_operation(s)?; // An error we don't need to match on.
27//!     Ok(())
28//! }
29//! ```
30//!
31//! The primary error type could be an enum that derives
32//! `thiserror::Error`, where only the meaningful errors are captured by
33//! the enum and any other errors are captured by the `anyhow::Error`
34//! underneath.
35//!
36//! You can also implement `DefaultError` so that all other errors are
37//! captured in a special "catch-all" variant of the primary error type.
38//!
39//! ```ignore
40//! #[derive(Debug, thiserror::Error)]
41//! enum MyError {
42//!    #[error("IO error: {0}")]
43//!    IoError(#[from] std::io::Error),
44//!    #[error("{0}")]
45//!    Misc(typederror::anyhow::Error)
46//! }
47//!
48//! impl DefaultError for MyError {
49//!     fn from_anyhow(err: typederror::anyhow::Error) -> Self {
50//!         Self::Misc(err)
51//!     }
52//! }
53//! ```
54//!
55//! ### Downcasting to the primary error type
56//!
57//! Since `TError` already knows the primary error type, it can provide
58//! convenience methods for downcasting to that type. This allows you to
59//! more easily work with errors of a single type without needing to match
60//! on several different error types.
61//!
62//! ```ignore
63//! if let Err(err) = my_fallible_function() { // returns Result<T, TError<MyError>>
64//!     match err.get() {
65//!         MyError::IoError(e) => { // e is of type `std::io::Error`
66//!             // Handle the error.
67//!         }
68//!         MyError::Misc(e) => { // e is of type `anyhow::Error`
69//!             // Handle the error.
70//!         }
71//!     }
72//! }
73//! ```
74//!
75//! You can also downcast to other types if needed, the same as you
76//! would with `anyhow`.
77//!
78//! ```ignore
79//! match err.downcast_ref::<serde::Error>() {
80//!     Ok(e) => {
81//!         // Handle serde error.
82//!     }
83//!     Err(e) => {
84//!         // Handle other error.
85//!     }
86//! }
87//! ```
88//!
89//! ### Start simple and add error variants later
90//!
91//! To get you started, you can use `TError<()>` as the primary error type.
92//! Or use `typederror::Result<T>` as the return type of your function.
93//! This will effectively work the same as `anyhow`, allowing you to
94//! write your code and worry about error types later.
95//!
96//! ```ignore
97//! fn do_something() -> typederror::Result<()> {
98//!     // Do something.
99//!     my_fallible_function()?;
100//!     Ok(())
101//! }
102//! ```
103//!
104//! Later, when you want to create specific variants for your function
105//! for easier matching by the caller, you can create an enum,
106//! derive `thiserror::Error`, and use that as the primary error type instead.
107//! You will need to add any necessary conversions, but you only need to add
108//! the variants you want to match on.
109//!
110//! All other errors will still be captured as per `anyhow` behaviour, or
111//! they can be captured in a special "catch-all" variant of your enum by
112//! implementing the `DefaultError` trait on the enum.
113//!
114//! ## Caveats
115//!
116//! Unfortunately the `?` operator cannot automatically convert error types
117//! to your primary error type.
118//!
119//! For example:
120//! ```ignore
121//! #[derive(Debug, thiserror::Error)]
122//! enum MyError {
123//!     #[error("IO error: {0}")]
124//!     IoError(#[from] std::io::Error),
125//!     #[error("{0}")]
126//!     Misc(anyhow::Error)
127//! }
128//!
129//! impl DefaultError for MyError {
130//!     fn from_anyhow(err: anyhow::Error) -> Self {
131//!         Self::Misc(err)
132//!     }
133//! }
134//!
135//! fn my_fallible_function() -> typederror::Result<(), MyError> {
136//!    let s = std::fs::read_to_string("file.txt")?;
137//!    // Do something else with s.
138//!    Ok(())
139//! }
140//!
141//! fn main() {
142//!     if let Err(e) = my_fallible_function() {
143//!         match e.get() {
144//!             // ...
145//!         }
146//!     }
147//! }
148//! ```
149//!
150//! In the above example, the `?` operator will not automatically convert the
151//! `std::io::Error` to `MyError::IoError`, as it would if you had used
152//! `MyError` as the error type directly. The error would instead match as
153//! `MyError::Misc` in the call to `e.get()`.
154//!
155//! To capture the `IoError` correctly, change the first line of the function to
156//! ```ignore
157//! let s = std::fs::read_to_string("file.txt").terror()?;
158//! ```
159//!
160mod error;
161pub use error::*;
162pub mod macros;
163
164pub mod prelude {
165    pub use crate::error::{Context, DefaultError, IntoTError, TError, WrapTError};
166    pub use crate::terror;
167    pub use crate::Result as TEResult;
168}
169
170/// Re-export of anyhow macros.
171pub mod anyhow {
172    pub use anyhow::{anyhow, bail};
173}