Skip to content
This repository was archived by the owner on Aug 16, 2021. It is now read-only.

Commit 4e94ee1

Browse files
little-dudemitsuhiko
authored andcommitted
add Context::map (#260)
1 parent 47c4ce5 commit 4e94ee1

File tree

5 files changed

+271
-1
lines changed

5 files changed

+271
-1
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
- [A Custom Fail type](./custom-fail.md)
1212
- [Using the Error type](./use-error.md)
1313
- [An Error and ErrorKind pair](./error-errorkind.md)
14+
- [Strings and custom fail type](./string-custom-error.md)

book/src/string-custom-error.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Strings and custom fail type
2+
3+
This pattern is an hybrid between the [_An Error and ErrorKind pair_](./error-errorkind.md) and
4+
[_Using the Error type_](./use-error.md).
5+
6+
Such an error type can be implemented in the same way that what was shown in
7+
the [_An Error and ErrorKind pair_](./error-errorkind.md) pattern, but here, the context is a
8+
simple string:
9+
10+
```rust
11+
extern crate core;
12+
extern crate failure;
13+
14+
use core::fmt::{self, Display};
15+
use failure::{Backtrace, Context, Fail, ResultExt};
16+
17+
#[derive(Debug)]
18+
pub struct MyError {
19+
inner: Context<String>,
20+
}
21+
22+
impl Fail for MyError {
23+
fn cause(&self) -> Option<&Fail> {
24+
self.inner.cause()
25+
}
26+
27+
fn backtrace(&self) -> Option<&Backtrace> {
28+
self.inner.backtrace()
29+
}
30+
}
31+
32+
impl Display for MyError {
33+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34+
Display::fmt(&self.inner, f)
35+
}
36+
}
37+
```
38+
39+
To make the type easier to use, a few impls can be added:
40+
41+
```rust
42+
// Allows writing `MyError::from("oops"))?`
43+
impl From<&'static str> for MyError {
44+
fn from(msg: &'static str) -> MyError {
45+
MyError {
46+
inner: Context::new(msg),
47+
}
48+
}
49+
}
50+
51+
// Allows adding more context via a String
52+
impl From<Context<String>> for MyError {
53+
fn from(inner: Context<String>) -> MyError {
54+
MyError { inner }
55+
}
56+
}
57+
58+
// Allows adding more context via a &str
59+
impl From<Context<&'static str>> for MyError {
60+
fn from(inner: Context<&'static str>) -> MyError {
61+
MyError {
62+
inner: inner.map(|s| s.to_string()),
63+
}
64+
}
65+
}
66+
```
67+
68+
Here is how it is used:
69+
70+
```rust
71+
fn main() {
72+
println!("{:?}", err2());
73+
}
74+
75+
// Unlike the "Using the Error type" pattern, functions return our own error
76+
// type here.
77+
fn err1() -> Result<(), MyError> {
78+
Ok(Err(MyError::from("err1"))?)
79+
}
80+
81+
fn err2() -> Result<(), MyError> {
82+
// Unlike the "An Error and ErrorKind pair" pattern, our context is a
83+
// simple string. We can chain errors and provide detailed error messages,
84+
// but we don't have to deal with the complexity of an error kind type
85+
Ok(err1().context("err2")?)
86+
}
87+
```
88+
89+
## Variant with `&'static str`
90+
91+
If you don't need to format strings, you can avoid an
92+
allocation by using a `Context<&'static str>` instead of a
93+
`Context<String>`.
94+
95+
```rust
96+
extern crate core;
97+
extern crate failure;
98+
99+
use core::fmt::{self, Display};
100+
use failure::{Backtrace, Context, Fail, ResultExt};
101+
102+
#[derive(Debug)]
103+
pub struct MyError {
104+
inner: Context<&'static str>,
105+
}
106+
107+
impl Fail for MyError {
108+
fn cause(&self) -> Option<&Fail> {
109+
self.inner.cause()
110+
}
111+
112+
fn backtrace(&self) -> Option<&Backtrace> {
113+
self.inner.backtrace()
114+
}
115+
}
116+
117+
impl Display for MyError {
118+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119+
Display::fmt(&self.inner, f)
120+
}
121+
}
122+
123+
impl From<&'static str> for MyError {
124+
fn from(msg: &'static str) -> MyError {
125+
MyError {
126+
inner: Context::new(msg.into()),
127+
}
128+
}
129+
}
130+
131+
impl From<Context<&'static str>> for MyError {
132+
fn from(inner: Context<&'static str>) -> MyError {
133+
MyError {
134+
inner,
135+
}
136+
}
137+
}
138+
```
139+
140+
## When might you use this pattern?
141+
142+
Sometimes, you don't want to use the [_Using the Error type_](./use-error.md)
143+
pattern, because you want to expose a few different error types. But you don't
144+
want to use the [_An Error and ErrorKind pair_](./error-errorkind.md) pattern
145+
either, because there is no need to provide the context as an enum or because
146+
it would be too much work, if the error can occur in many different contexts.
147+
148+
For instance, if you're writing a library that decodes/encodes a complex binary
149+
format, you might want to expose a `DecodeError` and an `EncodeError` error
150+
type, but provide the context as a simple string instead of an error kind, because:
151+
152+
- users may not care too much about the context in which a `DecodeError` or
153+
`EncodeError` was encountered, they just want a nice message to explain it
154+
- your binary format is really complex, errors can occur in many different
155+
places, and you don't want to end up with a giant `ErrorKind` enum
156+
157+
158+
## Caveats on this pattern
159+
160+
If using the `Context<String>` variant, an extra allocation is used for the string.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//! This example show the pattern "Strings and custom fail type" described in the book
2+
extern crate core;
3+
extern crate failure;
4+
5+
use core::fmt::{self, Display};
6+
use failure::{Backtrace, Context, Fail, ResultExt};
7+
8+
fn main() {
9+
let err = err1().unwrap_err();
10+
// Print the error itself
11+
println!("error: {}", err);
12+
// Print the chain of errors that caused it
13+
for cause in Fail::iter_causes(&err) {
14+
println!("caused by: {}", cause);
15+
}
16+
}
17+
18+
fn err1() -> Result<(), MyError> {
19+
// The context can be a String
20+
Ok(err2().context("err1".to_string())?)
21+
}
22+
23+
fn err2() -> Result<(), MyError> {
24+
// The context can be a &'static str
25+
Ok(err3().context("err2")?)
26+
}
27+
28+
fn err3() -> Result<(), MyError> {
29+
Ok(Err(MyError::from("err3"))?)
30+
}
31+
32+
#[derive(Debug)]
33+
pub struct MyError {
34+
inner: Context<String>,
35+
}
36+
37+
impl Fail for MyError {
38+
fn cause(&self) -> Option<&Fail> {
39+
self.inner.cause()
40+
}
41+
42+
fn backtrace(&self) -> Option<&Backtrace> {
43+
self.inner.backtrace()
44+
}
45+
}
46+
47+
impl Display for MyError {
48+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49+
Display::fmt(&self.inner, f)
50+
}
51+
}
52+
53+
// Allows writing `MyError::from("oops"))?`
54+
impl From<&'static str> for MyError {
55+
fn from(msg: &'static str) -> MyError {
56+
MyError {
57+
inner: Context::new(msg.into()),
58+
}
59+
}
60+
}
61+
62+
// Allows adding more context via a String
63+
impl From<Context<String>> for MyError {
64+
fn from(inner: Context<String>) -> MyError {
65+
MyError { inner }
66+
}
67+
}
68+
69+
// Allows adding more context via a &str
70+
impl From<Context<&'static str>> for MyError {
71+
fn from(inner: Context<&'static str>) -> MyError {
72+
MyError {
73+
inner: inner.map(|s| s.to_string()),
74+
}
75+
}
76+
}

