| 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). | 
|---|
| 97 | pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> { | 
|---|
| 98 | let path: &Path = path.as_ref(); | 
|---|
| 99 | let mut file: File = file::open(path).map_err(|err_gen: impl FnOnce(PathBuf) -> Error| err_gen(path.to_path_buf()))?; | 
|---|
| 100 | let mut bytes: Vec = Vec::with_capacity(initial_buffer_size(&file)); | 
|---|
| 101 | file.read_to_end(&mut bytes) | 
|---|
| 102 | .map_err(|err: Error| Error::build(source:err, kind: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). | 
|---|
| 109 | pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> { | 
|---|
| 110 | let path: &Path = path.as_ref(); | 
|---|
| 111 | let mut file: File = file::open(path).map_err(|err_gen: impl FnOnce(PathBuf) -> Error| err_gen(path.to_path_buf()))?; | 
|---|
| 112 | let mut string: String = String::with_capacity(initial_buffer_size(&file)); | 
|---|
| 113 | file.read_to_string(&mut string) | 
|---|
| 114 | .map_err(|err: Error| Error::build(source:err, kind: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). | 
|---|
| 121 | pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> { | 
|---|
| 122 | let path: &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| Error::build(source:err, kind: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). | 
|---|
| 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: &Path = from.as_ref(); | 
|---|
| 139 | let to: &Path = to.as_ref(); | 
|---|
| 140 | fs::copy(from, to) | 
|---|
| 141 | .map_err(|source: Error| SourceDestError::build(source, kind: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). | 
|---|
| 147 | pub fn create_dir<P>(path: P) -> io::Result<()> | 
|---|
| 148 | where | 
|---|
| 149 | P: AsRef<Path>, | 
|---|
| 150 | { | 
|---|
| 151 | let path: &Path = path.as_ref(); | 
|---|
| 152 | fs::create_dir(path).map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 158 | pub fn create_dir_all<P>(path: P) -> io::Result<()> | 
|---|
| 159 | where | 
|---|
| 160 | P: AsRef<Path>, | 
|---|
| 161 | { | 
|---|
| 162 | let path: &Path = path.as_ref(); | 
|---|
| 163 | fs::create_dir_all(path).map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 169 | pub fn remove_dir<P>(path: P) -> io::Result<()> | 
|---|
| 170 | where | 
|---|
| 171 | P: AsRef<Path>, | 
|---|
| 172 | { | 
|---|
| 173 | let path: &Path = path.as_ref(); | 
|---|
| 174 | fs::remove_dir(path).map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 180 | pub fn remove_dir_all<P>(path: P) -> io::Result<()> | 
|---|
| 181 | where | 
|---|
| 182 | P: AsRef<Path>, | 
|---|
| 183 | { | 
|---|
| 184 | let path: &Path = path.as_ref(); | 
|---|
| 185 | fs::remove_dir_all(path).map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 191 | pub fn remove_file<P>(path: P) -> io::Result<()> | 
|---|
| 192 | where | 
|---|
| 193 | P: AsRef<Path>, | 
|---|
| 194 | { | 
|---|
| 195 | let path: &Path = path.as_ref(); | 
|---|
| 196 | fs::remove_file(path).map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 202 | pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> { | 
|---|
| 203 | let path: &Path = path.as_ref(); | 
|---|
| 204 | fs::metadata(path).map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 211 | pub fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> { | 
|---|
| 212 | let path: &Path = path.as_ref(); | 
|---|
| 213 | fs::canonicalize(path).map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 219 | pub fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> { | 
|---|
| 220 | let src: &Path = src.as_ref(); | 
|---|
| 221 | let dst: &Path = dst.as_ref(); | 
|---|
| 222 | fs::hard_link(src, dst) | 
|---|
| 223 | .map_err(|source: Error| SourceDestError::build(source, kind:SourceDestErrorKind::HardLink, from_path:src, to_path: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). | 
|---|
| 229 | pub fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> { | 
|---|
| 230 | let path: &Path = path.as_ref(); | 
|---|
| 231 | fs::read_link(path).map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 237 | pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> { | 
|---|
| 238 | let from: &Path = from.as_ref(); | 
|---|
| 239 | let to: &Path = to.as_ref(); | 
|---|
| 240 | fs::rename(from, to) | 
|---|
| 241 | .map_err(|source: Error| SourceDestError::build(source, kind: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}"] | 
|---|
| 247 | pub fn soft_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> { | 
|---|
| 248 | let src: &Path = src.as_ref(); | 
|---|
| 249 | let dst: &Path = dst.as_ref(); | 
|---|
| 250 | #[ allow(deprecated)] | 
|---|
| 251 | fs::soft_link(src, dst) | 
|---|
| 252 | .map_err(|source: Error| SourceDestError::build(source, kind:SourceDestErrorKind::SoftLink, from_path:src, to_path: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). | 
|---|
| 258 | pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> { | 
|---|
| 259 | let path: &Path = path.as_ref(); | 
|---|
| 260 | fs::symlink_metadata(path) | 
|---|
| 261 | .map_err(|source: Error| Error::build(source, kind: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). | 
|---|
| 267 | pub fn set_permissions<P: AsRef<Path>>(path: P, perm: fs::Permissions) -> io::Result<()> { | 
|---|
| 268 | let path: &Path = path.as_ref(); | 
|---|
| 269 | fs::set_permissions(path, perm) | 
|---|
| 270 | .map_err(|source: Error| Error::build(source, kind:ErrorKind::SetPermissions, path)) | 
|---|
| 271 | } | 
|---|
| 272 |  | 
|---|
| 273 | fn initial_buffer_size(file: &std::fs::File) -> usize { | 
|---|
| 274 | file.metadata().map(|m| m.len() as usize + 1).unwrap_or(default: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 |  | 
|---|