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
67
68# Minimum Supported Rust Version
69
70The oldest rust version this crate is tested on is **1.40**.
71
72This 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.
73
74If the `tokio` feature is enabled, this crate will inherit the MSRV of the selected [`tokio`](https://crates.io/crates/tokio) version.
75
76[std::fs]: https://doc.rust-lang.org/stable/std/fs/
77[std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
78[std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html
79[serde_json]: https://crates.io/crates/serde_json
80*/
81
82#![doc(html_root_url = "https://docs.rs/fs-err/3.1.0")]
83#![deny(missing_debug_implementations, missing_docs)]
84#![cfg_attr(docsrs, feature(doc_cfg))]
85
86mod dir;
87mod errors;
88mod file;
89mod open_options;
90pub mod os;
91mod path;
92#[cfg(feature = "tokio")]
93#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
94pub mod tokio;
95
96use std::fs;
97use std::io::{self, Read, Write};
98use std::path::{Path, PathBuf};
99
100use errors::{Error, ErrorKind, SourceDestError, SourceDestErrorKind};
101
102pub use dir::*;
103pub use file::*;
104pub use open_options::OpenOptions;
105pub use path::PathExt;
106
107/// Read the entire contents of a file into a bytes vector.
108///
109/// Wrapper for [`fs::read`](https://doc.rust-lang.org/stable/std/fs/fn.read.html).
110pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
111    let path = path.as_ref();
112    let mut file = file::open(path).map_err(|err_gen| err_gen(path.to_path_buf()))?;
113    let mut bytes = Vec::with_capacity(initial_buffer_size(&file));
114    file.read_to_end(&mut bytes)
115        .map_err(|err| Error::build(err, ErrorKind::Read, path))?;
116    Ok(bytes)
117}
118
119/// Read the entire contents of a file into a string.
120///
121/// Wrapper for [`fs::read_to_string`](https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html).
122pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {
123    let path = path.as_ref();
124    let mut file = file::open(path).map_err(|err_gen| err_gen(path.to_path_buf()))?;
125    let mut string = String::with_capacity(initial_buffer_size(&file));
126    file.read_to_string(&mut string)
127        .map_err(|err| Error::build(err, ErrorKind::Read, path))?;
128    Ok(string)
129}
130
131/// Write a slice as the entire contents of a file.
132///
133/// Wrapper for [`fs::write`](https://doc.rust-lang.org/stable/std/fs/fn.write.html).
134pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
135    let path = path.as_ref();
136    file::create(path)
137        .map_err(|err_gen| err_gen(path.to_path_buf()))?
138        .write_all(contents.as_ref())
139        .map_err(|err| Error::build(err, ErrorKind::Write, path))
140}
141
142/// Copies the contents of one file to another. This function will also copy the
143/// permission bits of the original file to the destination file.
144///
145/// Wrapper for [`fs::copy`](https://doc.rust-lang.org/stable/std/fs/fn.copy.html).
146pub fn copy<P, Q>(from: P, to: Q) -> io::Result<u64>
147where
148    P: AsRef<Path>,
149    Q: AsRef<Path>,
150{
151    let from = from.as_ref();
152    let to = to.as_ref();
153    fs::copy(from, to)
154        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::Copy, from, to))
155}
156
157/// Creates a new, empty directory at the provided path.
158///
159/// Wrapper for [`fs::create_dir`](https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html).
160pub fn create_dir<P>(path: P) -> io::Result<()>
161where
162    P: AsRef<Path>,
163{
164    let path = path.as_ref();
165    fs::create_dir(path).map_err(|source| Error::build(source, ErrorKind::CreateDir, path))
166}
167
168/// Recursively create a directory and all of its parent components if they are missing.
169///
170/// Wrapper for [`fs::create_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html).
171pub fn create_dir_all<P>(path: P) -> io::Result<()>
172where
173    P: AsRef<Path>,
174{
175    let path = path.as_ref();
176    fs::create_dir_all(path).map_err(|source| Error::build(source, ErrorKind::CreateDir, path))
177}
178
179/// Removes an empty directory.
180///
181/// Wrapper for [`fs::remove_dir`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir.html).
182pub fn remove_dir<P>(path: P) -> io::Result<()>
183where
184    P: AsRef<Path>,
185{
186    let path = path.as_ref();
187    fs::remove_dir(path).map_err(|source| Error::build(source, ErrorKind::RemoveDir, path))
188}
189
190/// Removes a directory at this path, after removing all its contents. Use carefully!
191///
192/// Wrapper for [`fs::remove_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html).
193pub fn remove_dir_all<P>(path: P) -> io::Result<()>
194where
195    P: AsRef<Path>,
196{
197    let path = path.as_ref();
198    fs::remove_dir_all(path).map_err(|source| Error::build(source, ErrorKind::RemoveDir, path))
199}
200
201/// Removes a file from the filesystem.
202///
203/// Wrapper for [`fs::remove_file`](https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html).
204pub fn remove_file<P>(path: P) -> io::Result<()>
205where
206    P: AsRef<Path>,
207{
208    let path = path.as_ref();
209    fs::remove_file(path).map_err(|source| Error::build(source, ErrorKind::RemoveFile, path))
210}
211
212/// Given a path, query the file system to get information about a file, directory, etc.
213///
214/// Wrapper for [`fs::metadata`](https://doc.rust-lang.org/stable/std/fs/fn.metadata.html).
215pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> {
216    let path = path.as_ref();
217    fs::metadata(path).map_err(|source| Error::build(source, ErrorKind::Metadata, path))
218}
219
220/// Returns the canonical, absolute form of a path with all intermediate components
221/// normalized and symbolic links resolved.
222///
223/// Wrapper for [`fs::canonicalize`](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html).
224pub fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
225    let path = path.as_ref();
226    fs::canonicalize(path).map_err(|source| Error::build(source, ErrorKind::Canonicalize, path))
227}
228
229/// Creates a new hard link on the filesystem.
230///
231/// Wrapper for [`fs::hard_link`](https://doc.rust-lang.org/stable/std/fs/fn.hard_link.html).
232pub fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
233    let src = src.as_ref();
234    let dst = dst.as_ref();
235    fs::hard_link(src, dst)
236        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::HardLink, src, dst))
237}
238
239/// Reads a symbolic link, returning the file that the link points to.
240///
241/// Wrapper for [`fs::read_link`](https://doc.rust-lang.org/stable/std/fs/fn.read_link.html).
242pub fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
243    let path = path.as_ref();
244    fs::read_link(path).map_err(|source| Error::build(source, ErrorKind::ReadLink, path))
245}
246
247/// Rename a file or directory to a new name, replacing the original file if to already exists.
248///
249/// Wrapper for [`fs::rename`](https://doc.rust-lang.org/stable/std/fs/fn.rename.html).
250pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
251    let from = from.as_ref();
252    let to = to.as_ref();
253    fs::rename(from, to)
254        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::Rename, from, to))
255}
256
257/// Wrapper for [`fs::soft_link`](https://doc.rust-lang.org/stable/std/fs/fn.soft_link.html).
258#[deprecated = "replaced with std::os::unix::fs::symlink and \
259std::os::windows::fs::{symlink_file, symlink_dir}"]
260pub fn soft_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
261    let src = src.as_ref();
262    let dst = dst.as_ref();
263    #[allow(deprecated)]
264    fs::soft_link(src, dst)
265        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::SoftLink, src, dst))
266}
267
268/// Query the metadata about a file without following symlinks.
269///
270/// Wrapper for [`fs::symlink_metadata`](https://doc.rust-lang.org/stable/std/fs/fn.symlink_metadata.html).
271pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> {
272    let path = path.as_ref();
273    fs::symlink_metadata(path)
274        .map_err(|source| Error::build(source, ErrorKind::SymlinkMetadata, path))
275}
276
277/// Changes the permissions found on a file or a directory.
278///
279/// Wrapper for [`fs::set_permissions`](https://doc.rust-lang.org/stable/std/fs/fn.set_permissions.html).
280pub fn set_permissions<P: AsRef<Path>>(path: P, perm: fs::Permissions) -> io::Result<()> {
281    let path = path.as_ref();
282    fs::set_permissions(path, perm)
283        .map_err(|source| Error::build(source, ErrorKind::SetPermissions, path))
284}
285
286fn initial_buffer_size(file: &std::fs::File) -> usize {
287    file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
288}
289
290pub(crate) use private::Sealed;
291mod private {
292    pub trait Sealed {}
293
294    impl Sealed for crate::File {}
295    impl Sealed for std::path::Path {}
296    impl Sealed for crate::OpenOptions {}
297}