src/as_fail.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use Fail;
77
/// implementation. It is used in `failure_derive` in order to generate a
88
/// custom cause.
99
pub trait AsFail {
10-
/// Converts a referece to Self into a dynamic trait object of `Fail`.
10+
/// Converts a reference to `Self` into a dynamic trait object of `Fail`.
1111
fn as_fail(&self) -> &Fail;
1212
}
1313

src/context.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ without_std! {
2626
&self.context
2727
}
2828

29+
/// Maps `Context<D>` to `Context<T>` by applying a function to the contained context.
30+
pub fn map<F, T>(self, op: F) -> Context<T>
31+
where F: FnOnce(D) -> T,
32+
T: Display + Send + Sync + 'static
33+
{
34+
Context {
35+
context: op(self.context),
36+
}
37+
}
38+
2939
pub(crate) fn with_err<E: Fail>(context: D, _: E) -> Context<D> {
3040
Context { context }
3141
}
@@ -44,6 +54,12 @@ without_std! {
4454
write!(f, "{}", self.context)
4555
}
4656
}
57+
58+
#[test]
59+
fn test_map() {
60+
let ctx = Context::new("a string").map(|s| format!("{} with some more stuff", s));
61+
assert_eq!(ctx.context, String::from("a string with some more stuff"));
62+
}
4763
}
4864

4965
with_std! {
@@ -74,6 +90,17 @@ with_std! {
7490
&self.context
7591
}
7692

93+
/// Maps `Context<D>` to `Context<T>` by applying a function to the contained context.
94+
pub fn map<F, T>(self, op: F) -> Context<T>
95+
where F: FnOnce(D) -> T,
96+
T: Display + Send + Sync + 'static
97+
{
98+
Context {
99+
context: op(self.context),
100+
failure: self.failure,
101+
}
102+
}
103+
77104
pub(crate) fn with_err<E: Into<Error>>(context: D, error: E) -> Context<D> {
78105
let failure = Either::That(error.into());
79106
Context { context, failure }
@@ -131,6 +158,12 @@ with_std! {
131158
}
132159
}
133160
}
161+
162+
#[test]
163+
fn test_map() {
164+
let ctx = Context::new("a string").map(|s| format!("{} with some more stuff", s));
165+
assert_eq!(ctx.context, String::from("a string with some more stuff"));
166+
}
134167
}
135168

136169
impl<D> From<D> for Context<D>

0 commit comments

Comments
 (0)