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}