rama_error/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! Error utilities for rama and its users.
//!
//! Crate used by the end-user `rama` crate and `rama` crate authors alike.
//!
//! Learn more about `rama`:
//!
//! - Github: <https://github.com/plabayo/rama>
//! - Book: <https://ramaproxy.org/book/>
//!
//! # Errors
//!
//! Errors in Rust are a bit ambiguous:
//!
//! - the infamous `Result<T, E>` is a type that can either be `Ok(T)` or `Err(E)`, where `E` is
//!   the error type in case something went wrong.
//! - the [`std::error::Error`] trait is a trait that represents errors that can be displayed and
//!   have a source (cause).
//!
//! The ambiguity comes from the fact that the [`std::error::Error`] trait is not required to be
//! implemented for the error type `E` in the `Result<T, E>` type. This means that one can have
//! a `Result<T, E>` where `E` is not an error type. A common example of something else it can be
//! is that it has the same type as the `T` type, which is not an error type. E.g. in case of a web
//! service middleware a firewall could return a 403 Http response as the `Err` variant of the
//! `Result<T, Response>`. Where `T` is most likely also a `Response` type. In which
//! case you might as well have `Result<Response, Infallible>`.
//!
//! Within Web Services we usually do not want an error type, as it does not make any sense.
//! This is because the server has to respond something (unless you simply want to kill the connection),
//! and so it makes much more sense to enforce the code type-wise to always return a response.
//!
//! The most tricky scenario, if you can call it that, is what to do for middleware services.
//! These situations are tricky because they can wrap any generic `S` type, where `S` is the
//! service type. This means that the error type can be anything, and so it is not possible to
//! create values of that type for scenarios where the error comes from the middleware itself.
//!
//! There are several possibilities here and we'll go over them next. But before we do that,
//! I do want to emphasise that while Rust's `Result<T, E>` does not enforce that `E` is an error
//! type, it is still a good practice to use an error type for the `E` type. And that is also
//! that as a rule of thumb we do in Rama.
//!
//! ## Type Erasure
//!
//! The [`BoxError`] type alias is a boxed Error trait object and can be used to represent any error that
//! implements the [`std::error::Error`] trait and is used for cases where it is usually not
//! that important what specific error type is returned, but rather that an error occurred.
//! Boxed errors do allow to _downcast_ to check for concrete error types, but this checks
//! only the top-level error and not the cause chain.
//!
//! ## Error Extension
//!
//! The [`ErrorExt`] trait provides a set of methods to work with errors. These methods are
//! implemented for all types that implement the [`std::error::Error`] trait. The methods are
//! used to add context to an error, add a backtrace to an error, and to convert an error into
//! an opaque error.
//!
//! ## Opaque Error
//!
//! The [`OpaqueError`] type is a type-erased error that can be used to represent any error
//! that implements the [`std::error::Error`] trait. Using the [`OpaqueError::from_display`]
//! you can even create errors from a displayable type.
//!
//! The other advantage of [`OpaqueError`] over [`BoxError`]
//! is that it is Sized and can be used in places where a `Sized`` type is required,
//! while [`BoxError`] is `?Sized` and can give you a hard time in certain scenarios.
//!
//! ## `error` macro
//!
//! The `error` macro is a convenient way to create an [`OpaqueError`]
//! from an error, format string or displayable type.
//!
//! ### `error` macro Example
//!
//! ```rust
//! use rama_error::{error, ErrorExt, OpaqueError};
//!
//! let error = error!("error").context("foo");
//! assert_eq!(error.to_string(), "foo\r\n ↪ error");
//!
//! let error = error!("error {}", 404).context("foo");
//! assert_eq!(error.to_string(), "foo\r\n ↪ error 404");
//!
//! #[derive(Debug)]
//! struct CustomError;
//!
//! impl std::fmt::Display for CustomError {
//!   fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
//!      write!(f, "entity not found")
//!   }
//! }
//!
//! impl std::error::Error for CustomError {}
//!
//! let error = error!(CustomError).context("foo");
//!
//! assert_eq!(error.to_string(), "foo\r\n ↪ entity not found");
//! ```
//!
//! ## Error Context
//!
//! The [`ErrorContext`] allows you to add a context to [`Result`]
//! and [`Option`] types:
//!
//! - For [`Result`] types, the context is added to the error variant,
//!   turning `Result<T, E>` into `Result<T, OpaqueError>`;
//! - For [`Option`] types, the context is used as a DisplayError when
//!   the open is `None`, turning `Option<T>` into `Result<T, OpaqueError>`.
//!
//! This is useful when you want to add custom context.
//! And can also be combined with other [`ErrorExt`] methods,
//! such as [`ErrorExt::backtrace`] to add even more info to the error case,
//! if there is one.
//!
//! It is also an easy way to turn an option value into the inner value,
//! short-circuiting using `?` with the new context (Display) error
//! when the option was `None`.
//!
//! ### Error Context Example
//!
//! Option Example:
//!
//! ```rust
//! use rama_error::{ErrorContext, ErrorExt};
//!
//! let value = Some(42);
//! let value = match value.context("value is None") {
//!    Ok(value) => assert_eq!(value, 42),
//!    Err(error) => panic!("unexpected error: {error}"),
//! };
//!
//! let value: Option<usize> = None;
//! let result = value.context("value is None");
//! assert!(result.is_err());
//! ```
//!
//! Result Example:
//!
//! ```rust
//! use rama_error::{ErrorContext, ErrorExt, OpaqueError};
//!
//! let value: Result<_, OpaqueError> = Ok(42);
//! let value = match value.context("get the answer") {
//!   Ok(value) => assert_eq!(value, 42),
//!   Err(error) => panic!("unexpected error: {error}"),
//! };
//!
//! let value: Result<usize, _> = Err(OpaqueError::from_display("error"));
//! let result = value.context("get the answer");
//! assert!(result.is_err());
//! ```
//!
//! ## Error Composition
//!
//! Sometimes it can be useful to compose errors with more
//! expressive error types. In such cases [`OpaqueError`] is... too opaque.
//!
//! In an early design of Rama we considered adding a `compose_error` function macro
//! that would allow to create error types in a similar manner as [the `thiserror` crate](https://docs.rs/thiserror),
//! but we decided against it as it would be an abstraction too much.
//!
//! Rama was created to give developers the full power of the Rust language to develop
//! proxies, and by extension also web services and http clients. In a similar line of thought
//! it is also important that one has all tools available to create the error types for their purpose.
//!
//! As such, if you want your own custom error types we recommend just creating them
//! as you would any other type in Rust. The blog article <https://sabrinajewson.org/blog/errors>
//! gives a good overview and background on this topic.
//!
//! You can declare your own `macro_rules` in case there are common patterns for the services
//! and middlewares that you are writing for your project. For inspiration you can
//! see the http rejection macros we borrowed and modified from
//! [Axum's extract logic](https://github.com/tokio-rs/axum/blob/5201798d4e4d4759c208ef83e30ce85820c07baa/axum-core/src/macros.rs):
//! <https://github.com/plabayo/rama/blob/main/rama-http/src/utils/macros/http_error.rs>
//!
//! And of course... if you really want, against our advice in,
//! you can use [the `thiserror` crate](https://docs.rs/thiserror),
//! or even [the `anyhow` crate](https://docs.rs/anyhow). All is possible.

#![doc(
    html_favicon_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/old_logo.png"
)]
#![doc(html_logo_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/old_logo.png")]
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![cfg_attr(test, allow(clippy::float_cmp))]
#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))]

use std::error::Error as StdError;

/// Alias for a type-erased error type.
///
/// See the [module level documentation](crate::error) for more information.
pub type BoxError = Box<dyn StdError + Send + Sync>;

mod ext;
pub use ext::{ErrorContext, ErrorExt, OpaqueError};

mod macros;
#[doc(inline)]
pub use macros::error;

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_macro_error_string() {
        let error = error!("error").context("foo");
        assert_eq!(error.to_string(), "foo\r\n ↪ error");
    }

    #[test]
    fn test_macro_error_format_string() {
        let error = error!("error {}", 404).context("foo");
        assert_eq!(error.to_string(), "foo\r\n ↪ error 404");
    }

    #[derive(Debug)]
    struct CustomError;

    impl std::fmt::Display for CustomError {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(f, "entity not found")
        }
    }

    impl std::error::Error for CustomError {}

    #[test]
    fn test_macro_error_from_error() {
        let error = error!(CustomError).context("foo");
        assert_eq!(error.to_string(), "foo\r\n ↪ entity not found");
    }
}