• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*!
2 fs-err is a drop-in replacement for [`std::fs`][std::fs] that provides more
3 helpful messages on errors. Extra information includes which operations was
4 attempted and any involved paths.
5 
6 # Error Messages
7 
8 Using [`std::fs`][std::fs], if this code fails:
9 
10 ```no_run
11 # use std::fs::File;
12 let file = File::open("does not exist.txt")?;
13 # Ok::<(), std::io::Error>(())
14 ```
15 
16 The error message that Rust gives you isn't very useful:
17 
18 ```txt
19 The 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
25 failed 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 
31 fs-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;
35 use fs_err as fs;
36 
37 let contents = fs::read_to_string("foo.txt")?;
38 
39 println!("Read foo.txt: {}", contents);
40 
41 # Ok::<(), std::io::Error>(())
42 ```
43 
44 fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err
45 compose 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
50 use fs_err::File;
51 
52 let 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.
56 let decoded: Vec<String> = serde_json::from_reader(file)?;
57 
58 println!("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 
73 mod dir;
74 mod errors;
75 mod file;
76 mod open_options;
77 pub mod os;
78 mod path;
79 #[cfg(feature = "tokio")]
80 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
81 pub mod tokio;
82 
83 use std::fs;
84 use std::io::{self, Read, Write};
85 use std::path::{Path, PathBuf};
86 
87 use errors::{Error, ErrorKind, SourceDestError, SourceDestErrorKind};
88 
89 pub use dir::*;
90 pub use file::*;
91 pub use open_options::OpenOptions;
92 pub 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).
read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>>97 pub 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).
read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String>109 pub 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).
write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()>121 pub 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).
copy<P, Q>(from: P, to: Q) -> io::Result<u64> where P: AsRef<Path>, Q: AsRef<Path>,133 pub fn copy<P, Q>(from: P, to: Q) -> io::Result<u64>
134 where
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).
create_dir<P>(path: P) -> io::Result<()> where P: AsRef<Path>,147 pub fn create_dir<P>(path: P) -> io::Result<()>
148 where
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).
create_dir_all<P>(path: P) -> io::Result<()> where P: AsRef<Path>,158 pub fn create_dir_all<P>(path: P) -> io::Result<()>
159 where
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).
remove_dir<P>(path: P) -> io::Result<()> where P: AsRef<Path>,169 pub fn remove_dir<P>(path: P) -> io::Result<()>
170 where
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).
remove_dir_all<P>(path: P) -> io::Result<()> where P: AsRef<Path>,180 pub fn remove_dir_all<P>(path: P) -> io::Result<()>
181 where
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).
remove_file<P>(path: P) -> io::Result<()> where P: AsRef<Path>,191 pub fn remove_file<P>(path: P) -> io::Result<()>
192 where
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).
metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata>202 pub 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).
canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf>211 pub 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).
hard_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()>219 pub 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).
read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf>229 pub 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).
rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()>237 pub 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 \
246 std::os::windows::fs::{symlink_file, symlink_dir}"]
soft_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()>247 pub 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).
symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata>258 pub 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).
set_permissions<P: AsRef<Path>>(path: P, perm: fs::Permissions) -> io::Result<()>267 pub 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 
initial_buffer_size(file: &std::fs::File) -> usize273 fn initial_buffer_size(file: &std::fs::File) -> usize {
274     file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
275 }
276 
277 pub(crate) use private::Sealed;
278 mod 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 }
285