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`
26    caused by: The system cannot find the file specified. (os error 2)
27```
28
29# Usage
30
31fs-err's API is the same as [`std::fs`][std::fs], so migrating code to use it is easy.
32
33```no_run
34// use std::fs;
35use fs_err as fs;
36
37let contents = fs::read_to_string("foo.txt")?;
38
39println!("Read foo.txt: {}", contents);
40
41# Ok::<(), std::io::Error>(())
42```
43
44fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err
45compose well with traits from the standard library like
46[`std::io::Read`][std::io::Read] and crates that use them like
47[`serde_json`][serde_json]:
48
49```no_run
50use fs_err::File;
51
52let file = File::open("my-config.json")?;
53
54// If an I/O error occurs inside serde_json, the error will include a file path
55// as well as what operation was being performed.
56let decoded: Vec<String> = serde_json::from_reader(file)?;
57
58println!("Program config: {:?}", decoded);
59
60# Ok::<(), Box<dyn std::error::Error>>(())
61```
62
63[std::fs]: https://doc.rust-lang.org/stable/std/fs/
64[std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
65[std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html
66[serde_json]: https://crates.io/crates/serde_json
67*/
68
69#![doc(html_root_url = "https://docs.rs/fs-err/2.11.0")]
70#![deny(missing_debug_implementations, missing_docs)]
71#![cfg_attr(docsrs, feature(doc_cfg))]
72
73mod dir;
74mod errors;
75mod file;
76mod open_options;
77pub mod os;
78mod path;
79#[cfg(feature = "tokio")]
80#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
81pub mod tokio;
82
83use std::fs;
84use std::io::{self, Read, Write};
85use std::path::{Path, PathBuf};
86
87use errors::{Error, ErrorKind, SourceDestError, SourceDestErrorKind};
88
89pub use dir::*;
90pub use file::*;
91pub use open_options::OpenOptions;
92pub use path::PathExt;
93
94/// Read the entire contents of a file into a bytes vector.
95///
96/// Wrapper for [`fs::read`](https://doc.rust-lang.org/stable/std/fs/fn.read.html).
97pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
98    let path = path.as_ref();
99    let mut file = file::open(path).map_err(|err_gen| err_gen(path.to_path_buf()))?;
100    let mut bytes = Vec::with_capacity(initial_buffer_size(&file));
101    file.read_to_end(&mut bytes)
102        .map_err(|err| Error::build(err, ErrorKind::Read, path))?;
103    Ok(bytes)
104}
105
106/// Read the entire contents of a file into a string.
107///
108/// Wrapper for [`fs::read_to_string`](https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html).
109pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {
110    let path = path.as_ref();
111    let mut file = file::open(path).map_err(|err_gen| err_gen(path.to_path_buf()))?;
112    let mut string = String::with_capacity(initial_buffer_size(&file));
113    file.read_to_string(&mut string)
114        .map_err(|err| Error::build(err, ErrorKind::Read, path))?;
115    Ok(string)
116}
117
118/// Write a slice as the entire contents of a file.
119///
120/// Wrapper for [`fs::write`](https://doc.rust-lang.org/stable/std/fs/fn.write.html).
121pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
122    let path = path.as_ref();
123    file::create(path)
124        .map_err(|err_gen| err_gen(path.to_path_buf()))?
125        .write_all(contents.as_ref())
126        .map_err(|err| Error::build(err, ErrorKind::Write, path))
127}
128
129/// Copies the contents of one file to another. This function will also copy the
130/// permission bits of the original file to the destination file.
131///
132/// Wrapper for [`fs::copy`](https://doc.rust-lang.org/stable/std/fs/fn.copy.html).
133pub fn copy<P, Q>(from: P, to: Q) -> io::Result<u64>
134where
135    P: AsRef<Path>,
136    Q: AsRef<Path>,
137{
138    let from = from.as_ref();
139    let to = to.as_ref();
140    fs::copy(from, to)
141        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::Copy, from, to))
142}
143
144/// Creates a new, empty directory at the provided path.
145///
146/// Wrapper for [`fs::create_dir`](https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html).
147pub fn create_dir<P>(path: P) -> io::Result<()>
148where
149    P: AsRef<Path>,
150{
151    let path = path.as_ref();
152    fs::create_dir(path).map_err(|source| Error::build(source, ErrorKind::CreateDir, path))
153}
154
155/// Recursively create a directory and all of its parent components if they are missing.
156///
157/// Wrapper for [`fs::create_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html).
158pub fn create_dir_all<P>(path: P) -> io::Result<()>
159where
160    P: AsRef<Path>,
161{
162    let path = path.as_ref();
163    fs::create_dir_all(path).map_err(|source| Error::build(source, ErrorKind::CreateDir, path))
164}
165
166/// Removes an empty directory.
167///
168/// Wrapper for [`fs::remove_dir`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir.html).
169pub fn remove_dir<P>(path: P) -> io::Result<()>
170where
171    P: AsRef<Path>,
172{
173    let path = path.as_ref();
174    fs::remove_dir(path).map_err(|source| Error::build(source, ErrorKind::RemoveDir, path))
175}
176
177/// Removes a directory at this path, after removing all its contents. Use carefully!
178///
179/// Wrapper for [`fs::remove_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html).
180pub fn remove_dir_all<P>(path: P) -> io::Result<()>
181where
182    P: AsRef<Path>,
183{
184    let path = path.as_ref();
185    fs::remove_dir_all(path).map_err(|source| Error::build(source, ErrorKind::RemoveDir, path))
186}
187
188/// Removes a file from the filesystem.
189///
190/// Wrapper for [`fs::remove_file`](https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html).
191pub fn remove_file<P>(path: P) -> io::Result<()>
192where
193    P: AsRef<Path>,
194{
195    let path = path.as_ref();
196    fs::remove_file(path).map_err(|source| Error::build(source, ErrorKind::RemoveFile, path))
197}
198
199/// Given a path, query the file system to get information about a file, directory, etc.
200///
201/// Wrapper for [`fs::metadata`](https://doc.rust-lang.org/stable/std/fs/fn.metadata.html).
202pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> {
203    let path = path.as_ref();
204    fs::metadata(path).map_err(|source| Error::build(source, ErrorKind::Metadata, path))
205}
206
207/// Returns the canonical, absolute form of a path with all intermediate components
208/// normalized and symbolic links resolved.
209///
210/// Wrapper for [`fs::canonicalize`](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html).
211pub fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
212    let path = path.as_ref();
213    fs::canonicalize(path).map_err(|source| Error::build(source, ErrorKind::Canonicalize, path))
214}
215
216/// Creates a new hard link on the filesystem.
217///
218/// Wrapper for [`fs::hard_link`](https://doc.rust-lang.org/stable/std/fs/fn.hard_link.html).
219pub fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
220    let src = src.as_ref();
221    let dst = dst.as_ref();
222    fs::hard_link(src, dst)
223        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::HardLink, src, dst))
224}
225
226/// Reads a symbolic link, returning the file that the link points to.
227///
228/// Wrapper for [`fs::read_link`](https://doc.rust-lang.org/stable/std/fs/fn.read_link.html).
229pub fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
230    let path = path.as_ref();
231    fs::read_link(path).map_err(|source| Error::build(source, ErrorKind::ReadLink, path))
232}
233
234/// Rename a file or directory to a new name, replacing the original file if to already exists.
235///
236/// Wrapper for [`fs::rename`](https://doc.rust-lang.org/stable/std/fs/fn.rename.html).
237pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
238    let from = from.as_ref();
239    let to = to.as_ref();
240    fs::rename(from, to)
241        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::Rename, from, to))
242}
243
244/// Wrapper for [`fs::soft_link`](https://doc.rust-lang.org/stable/std/fs/fn.soft_link.html).
245#[deprecated = "replaced with std::os::unix::fs::symlink and \
246std::os::windows::fs::{symlink_file, symlink_dir}"]
247pub fn soft_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
248    let src = src.as_ref();
249    let dst = dst.as_ref();
250    #[allow(deprecated)]
251    fs::soft_link(src, dst)
252        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::SoftLink, src, dst))
253}
254
255/// Query the metadata about a file without following symlinks.
256///
257/// Wrapper for [`fs::symlink_metadata`](https://doc.rust-lang.org/stable/std/fs/fn.symlink_metadata.html).
258pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> {
259    let path = path.as_ref();
260    fs::symlink_metadata(path)
261        .map_err(|source| Error::build(source, ErrorKind::SymlinkMetadata, path))
262}
263
264/// Changes the permissions found on a file or a directory.
265///
266/// Wrapper for [`fs::set_permissions`](https://doc.rust-lang.org/stable/std/fs/fn.set_permissions.html).
267pub fn set_permissions<P: AsRef<Path>>(path: P, perm: fs::Permissions) -> io::Result<()> {
268    let path = path.as_ref();
269    fs::set_permissions(path, perm)
270        .map_err(|source| Error::build(source, ErrorKind::SetPermissions, path))
271}
272
273fn initial_buffer_size(file: &std::fs::File) -> usize {
274    file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
275}
276
277pub(crate) use private::Sealed;
278mod private {
279    pub trait Sealed {}
280
281    impl Sealed for crate::File {}
282    impl Sealed for std::path::Path {}
283    impl Sealed for crate::OpenOptions {}
284}