fs_err/
lib.rs

1/*!
2fs-err is a drop-in replacement for [`std::fs`][std::fs] that provides more
3helpful messages on errors. Extra information includes which operations was
4attempted and any involved paths.
5
6# Error Messages
7
8Using [`std::fs`][std::fs], if this code fails:
9
10```no_run
11# use std::fs::File;
12let file = File::open("does not exist.txt")?;
13# Ok::<(), std::io::Error>(())
14```
15
16The error message that Rust gives you isn't very useful:
17
18```txt
19The system cannot find the file specified. (os error 2)
20```
21
22...but if we use fs-err instead, our error contains more actionable information:
23
24```txt
25failed to open file `does not exist.txt`: The system cannot find the file specified. (os error 2)
26```
27
28# Usage
29
30fs-err's API is the same as [`std::fs`][std::fs], so migrating code to use it is easy.
31
32```no_run
33// use std::fs;
34use fs_err as fs;
35
36let contents = fs::read_to_string("foo.txt")?;
37
38println!("Read foo.txt: {}", contents);
39
40# Ok::<(), std::io::Error>(())
41```
42
43fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err
44compose well with traits from the standard library like
45[`std::io::Read`][std::io::Read] and crates that use them like
46[`serde_json`][serde_json]:
47
48```no_run
49use fs_err::File;
50
51let file = File::open("my-config.json")?;
52
53// If an I/O error occurs inside serde_json, the error will include a file path
54// as well as what operation was being performed.
55let decoded: Vec<String> = serde_json::from_reader(file)?;
56
57println!("Program config: {:?}", decoded);
58
59# Ok::<(), Box<dyn std::error::Error>>(())
60```
61
62# Feature flags
63
64* `expose_original_error`: when enabled, the [`Error::source()`](https://doc.rust-lang.org/stable/std/error/trait.Error.html#method.source) method of errors returned by this crate return the original `io::Error`. To avoid duplication in error messages,
65  this also suppresses printing its message in their `Display` implementation, so make sure that you are printing the full error chain.
66* `debug`: Debug filesystem errors faster by exposing more information. When a filesystem command
67  fails, the error message might say "file does not exist." But it won't say **why** it doesn't exist.
68  Perhaps the programmer misspelled the filename, perhaps that directory doesn't exist, or if it does,
69  but the current user doesn't have permissions to see the contents. This feature analyzes the filesystem
70  to output various "facts" that will help a developer debug the root of the current error.
71  * Warning: Exposes filesystem metadata. This feature exposes additional metadata about your filesystem
72    such as directory contents and permissions, which may be sensitive. Only enable `debug` when
73    error messages won't be displayed to the end user, or they have access to filesystem metadata some
74    other way.
75  * Warning: This may slow down your program. This feature will trigger additional filesystem calls when
76    errors occur, which may cause performance issues. Do not use if filesystem errors are common on a
77    performance-sensitive "hotpath." Use in scenarios where developer hours are more expensive than
78    compute time.
79  * To mitigate performance and security concerns, consider only enabling this feature in `dev-dependencies`:
80  * Requires Rust 1.79 or later
81
82```toml
83[dev-dependencies]
84fs-err = { features = ["debug"] }
85```
86
87To use with the `tokio` feature, use `debug_tokio`:
88
89```toml
90[dependencies]
91fs-err = { features = ["debug_tokio", "tokio"] }
92```
93
94# Minimum Supported Rust Version
95
96The oldest rust version this crate is tested on is **1.40**.
97
98This crate will generally be conservative with rust version updates. It uses the [`autocfg`](https://crates.io/crates/autocfg) crate to allow wrapping new APIs without incrementing the MSRV.
99
100If the `tokio` feature is enabled, this crate will inherit the MSRV of the selected [`tokio`](https://crates.io/crates/tokio) version.
101
102[std::fs]: https://doc.rust-lang.org/stable/std/fs/
103[std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
104[std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html
105[serde_json]: https://crates.io/crates/serde_json
106*/
107
108#![doc(html_root_url = "https://docs.rs/fs-err/3.2.0")]
109#![deny(missing_debug_implementations, missing_docs)]
110#![cfg_attr(docsrs, feature(doc_cfg))]
111
112mod dir;
113mod errors;
114mod file;
115mod open_options;
116pub mod os;
117mod path;
118#[cfg(feature = "tokio")]
119#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
120pub mod tokio;
121
122use std::fs;
123use std::io::{self, Read, Write};
124use std::path::{Path, PathBuf};
125
126use errors::{Error, ErrorKind, SourceDestError, SourceDestErrorKind};
127
128pub use dir::*;
129pub use file::*;
130pub use open_options::OpenOptions;
131pub use path::PathExt;
132
133/// Read the entire contents of a file into a bytes vector.
134///
135/// Wrapper for [`fs::read`](https://doc.rust-lang.org/stable/std/fs/fn.read.html).
136pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
137    let path = path.as_ref();
138    let mut file = file::open(path).map_err(|err_gen| err_gen(path.to_path_buf()))?;
139    let mut bytes = Vec::with_capacity(initial_buffer_size(&file));
140    file.read_to_end(&mut bytes)
141        .map_err(|err| Error::build(err, ErrorKind::Read, path))?;
142    Ok(bytes)
143}
144
145/// Read the entire contents of a file into a string.
146///
147/// Wrapper for [`fs::read_to_string`](https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html).
148pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {
149    let path = path.as_ref();
150    let mut file = file::open(path).map_err(|err_gen| err_gen(path.to_path_buf()))?;
151    let mut string = String::with_capacity(initial_buffer_size(&file));
152    file.read_to_string(&mut string)
153        .map_err(|err| Error::build(err, ErrorKind::Read, path))?;
154    Ok(string)
155}
156
157/// Write a slice as the entire contents of a file.
158///
159/// Wrapper for [`fs::write`](https://doc.rust-lang.org/stable/std/fs/fn.write.html).
160pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
161    let path = path.as_ref();
162    file::create(path)
163        .map_err(|err_gen| err_gen(path.to_path_buf()))?
164        .write_all(contents.as_ref())
165        .map_err(|err| Error::build(err, ErrorKind::Write, path))
166}
167
168/// Copies the contents of one file to another. This function will also copy the
169/// permission bits of the original file to the destination file.
170///
171/// Wrapper for [`fs::copy`](https://doc.rust-lang.org/stable/std/fs/fn.copy.html).
172pub fn copy<P, Q>(from: P, to: Q) -> io::Result<u64>
173where
174    P: AsRef<Path>,
175    Q: AsRef<Path>,
176{
177    let from = from.as_ref();
178    let to = to.as_ref();
179    fs::copy(from, to)
180        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::Copy, from, to))
181}
182
183/// Creates a new, empty directory at the provided path.
184///
185/// Wrapper for [`fs::create_dir`](https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html).
186pub fn create_dir<P>(path: P) -> io::Result<()>
187where
188    P: AsRef<Path>,
189{
190    let path = path.as_ref();
191    fs::create_dir(path).map_err(|source| Error::build(source, ErrorKind::CreateDir, path))
192}
193
194/// Recursively create a directory and all of its parent components if they are missing.
195///
196/// Wrapper for [`fs::create_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html).
197pub fn create_dir_all<P>(path: P) -> io::Result<()>
198where
199    P: AsRef<Path>,
200{
201    let path = path.as_ref();
202    fs::create_dir_all(path).map_err(|source| Error::build(source, ErrorKind::CreateDir, path))
203}
204
205/// Removes an empty directory.
206///
207/// Wrapper for [`fs::remove_dir`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir.html).
208pub fn remove_dir<P>(path: P) -> io::Result<()>
209where
210    P: AsRef<Path>,
211{
212    let path = path.as_ref();
213    fs::remove_dir(path).map_err(|source| Error::build(source, ErrorKind::RemoveDir, path))
214}
215
216/// Removes a directory at this path, after removing all its contents. Use carefully!
217///
218/// Wrapper for [`fs::remove_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html).
219pub fn remove_dir_all<P>(path: P) -> io::Result<()>
220where
221    P: AsRef<Path>,
222{
223    let path = path.as_ref();
224    fs::remove_dir_all(path).map_err(|source| Error::build(source, ErrorKind::RemoveDir, path))
225}
226
227/// Removes a file from the filesystem.
228///
229/// Wrapper for [`fs::remove_file`](https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html).
230pub fn remove_file<P>(path: P) -> io::Result<()>
231where
232    P: AsRef<Path>,
233{
234    let path = path.as_ref();
235    fs::remove_file(path).map_err(|source| Error::build(source, ErrorKind::RemoveFile, path))
236}
237
238/// Given a path, query the file system to get information about a file, directory, etc.
239///
240/// Wrapper for [`fs::metadata`](https://doc.rust-lang.org/stable/std/fs/fn.metadata.html).
241pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> {
242    let path = path.as_ref();
243    fs::metadata(path).map_err(|source| Error::build(source, ErrorKind::Metadata, path))
244}
245
246/// Returns `Ok(true)` if the path points at an existing entity.
247///
248/// Wrapper for [`fs::exists`](https://doc.rust-lang.org/stable/std/fs/fn.exists.html).
249#[cfg(rustc_1_81)]
250pub fn exists<P: AsRef<Path>>(path: P) -> io::Result<bool> {
251    let path = path.as_ref();
252    fs::exists(path).map_err(|source| Error::build(source, ErrorKind::FileExists, path))
253}
254
255/// Returns the canonical, absolute form of a path with all intermediate components
256/// normalized and symbolic links resolved.
257///
258/// Wrapper for [`fs::canonicalize`](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html).
259pub fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
260    let path = path.as_ref();
261    fs::canonicalize(path).map_err(|source| Error::build(source, ErrorKind::Canonicalize, path))
262}
263
264/// Creates a new hard link on the filesystem.
265///
266/// Wrapper for [`fs::hard_link`](https://doc.rust-lang.org/stable/std/fs/fn.hard_link.html).
267pub fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
268    let src = src.as_ref();
269    let dst = dst.as_ref();
270    fs::hard_link(src, dst)
271        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::HardLink, src, dst))
272}
273
274/// Reads a symbolic link, returning the file that the link points to.
275///
276/// Wrapper for [`fs::read_link`](https://doc.rust-lang.org/stable/std/fs/fn.read_link.html).
277pub fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
278    let path = path.as_ref();
279    fs::read_link(path).map_err(|source| Error::build(source, ErrorKind::ReadLink, path))
280}
281
282/// Rename a file or directory to a new name, replacing the original file if to already exists.
283///
284/// Wrapper for [`fs::rename`](https://doc.rust-lang.org/stable/std/fs/fn.rename.html).
285pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
286    let from = from.as_ref();
287    let to = to.as_ref();
288    fs::rename(from, to)
289        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::Rename, from, to))
290}
291
292/// Wrapper for [`fs::soft_link`](https://doc.rust-lang.org/stable/std/fs/fn.soft_link.html).
293#[deprecated = "replaced with std::os::unix::fs::symlink and \
294std::os::windows::fs::{symlink_file, symlink_dir}"]
295pub fn soft_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
296    let src = src.as_ref();
297    let dst = dst.as_ref();
298    #[allow(deprecated)]
299    fs::soft_link(src, dst)
300        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::SoftLink, src, dst))
301}
302
303/// Query the metadata about a file without following symlinks.
304///
305/// Wrapper for [`fs::symlink_metadata`](https://doc.rust-lang.org/stable/std/fs/fn.symlink_metadata.html).
306pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> {
307    let path = path.as_ref();
308    fs::symlink_metadata(path)
309        .map_err(|source| Error::build(source, ErrorKind::SymlinkMetadata, path))
310}
311
312/// Changes the permissions found on a file or a directory.
313///
314/// Wrapper for [`fs::set_permissions`](https://doc.rust-lang.org/stable/std/fs/fn.set_permissions.html).
315pub fn set_permissions<P: AsRef<Path>>(path: P, perm: fs::Permissions) -> io::Result<()> {
316    let path = path.as_ref();
317    fs::set_permissions(path, perm)
318        .map_err(|source| Error::build(source, ErrorKind::SetPermissions, path))
319}
320
321fn initial_buffer_size(file: &std::fs::File) -> usize {
322    file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
323}
324
325pub(crate) use private::Sealed;
326mod private {
327    pub trait Sealed {}
328
329    impl Sealed for crate::File {}
330    impl Sealed for std::path::Path {}
331    impl Sealed for crate::OpenOptions {}
332}