Skip to content

Commit 8ce3e78

Browse files
author
Pascal Hertleif
committed
verbose errors feature
This adds a new "verbose-errors" feature flag to async-std that enables wrapping certain errors in structures with more context. As an example, we use it in `fs::File::{open,create}` to add the given path to the error message (something that is lacking in std to annoyance of many).
1 parent 355e2ed commit 8ce3e78

File tree

7 files changed

+98
-2
lines changed

7 files changed

+98
-2
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ jobs:
6464
command: test
6565
args: --all --features unstable
6666

67+
- name: tests with verbose errors
68+
uses: actions-rs/cargo@v1
69+
with:
70+
command: test
71+
args: --all --features 'unstable verbose-errors'
72+
6773
check_fmt_and_docs:
6874
name: Checking fmt and docs
6975
runs-on: ubuntu-latest

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ std = [
4848
"pin-utils",
4949
"slab",
5050
]
51+
verbose-errors = []
5152

5253
[dependencies]
5354
async-attributes = { version = "1.1.1", optional = true }

src/fs/file.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::sync::{Arc, Mutex};
99

1010
use crate::fs::{Metadata, Permissions};
1111
use crate::future;
12+
use crate::utils::VerboseErrorExt;
1213
use crate::io::{self, Read, Seek, SeekFrom, Write};
1314
use crate::path::Path;
1415
use crate::prelude::*;
@@ -112,7 +113,11 @@ impl File {
112113
/// ```
113114
pub async fn open<P: AsRef<Path>>(path: P) -> io::Result<File> {
114115
let path = path.as_ref().to_owned();
115-
let file = spawn_blocking(move || std::fs::File::open(&path)).await?;
116+
let file = spawn_blocking(move || {
117+
std::fs::File::open(&path)
118+
.verbose_context(|| format!("Could not open {}", path.display()))
119+
})
120+
.await?;
116121
Ok(File::new(file, true))
117122
}
118123

@@ -147,7 +152,11 @@ impl File {
147152
/// ```
148153
pub async fn create<P: AsRef<Path>>(path: P) -> io::Result<File> {
149154
let path = path.as_ref().to_owned();
150-
let file = spawn_blocking(move || std::fs::File::create(&path)).await?;
155+
let file = spawn_blocking(move || {
156+
std::fs::File::create(&path)
157+
.verbose_context(|| format!("Could not create {}", path.display()))
158+
})
159+
.await?;
151160
Ok(File::new(file, true))
152161
}
153162

src/io/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ cfg_std! {
291291
pub(crate) mod read;
292292
pub(crate) mod seek;
293293
pub(crate) mod write;
294+
pub(crate) mod utils;
294295

295296
mod buf_reader;
296297
mod buf_writer;

src/io/utils.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use std::{error::Error, fmt, io};
2+
use crate::utils::VerboseErrorExt;
3+
4+
/// Wrap `std::io::Error` with additional message
5+
///
6+
/// *Note* Only active when `verbose-errors` feature is enabled for this crate!
7+
///
8+
/// Keeps the original error kind and stores the original I/O error as `source`.
9+
impl<T> VerboseErrorExt for Result<T, io::Error> {
10+
fn verbose_context(self, message: impl Fn() -> String) -> Self {
11+
if cfg!(feature = "verbose-errors") {
12+
self.map_err(|e| VerboseError::wrap(e, message()))
13+
} else {
14+
self
15+
}
16+
}
17+
}
18+
19+
#[derive(Debug)]
20+
struct VerboseError {
21+
source: io::Error,
22+
message: String,
23+
}
24+
25+
impl VerboseError {
26+
fn wrap(source: io::Error, message: impl Into<String>) -> io::Error {
27+
io::Error::new(
28+
source.kind(),
29+
VerboseError {
30+
source,
31+
message: message.into(),
32+
},
33+
)
34+
}
35+
}
36+
37+
impl fmt::Display for VerboseError {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
write!(f, "{}", self.message)
40+
}
41+
}
42+
43+
impl Error for VerboseError {
44+
fn description(&self) -> &str {
45+
self.source.description()
46+
}
47+
48+
fn source(&self) -> Option<&(dyn Error + 'static)> {
49+
Some(&self.source)
50+
}
51+
}

src/utils.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ pub fn random(n: u32) -> u32 {
5252
})
5353
}
5454

55+
/// Add additional context to errors
56+
///
57+
/// *Note for implementors:* The given closure must only be executed when
58+
/// `verbose-errors` feature is enabled for this crate!
59+
pub(crate) trait VerboseErrorExt {
60+
fn verbose_context(self, message: impl Fn() -> String) -> Self;
61+
}
62+
5563
/// Defers evaluation of a block of code until the end of the scope.
5664
#[cfg(feature = "default")]
5765
#[doc(hidden)]

tests/verbose_errors.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#[cfg(feature = "verbose-errors")]
2+
mod verbose_tests {
3+
use async_std::{fs, task};
4+
5+
#[test]
6+
fn open_file() {
7+
task::block_on(async {
8+
let non_existing_file =
9+
"/ashjudlkahasdasdsikdhajik/asdasdasdasdasdasd/fjuiklashdbflasas";
10+
let res = fs::File::open(non_existing_file).await;
11+
match res {
12+
Ok(_) => panic!("Found file with random name: We live in a simulation"),
13+
Err(e) => assert_eq!(
14+
"Could not open /ashjudlkahasdasdsikdhajik/asdasdasdasdasdasd/fjuiklashdbflasas",
15+
&format!("{}", e)
16+
),
17+
}
18+
})
19+
}
20+
}

0 commit comments

Comments
 (0)