├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README.tpl ├── build.rs └── src ├── dir.rs ├── errors.rs ├── file.rs ├── lib.rs ├── open_options.rs ├── os.rs ├── os ├── unix.rs └── windows.rs ├── path.rs └── tokio ├── dir_builder.rs ├── file.rs ├── mod.rs ├── open_options.rs └── read_dir.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | rust_version: [stable, beta, 1.40.0] 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v2 25 | 26 | - uses: dtolnay/rust-toolchain@v1 27 | with: 28 | toolchain: ${{ matrix.rust_version }} 29 | 30 | - run: cargo update 31 | if: ${{ matrix.rust_version == 'stable' || matrix.rust_version == 'beta' }} 32 | 33 | - name: cargo build 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: build 37 | 38 | - name: cargo test 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: test 42 | 43 | - name: cargo fmt 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: fmt 47 | args: --all -- --check 48 | if: matrix.rust_version == 'stable' 49 | 50 | - name: cargo clippy 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: clippy 54 | args: --tests -- -D warnings 55 | if: matrix.rust_version == 'stable' 56 | 57 | - name: cargo check --features tokio 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: check 61 | args: --features tokio 62 | if: ${{ matrix.rust_version == 'stable' || matrix.rust_version == 'beta' }} 63 | 64 | - name: cargo check --features expose_original_error 65 | uses: actions-rs/cargo@v1 66 | with: 67 | command: check 68 | args: --features expose_original_error 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # fs-err Changelog 2 | 3 | ## 3.1.0 4 | 5 | * Added new wrappers for `create_new` and `options` functions on `File` ([#69](https://github.com/andrewhickman/fs-err/pull/69)) 6 | 7 | ## 3.0.0 8 | 9 | * Error messages now include the original message from `std::io::Error` by default ([#60](https://github.com/andrewhickman/fs-err/pull/60)). Previously this was exposed through the [`Error::source()`](https://doc.rust-lang.org/stable/std/error/trait.Error.html#method.source) method. For example, previously a message would look like: 10 | 11 | ``` 12 | failed to open file `file.txt` 13 | ``` 14 | 15 | and you would have to remember to print the source, or use a library like `anyhow` to print the full chain of source errors. The new error message includes the cause by default 16 | 17 | ``` 18 | failed to open file `file.txt`: The system cannot find the file specified. (os error 2) 19 | ``` 20 | 21 | Note that the original error is no longer exposed though [`Error::source()`](https://doc.rust-lang.org/stable/std/error/trait.Error.html#method.source) by default. If you need access to it, you can restore the previous behaviour with the `expose_original_error` feature flag. 22 | 23 | * The `io_safety` feature flag has been removed, and this functionality is now always enabled on Rust versions which support it (1.63.0 and greater). 24 | 25 | * Removed deprecated APIs: `File::from_options`, `tokio::symlink` 26 | 27 | ## 2.11.0 28 | 29 | * Added the first line of the standard library documentation to each function's rustdocs, to make them more useful in IDEs ([#50](https://github.com/andrewhickman/fs-err/issues/45)) 30 | * Fixed the wrapper for `tokio::fs::symlink_dir()` on Windows being incorrectly named `symlink`. The old function is now deprecated and will be removed in the next breaking release. 31 | 32 | ## 2.10.0 33 | 34 | * Add `fs_err_try_exists` to `std::path::Path` via extension trait. This feature requires Rust 1.63 or later. ([#48](https://github.com/andrewhickman/fs-err/pull/48)) 35 | 36 | ## 2.9.0 37 | 38 | * Add wrappers for [`tokio::fs`](https://docs.rs/tokio/latest/tokio/fs/index.html) ([#40](https://github.com/andrewhickman/fs-err/pull/40)). 39 | 40 | ## 2.8.1 41 | 42 | * Fixed docs.rs build 43 | 44 | ## 2.8.0 45 | 46 | * Implement I/O safety traits (`AsFd`/`AsHandle`, `Into`/`Into`) for file. This feature requires Rust 1.63 or later and is gated behind the `io_safety` feature flag. ([#39](https://github.com/andrewhickman/fs-err/pull/39)) 47 | 48 | ## 2.7.0 49 | 50 | * Implement `From for std::fs::File` ([#38](https://github.com/andrewhickman/fs-err/pull/38)) 51 | 52 | ## 2.6.0 53 | 54 | * Added [`File::into_parts`](https://docs.rs/fs-err/2.6.0/fs_err/struct.File.html#method.into_parts) and [`File::file_mut`](https://docs.rs/fs-err/2.6.0/fs_err/struct.File.html#method.file_mut) to provide more access to the underlying `std::fs::File`. 55 | * Fixed some typos in documention ([#33](https://github.com/andrewhickman/fs-err/pull/33)) 56 | 57 | ## 2.5.0 58 | * Added `symlink` for unix platforms 59 | * Added `symlink_file` and `symlink_dir` for windows 60 | * Implemented os-specific extension traits for `File` 61 | - `std::os::unix::io::{AsRawFd, IntoRawFd}` 62 | - `std::os::windows::io::{AsRawHandle, IntoRawHandle}` 63 | - Added trait wrappers for `std::os::{unix, windows}::fs::FileExt` and implemented them for `fs_err::File` 64 | * Implemented os-specific extension traits for `OpenOptions` 65 | - Added trait wrappers for `std::os::{unix, windows}::fs::OpenOptionsExt` and implemented them for `fs_err::OpenOptions` 66 | * Improved compile times by converting arguments early and forwarding only a small number of types internally. There will be a slight performance hit only in the error case. 67 | * Reduced trait bounds on generics from `AsRef + Into` to either `AsRef` or `Into`, making the functions more general. 68 | 69 | ## 2.4.0 70 | * Added `canonicalize`, `hard link`, `read_link`, `rename`, `symlink_metadata` and `soft_link`. ([#25](https://github.com/andrewhickman/fs-err/pull/25)) 71 | * Added aliases to `std::path::Path` via extension trait ([#26](https://github.com/andrewhickman/fs-err/pull/26)) 72 | * Added `OpenOptions` ([#27](https://github.com/andrewhickman/fs-err/pull/27)) 73 | * Added `set_permissions` ([#28](https://github.com/andrewhickman/fs-err/pull/28)) 74 | 75 | ## 2.3.0 76 | * Added `create_dir` and `create_dir_all`. ([#19](https://github.com/andrewhickman/fs-err/pull/19)) 77 | * Added `remove_file`, `remove_dir`, and `remove_dir_all`. ([#16](https://github.com/andrewhickman/fs-err/pull/16)) 78 | 79 | ## 2.2.0 80 | * Added `metadata`. ([#15](https://github.com/andrewhickman/fs-err/pull/15)) 81 | 82 | ## 2.1.0 83 | * Updated crate-level documentation. ([#8](https://github.com/andrewhickman/fs-err/pull/8)) 84 | * Added `read_dir`, `ReadDir`, and `DirEntry`. ([#9](https://github.com/andrewhickman/fs-err/pull/9)) 85 | 86 | ## 2.0.1 (2020-02-22) 87 | * Added `copy`. ([#7](https://github.com/andrewhickman/fs-err/pull/7)) 88 | 89 | ## 2.0.0 (2020-02-19) 90 | * Removed custom error type in favor of `std::io::Error`. ([#2](https://github.com/andrewhickman/fs-err/pull/2)) 91 | 92 | ## 1.0.1 (2020-02-15) 93 | * Fixed bad documentation link in `Cargo.toml`. 94 | 95 | ## 1.0.0 (2020-02-15) 96 | * No changes from 0.1.2. 97 | 98 | ## 0.1.2 (2020-02-10) 99 | * Added `Error::cause` implementation for `fs_err::Error`. 100 | 101 | ## 0.1.1 (2020-02-05) 102 | * Added wrappers for `std::fs::*` functions. 103 | 104 | ## 0.1.0 (2020-02-02) 105 | * Initial release, containing a wrapper around `std::fs::File`. 106 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.20.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.4.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 25 | 26 | [[package]] 27 | name = "backtrace" 28 | version = "0.3.68" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" 31 | dependencies = [ 32 | "addr2line", 33 | "cc", 34 | "cfg-if", 35 | "libc", 36 | "miniz_oxide", 37 | "object", 38 | "rustc-demangle", 39 | ] 40 | 41 | [[package]] 42 | name = "cc" 43 | version = "1.1.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "907d8581360765417f8f2e0e7d602733bbed60156b4465b7617243689ef9b83d" 46 | 47 | [[package]] 48 | name = "cfg-if" 49 | version = "1.0.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 52 | 53 | [[package]] 54 | name = "fs-err" 55 | version = "3.1.0" 56 | dependencies = [ 57 | "autocfg", 58 | "serde_json", 59 | "tokio", 60 | ] 61 | 62 | [[package]] 63 | name = "gimli" 64 | version = "0.27.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 67 | 68 | [[package]] 69 | name = "itoa" 70 | version = "1.0.11" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 73 | 74 | [[package]] 75 | name = "libc" 76 | version = "0.2.161" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 79 | 80 | [[package]] 81 | name = "memchr" 82 | version = "2.7.4" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 85 | 86 | [[package]] 87 | name = "miniz_oxide" 88 | version = "0.7.4" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 91 | dependencies = [ 92 | "adler", 93 | ] 94 | 95 | [[package]] 96 | name = "object" 97 | version = "0.31.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" 100 | dependencies = [ 101 | "memchr", 102 | ] 103 | 104 | [[package]] 105 | name = "pin-project-lite" 106 | version = "0.2.14" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 109 | 110 | [[package]] 111 | name = "proc-macro2" 112 | version = "1.0.88" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" 115 | dependencies = [ 116 | "unicode-ident", 117 | ] 118 | 119 | [[package]] 120 | name = "quote" 121 | version = "1.0.37" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 124 | dependencies = [ 125 | "proc-macro2", 126 | ] 127 | 128 | [[package]] 129 | name = "rustc-demangle" 130 | version = "0.1.24" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 133 | 134 | [[package]] 135 | name = "ryu" 136 | version = "1.0.18" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 139 | 140 | [[package]] 141 | name = "serde" 142 | version = "1.0.210" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 145 | dependencies = [ 146 | "serde_derive", 147 | ] 148 | 149 | [[package]] 150 | name = "serde_derive" 151 | version = "1.0.210" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 154 | dependencies = [ 155 | "proc-macro2", 156 | "quote", 157 | "syn", 158 | ] 159 | 160 | [[package]] 161 | name = "serde_json" 162 | version = "1.0.100" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" 165 | dependencies = [ 166 | "itoa", 167 | "ryu", 168 | "serde", 169 | ] 170 | 171 | [[package]] 172 | name = "syn" 173 | version = "2.0.56" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "6e2415488199887523e74fd9a5f7be804dfd42d868ae0eca382e3917094d210e" 176 | dependencies = [ 177 | "proc-macro2", 178 | "quote", 179 | "unicode-ident", 180 | ] 181 | 182 | [[package]] 183 | name = "tokio" 184 | version = "1.40.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 187 | dependencies = [ 188 | "backtrace", 189 | "pin-project-lite", 190 | ] 191 | 192 | [[package]] 193 | name = "unicode-ident" 194 | version = "1.0.13" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 197 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fs-err" 3 | description = "A drop-in replacement for std::fs with more helpful error messages." 4 | version = "3.1.0" 5 | authors = ["Andrew Hickman "] 6 | edition = "2018" 7 | repository = "https://github.com/andrewhickman/fs-err" 8 | documentation = "https://docs.rs/fs-err" 9 | categories = ["command-line-interface", "filesystem"] 10 | license = "MIT/Apache-2.0" 11 | readme = "README.md" 12 | exclude = [".github", ".gitignore", "README.tpl"] 13 | 14 | [dependencies] 15 | tokio = { version = "1.21", optional = true, default-features = false, features = ["fs"] } 16 | 17 | [build-dependencies] 18 | autocfg = "1" 19 | 20 | [dev-dependencies] 21 | serde_json = "1.0.64" 22 | 23 | [features] 24 | # Allow custom formatting of the error source 25 | # 26 | # When enabled errors emit `std::error::Error::source()` as Some (default is `None`) and 27 | # no longer include the original `std::io::Error` source in the `Display` implementation. 28 | # This is useful if errors are wrapped in another library such as Anyhow. 29 | expose_original_error = [] 30 | 31 | [package.metadata.release] 32 | tag-name = "{{version}}" 33 | sign-tag = true 34 | 35 | [[package.metadata.release.pre-release-replacements]] 36 | file = "src/lib.rs" 37 | search = "html_root_url = \"https://docs\\.rs/fs-err/.*?\"" 38 | replace = "html_root_url = \"https://docs.rs/fs-err/{{version}}\"" 39 | exactly = 1 40 | 41 | [package.metadata.docs.rs] 42 | all-features = true 43 | rustdoc-args = ["--cfg", "docsrs"] 44 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # fs-err 8 | 9 | [![Crates.io](https://img.shields.io/crates/v/fs-err.svg)](https://crates.io/crates/fs-err) 10 | [![GitHub Actions](https://github.com/andrewhickman/fs-err/workflows/CI/badge.svg)](https://github.com/andrewhickman/fs-err/actions?query=workflow%3ACI) 11 | 12 | fs-err is a drop-in replacement for [`std::fs`][std::fs] that provides more 13 | helpful messages on errors. Extra information includes which operations was 14 | attempted and any involved paths. 15 | 16 | ## Error Messages 17 | 18 | Using [`std::fs`][std::fs], if this code fails: 19 | 20 | ```rust 21 | let file = File::open("does not exist.txt")?; 22 | ``` 23 | 24 | The error message that Rust gives you isn't very useful: 25 | 26 | ```txt 27 | The system cannot find the file specified. (os error 2) 28 | ``` 29 | 30 | ...but if we use fs-err instead, our error contains more actionable information: 31 | 32 | ```txt 33 | failed to open file `does not exist.txt`: The system cannot find the file specified. (os error 2) 34 | ``` 35 | 36 | ## Usage 37 | 38 | fs-err's API is the same as [`std::fs`][std::fs], so migrating code to use it is easy. 39 | 40 | ```rust 41 | // use std::fs; 42 | use fs_err as fs; 43 | 44 | let contents = fs::read_to_string("foo.txt")?; 45 | 46 | println!("Read foo.txt: {}", contents); 47 | 48 | ``` 49 | 50 | fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err 51 | compose well with traits from the standard library like 52 | [`std::io::Read`][std::io::Read] and crates that use them like 53 | [`serde_json`][serde_json]: 54 | 55 | ```rust 56 | use fs_err::File; 57 | 58 | let file = File::open("my-config.json")?; 59 | 60 | // If an I/O error occurs inside serde_json, the error will include a file path 61 | // as well as what operation was being performed. 62 | let decoded: Vec = serde_json::from_reader(file)?; 63 | 64 | println!("Program config: {:?}", decoded); 65 | 66 | ``` 67 | 68 | ## Feature flags 69 | 70 | * `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, 71 | this also suppresses printing its message in their `Display` implementation, so make sure that you are printing the full error chain. 72 | 73 | 74 | ## Minimum Supported Rust Version 75 | 76 | The oldest rust version this crate is tested on is **1.40**. 77 | 78 | This 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. 79 | 80 | If the `tokio` feature is enabled, this crate will inherit the MSRV of the selected [`tokio`](https://crates.io/crates/tokio) version. 81 | 82 | [std::fs]: https://doc.rust-lang.org/stable/std/fs/ 83 | [std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 84 | [std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html 85 | [serde_json]: https://crates.io/crates/serde_json 86 | 87 | ## License 88 | 89 | Licensed under either of 90 | 91 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) 92 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 93 | 94 | at your option. 95 | 96 | ### Contribution 97 | 98 | Unless you explicitly state otherwise, any contribution intentionally 99 | submitted for inclusion in the work by you, as defined in the Apache-2.0 100 | license, shall be dual licensed as above, without any additional terms or 101 | conditions. 102 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | 6 | 7 | # {{crate}} 8 | 9 | [![Crates.io](https://img.shields.io/crates/v/fs-err.svg)](https://crates.io/crates/fs-err) 10 | [![GitHub Actions](https://github.com/andrewhickman/fs-err/workflows/CI/badge.svg)](https://github.com/andrewhickman/fs-err/actions?query=workflow%3ACI) 11 | 12 | {{readme}} 13 | 14 | ## License 15 | 16 | Licensed under either of 17 | 18 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) 19 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 20 | 21 | at your option. 22 | 23 | ### Contribution 24 | 25 | Unless you explicitly state otherwise, any contribution intentionally 26 | submitted for inclusion in the work by you, as defined in the Apache-2.0 27 | license, shall be dual licensed as above, without any additional terms or 28 | conditions. -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate autocfg; 2 | 3 | fn main() { 4 | let ac = autocfg::new(); 5 | // Allows `#[cfg(rustc_1_63)]` to be used in code 6 | ac.emit_rustc_version(1, 63); 7 | 8 | // Re-run if this file changes 9 | autocfg::rerun_path("build.rs"); 10 | } 11 | -------------------------------------------------------------------------------- /src/dir.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsString; 2 | use std::fs; 3 | use std::io; 4 | use std::path::PathBuf; 5 | 6 | use crate::errors::{Error, ErrorKind}; 7 | 8 | /// Returns an iterator over the entries within a directory. 9 | /// 10 | /// Wrapper for [`fs::read_dir`](https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html). 11 | pub fn read_dir>(path: P) -> io::Result { 12 | let path = path.into(); 13 | 14 | match fs::read_dir(&path) { 15 | Ok(inner) => Ok(ReadDir { inner, path }), 16 | Err(source) => Err(Error::build(source, ErrorKind::ReadDir, path)), 17 | } 18 | } 19 | 20 | /// Wrapper around [`std::fs::ReadDir`][std::fs::ReadDir] which adds more 21 | /// helpful information to all errors. 22 | /// 23 | /// This struct is created via [`fs_err::read_dir`][fs_err::read_dir]. 24 | /// 25 | /// [std::fs::ReadDir]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html 26 | /// [fs_err::read_dir]: fn.read_dir.html 27 | #[derive(Debug)] 28 | pub struct ReadDir { 29 | inner: fs::ReadDir, 30 | path: PathBuf, 31 | } 32 | 33 | impl Iterator for ReadDir { 34 | type Item = io::Result; 35 | 36 | fn next(&mut self) -> Option { 37 | Some( 38 | self.inner 39 | .next()? 40 | .map_err(|source| Error::build(source, ErrorKind::ReadDir, &self.path)) 41 | .map(|inner| DirEntry { inner }), 42 | ) 43 | } 44 | } 45 | 46 | /// Wrapper around [`std::fs::DirEntry`][std::fs::DirEntry] which adds more 47 | /// helpful information to all errors. 48 | /// 49 | /// [std::fs::DirEntry]: https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html 50 | #[derive(Debug)] 51 | pub struct DirEntry { 52 | inner: fs::DirEntry, 53 | } 54 | 55 | impl DirEntry { 56 | /// Returns the full path to the file that this entry represents. 57 | /// 58 | /// Wrapper for [`DirEntry::path`](https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html#method.path). 59 | pub fn path(&self) -> PathBuf { 60 | self.inner.path() 61 | } 62 | 63 | /// Returns the metadata for the file that this entry points at. 64 | /// 65 | /// Wrapper for [`DirEntry::metadata`](https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html#method.metadata). 66 | pub fn metadata(&self) -> io::Result { 67 | self.inner 68 | .metadata() 69 | .map_err(|source| Error::build(source, ErrorKind::Metadata, self.path())) 70 | } 71 | 72 | /// Returns the file type for the file that this entry points at. 73 | /// 74 | /// Wrapper for [`DirEntry::file_type`](https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html#method.file_type). 75 | pub fn file_type(&self) -> io::Result { 76 | self.inner 77 | .file_type() 78 | .map_err(|source| Error::build(source, ErrorKind::Metadata, self.path())) 79 | } 80 | 81 | /// Returns the file name of this directory entry without any leading path component(s). 82 | /// 83 | /// Wrapper for [`DirEntry::file_name`](https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html#method.file_name). 84 | pub fn file_name(&self) -> OsString { 85 | self.inner.file_name() 86 | } 87 | } 88 | 89 | #[cfg(unix)] 90 | mod unix { 91 | use std::os::unix::fs::DirEntryExt; 92 | 93 | use super::*; 94 | 95 | impl DirEntryExt for DirEntry { 96 | fn ino(&self) -> u64 { 97 | self.inner.ino() 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | use std::io; 4 | use std::path::PathBuf; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub(crate) enum ErrorKind { 8 | OpenFile, 9 | CreateFile, 10 | CreateDir, 11 | SyncFile, 12 | SetLen, 13 | Metadata, 14 | Clone, 15 | SetPermissions, 16 | Read, 17 | Seek, 18 | Write, 19 | Flush, 20 | ReadDir, 21 | RemoveFile, 22 | RemoveDir, 23 | Canonicalize, 24 | ReadLink, 25 | SymlinkMetadata, 26 | #[allow(dead_code)] 27 | FileExists, 28 | 29 | #[cfg(windows)] 30 | SeekRead, 31 | #[cfg(windows)] 32 | SeekWrite, 33 | 34 | #[cfg(unix)] 35 | ReadAt, 36 | #[cfg(unix)] 37 | WriteAt, 38 | } 39 | 40 | /// Contains an IO error that has a file path attached. 41 | /// 42 | /// This type is never returned directly, but is instead wrapped inside yet 43 | /// another IO error. 44 | #[derive(Debug)] 45 | pub(crate) struct Error { 46 | kind: ErrorKind, 47 | source: io::Error, 48 | path: PathBuf, 49 | } 50 | 51 | impl Error { 52 | pub fn build(source: io::Error, kind: ErrorKind, path: impl Into) -> io::Error { 53 | io::Error::new( 54 | source.kind(), 55 | Self { 56 | kind, 57 | source, 58 | path: path.into(), 59 | }, 60 | ) 61 | } 62 | } 63 | 64 | impl fmt::Display for Error { 65 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 66 | use ErrorKind as E; 67 | 68 | let path = self.path.display(); 69 | 70 | match self.kind { 71 | E::OpenFile => write!(formatter, "failed to open file `{}`", path), 72 | E::CreateFile => write!(formatter, "failed to create file `{}`", path), 73 | E::CreateDir => write!(formatter, "failed to create directory `{}`", path), 74 | E::SyncFile => write!(formatter, "failed to sync file `{}`", path), 75 | E::SetLen => write!(formatter, "failed to set length of file `{}`", path), 76 | E::Metadata => write!(formatter, "failed to query metadata of file `{}`", path), 77 | E::Clone => write!(formatter, "failed to clone handle for file `{}`", path), 78 | E::SetPermissions => write!(formatter, "failed to set permissions for file `{}`", path), 79 | E::Read => write!(formatter, "failed to read from file `{}`", path), 80 | E::Seek => write!(formatter, "failed to seek in file `{}`", path), 81 | E::Write => write!(formatter, "failed to write to file `{}`", path), 82 | E::Flush => write!(formatter, "failed to flush file `{}`", path), 83 | E::ReadDir => write!(formatter, "failed to read directory `{}`", path), 84 | E::RemoveFile => write!(formatter, "failed to remove file `{}`", path), 85 | E::RemoveDir => write!(formatter, "failed to remove directory `{}`", path), 86 | E::Canonicalize => write!(formatter, "failed to canonicalize path `{}`", path), 87 | E::ReadLink => write!(formatter, "failed to read symbolic link `{}`", path), 88 | E::SymlinkMetadata => { 89 | write!(formatter, "failed to query metadata of symlink `{}`", path) 90 | } 91 | E::FileExists => write!(formatter, "failed to check file existence `{}`", path), 92 | 93 | #[cfg(windows)] 94 | E::SeekRead => write!(formatter, "failed to seek and read from `{}`", path), 95 | #[cfg(windows)] 96 | E::SeekWrite => write!(formatter, "failed to seek and write to `{}`", path), 97 | 98 | #[cfg(unix)] 99 | E::ReadAt => write!(formatter, "failed to read with offset from `{}`", path), 100 | #[cfg(unix)] 101 | E::WriteAt => write!(formatter, "failed to write with offset to `{}`", path), 102 | }?; 103 | 104 | // The `expose_original_error` feature indicates the caller should display the original error 105 | #[cfg(not(feature = "expose_original_error"))] 106 | write!(formatter, ": {}", self.source)?; 107 | 108 | Ok(()) 109 | } 110 | } 111 | 112 | impl StdError for Error { 113 | fn cause(&self) -> Option<&dyn StdError> { 114 | self.source() 115 | } 116 | 117 | #[cfg(not(feature = "expose_original_error"))] 118 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 119 | None 120 | } 121 | 122 | #[cfg(feature = "expose_original_error")] 123 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 124 | Some(&self.source) 125 | } 126 | } 127 | 128 | #[derive(Debug, Clone, Copy)] 129 | pub(crate) enum SourceDestErrorKind { 130 | Copy, 131 | HardLink, 132 | Rename, 133 | SoftLink, 134 | 135 | #[cfg(unix)] 136 | Symlink, 137 | 138 | #[cfg(windows)] 139 | SymlinkDir, 140 | #[cfg(windows)] 141 | SymlinkFile, 142 | } 143 | 144 | /// Error type used by functions like `fs::copy` that holds two paths. 145 | #[derive(Debug)] 146 | pub(crate) struct SourceDestError { 147 | kind: SourceDestErrorKind, 148 | source: io::Error, 149 | from_path: PathBuf, 150 | to_path: PathBuf, 151 | } 152 | 153 | impl SourceDestError { 154 | pub fn build( 155 | source: io::Error, 156 | kind: SourceDestErrorKind, 157 | from_path: impl Into, 158 | to_path: impl Into, 159 | ) -> io::Error { 160 | io::Error::new( 161 | source.kind(), 162 | Self { 163 | kind, 164 | source, 165 | from_path: from_path.into(), 166 | to_path: to_path.into(), 167 | }, 168 | ) 169 | } 170 | } 171 | 172 | impl fmt::Display for SourceDestError { 173 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 174 | let from = self.from_path.display(); 175 | let to = self.to_path.display(); 176 | match self.kind { 177 | SourceDestErrorKind::Copy => { 178 | write!(formatter, "failed to copy file from {} to {}", from, to) 179 | } 180 | SourceDestErrorKind::HardLink => { 181 | write!(formatter, "failed to hardlink file from {} to {}", from, to) 182 | } 183 | SourceDestErrorKind::Rename => { 184 | write!(formatter, "failed to rename file from {} to {}", from, to) 185 | } 186 | SourceDestErrorKind::SoftLink => { 187 | write!(formatter, "failed to softlink file from {} to {}", from, to) 188 | } 189 | 190 | #[cfg(unix)] 191 | SourceDestErrorKind::Symlink => { 192 | write!(formatter, "failed to symlink file from {} to {}", from, to) 193 | } 194 | 195 | #[cfg(windows)] 196 | SourceDestErrorKind::SymlinkFile => { 197 | write!(formatter, "failed to symlink file from {} to {}", from, to) 198 | } 199 | #[cfg(windows)] 200 | SourceDestErrorKind::SymlinkDir => { 201 | write!(formatter, "failed to symlink dir from {} to {}", from, to) 202 | } 203 | }?; 204 | 205 | // The `expose_original_error` feature indicates the caller should display the original error 206 | #[cfg(not(feature = "expose_original_error"))] 207 | write!(formatter, ": {}", self.source)?; 208 | 209 | Ok(()) 210 | } 211 | } 212 | 213 | impl StdError for SourceDestError { 214 | fn cause(&self) -> Option<&dyn StdError> { 215 | self.source() 216 | } 217 | 218 | #[cfg(not(feature = "expose_original_error"))] 219 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 220 | None 221 | } 222 | 223 | #[cfg(feature = "expose_original_error")] 224 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 225 | Some(&self.source) 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::{self, Read, Seek, Write}; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use crate::errors::{Error, ErrorKind}; 6 | use crate::OpenOptions; 7 | 8 | /// Wrapper around [`std::fs::File`][std::fs::File] which adds more helpful 9 | /// information to all errors. 10 | /// 11 | /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html 12 | #[derive(Debug)] 13 | pub struct File { 14 | file: fs::File, 15 | path: PathBuf, 16 | } 17 | 18 | // Opens a std File and returns it or an error generator which only needs the path to produce the error. 19 | // Exists for the `crate::read*` functions so they don't unconditionally build a PathBuf. 20 | pub(crate) fn open(path: &Path) -> Result io::Error> { 21 | fs::File::open(path).map_err(|err| |path| Error::build(err, ErrorKind::OpenFile, path)) 22 | } 23 | 24 | // like `open()` but for `crate::write` 25 | pub(crate) fn create(path: &Path) -> Result io::Error> { 26 | fs::File::create(path).map_err(|err| |path| Error::build(err, ErrorKind::CreateFile, path)) 27 | } 28 | 29 | /// Wrappers for methods from [`std::fs::File`][std::fs::File]. 30 | /// 31 | /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html 32 | impl File { 33 | /// Attempts to open a file in read-only mode. 34 | /// 35 | /// Wrapper for [`File::open`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.open). 36 | pub fn open

(path: P) -> Result 37 | where 38 | P: Into, 39 | { 40 | let path = path.into(); 41 | match open(&path) { 42 | Ok(file) => Ok(File::from_parts(file, path)), 43 | Err(err_gen) => Err(err_gen(path)), 44 | } 45 | } 46 | 47 | /// Opens a file in write-only mode. 48 | /// 49 | /// Wrapper for [`File::create`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.create). 50 | pub fn create

(path: P) -> Result 51 | where 52 | P: Into, 53 | { 54 | let path = path.into(); 55 | match create(&path) { 56 | Ok(file) => Ok(File::from_parts(file, path)), 57 | Err(err_gen) => Err(err_gen(path)), 58 | } 59 | } 60 | 61 | /// Opens a file in read-write mode. 62 | /// 63 | /// Wrapper for [`File::create_new`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.create_new). 64 | pub fn create_new

(path: P) -> Result 65 | where 66 | P: Into, 67 | { 68 | let path = path.into(); 69 | // TODO: Use fs::File::create_new once MSRV is at least 1.77 70 | match fs::OpenOptions::new() 71 | .read(true) 72 | .write(true) 73 | .create_new(true) 74 | .open(&path) 75 | { 76 | Ok(file) => Ok(File::from_parts(file, path)), 77 | Err(err) => Err(Error::build(err, ErrorKind::CreateFile, path)), 78 | } 79 | } 80 | 81 | /// Returns a new `OpenOptions` object. 82 | /// 83 | /// Wrapper for [`File::options`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.options). 84 | pub fn options() -> OpenOptions { 85 | OpenOptions::new() 86 | } 87 | 88 | /// Attempts to sync all OS-internal metadata to disk. 89 | /// 90 | /// Wrapper for [`File::sync_all`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.sync_all). 91 | pub fn sync_all(&self) -> Result<(), io::Error> { 92 | self.file 93 | .sync_all() 94 | .map_err(|source| self.error(source, ErrorKind::SyncFile)) 95 | } 96 | 97 | /// This function is similar to [`sync_all`], except that it might not synchronize file metadata to the filesystem. 98 | /// 99 | /// Wrapper for [`File::sync_data`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.sync_data). 100 | pub fn sync_data(&self) -> Result<(), io::Error> { 101 | self.file 102 | .sync_data() 103 | .map_err(|source| self.error(source, ErrorKind::SyncFile)) 104 | } 105 | 106 | /// Truncates or extends the underlying file, updating the size of this file to become `size`. 107 | /// 108 | /// Wrapper for [`File::set_len`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.set_len). 109 | pub fn set_len(&self, size: u64) -> Result<(), io::Error> { 110 | self.file 111 | .set_len(size) 112 | .map_err(|source| self.error(source, ErrorKind::SetLen)) 113 | } 114 | 115 | /// Queries metadata about the underlying file. 116 | /// 117 | /// Wrapper for [`File::metadata`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.metadata). 118 | pub fn metadata(&self) -> Result { 119 | self.file 120 | .metadata() 121 | .map_err(|source| self.error(source, ErrorKind::Metadata)) 122 | } 123 | 124 | /// Creates a new `File` instance that shares the same underlying file handle as the 125 | /// existing `File` instance. Reads, writes, and seeks will affect both `File` 126 | /// instances simultaneously. 127 | /// 128 | /// Wrapper for [`File::try_clone`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.try_clone). 129 | pub fn try_clone(&self) -> Result { 130 | self.file 131 | .try_clone() 132 | .map(|file| File { 133 | file, 134 | path: self.path.clone(), 135 | }) 136 | .map_err(|source| self.error(source, ErrorKind::Clone)) 137 | } 138 | 139 | /// Changes the permissions on the underlying file. 140 | /// 141 | /// Wrapper for [`File::set_permissions`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.set_permissions). 142 | pub fn set_permissions(&self, perm: fs::Permissions) -> Result<(), io::Error> { 143 | self.file 144 | .set_permissions(perm) 145 | .map_err(|source| self.error(source, ErrorKind::SetPermissions)) 146 | } 147 | } 148 | 149 | /// Methods added by fs-err that are not available on 150 | /// [`std::fs::File`][std::fs::File]. 151 | /// 152 | /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html 153 | impl File { 154 | /// Creates a [`File`](struct.File.html) from a raw file and its path. 155 | pub fn from_parts

(file: fs::File, path: P) -> Self 156 | where 157 | P: Into, 158 | { 159 | File { 160 | file, 161 | path: path.into(), 162 | } 163 | } 164 | 165 | /// Extract the raw file and its path from this [`File`](struct.File.html) 166 | pub fn into_parts(self) -> (fs::File, PathBuf) { 167 | (self.file, self.path) 168 | } 169 | 170 | /// Returns a reference to the underlying [`std::fs::File`][std::fs::File]. 171 | /// 172 | /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html 173 | pub fn file(&self) -> &fs::File { 174 | &self.file 175 | } 176 | 177 | /// Returns a mutable reference to the underlying [`std::fs::File`][std::fs::File]. 178 | /// 179 | /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html 180 | pub fn file_mut(&mut self) -> &mut fs::File { 181 | &mut self.file 182 | } 183 | 184 | /// Returns a reference to the path that this file was created with. 185 | pub fn path(&self) -> &Path { 186 | &self.path 187 | } 188 | 189 | /// Wrap the error in information specific to this `File` object. 190 | fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error { 191 | Error::build(source, kind, &self.path) 192 | } 193 | } 194 | 195 | impl Read for File { 196 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 197 | self.file 198 | .read(buf) 199 | .map_err(|source| self.error(source, ErrorKind::Read)) 200 | } 201 | 202 | fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { 203 | self.file 204 | .read_vectored(bufs) 205 | .map_err(|source| self.error(source, ErrorKind::Read)) 206 | } 207 | } 208 | 209 | impl Read for &File { 210 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 211 | (&self.file) 212 | .read(buf) 213 | .map_err(|source| self.error(source, ErrorKind::Read)) 214 | } 215 | 216 | fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { 217 | (&self.file) 218 | .read_vectored(bufs) 219 | .map_err(|source| self.error(source, ErrorKind::Read)) 220 | } 221 | } 222 | 223 | impl From for fs::File { 224 | fn from(file: File) -> Self { 225 | file.into_parts().0 226 | } 227 | } 228 | 229 | impl Seek for File { 230 | fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { 231 | self.file 232 | .seek(pos) 233 | .map_err(|source| self.error(source, ErrorKind::Seek)) 234 | } 235 | } 236 | 237 | impl Seek for &File { 238 | fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { 239 | (&self.file) 240 | .seek(pos) 241 | .map_err(|source| self.error(source, ErrorKind::Seek)) 242 | } 243 | } 244 | 245 | impl Write for File { 246 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 247 | self.file 248 | .write(buf) 249 | .map_err(|source| self.error(source, ErrorKind::Write)) 250 | } 251 | 252 | fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { 253 | self.file 254 | .write_vectored(bufs) 255 | .map_err(|source| self.error(source, ErrorKind::Write)) 256 | } 257 | 258 | fn flush(&mut self) -> std::io::Result<()> { 259 | self.file 260 | .flush() 261 | .map_err(|source| self.error(source, ErrorKind::Flush)) 262 | } 263 | } 264 | 265 | impl Write for &File { 266 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 267 | (&self.file) 268 | .write(buf) 269 | .map_err(|source| self.error(source, ErrorKind::Write)) 270 | } 271 | 272 | fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { 273 | (&self.file) 274 | .write_vectored(bufs) 275 | .map_err(|source| self.error(source, ErrorKind::Write)) 276 | } 277 | 278 | fn flush(&mut self) -> std::io::Result<()> { 279 | (&self.file) 280 | .flush() 281 | .map_err(|source| self.error(source, ErrorKind::Flush)) 282 | } 283 | } 284 | 285 | #[cfg(unix)] 286 | mod unix { 287 | use crate::os::unix::fs::FileExt; 288 | use crate::ErrorKind; 289 | use std::io; 290 | use std::os::unix::fs::FileExt as _; 291 | use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; 292 | 293 | impl AsRawFd for crate::File { 294 | fn as_raw_fd(&self) -> RawFd { 295 | self.file().as_raw_fd() 296 | } 297 | } 298 | 299 | impl IntoRawFd for crate::File { 300 | fn into_raw_fd(self) -> RawFd { 301 | self.file.into_raw_fd() 302 | } 303 | } 304 | 305 | impl FileExt for crate::File { 306 | fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { 307 | self.file() 308 | .read_at(buf, offset) 309 | .map_err(|err| self.error(err, ErrorKind::ReadAt)) 310 | } 311 | fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { 312 | self.file() 313 | .write_at(buf, offset) 314 | .map_err(|err| self.error(err, ErrorKind::WriteAt)) 315 | } 316 | } 317 | 318 | #[cfg(rustc_1_63)] 319 | mod io_safety { 320 | use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd}; 321 | 322 | impl AsFd for crate::File { 323 | fn as_fd(&self) -> BorrowedFd<'_> { 324 | self.file().as_fd() 325 | } 326 | } 327 | 328 | impl From for OwnedFd { 329 | fn from(file: crate::File) -> Self { 330 | file.into_parts().0.into() 331 | } 332 | } 333 | } 334 | } 335 | 336 | #[cfg(windows)] 337 | mod windows { 338 | use crate::os::windows::fs::FileExt; 339 | use crate::ErrorKind; 340 | use std::io; 341 | use std::os::windows::{ 342 | fs::FileExt as _, 343 | io::{AsRawHandle, IntoRawHandle, RawHandle}, 344 | }; 345 | 346 | impl FileExt for crate::File { 347 | fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { 348 | self.file() 349 | .seek_read(buf, offset) 350 | .map_err(|err| self.error(err, ErrorKind::SeekRead)) 351 | } 352 | 353 | fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { 354 | self.file() 355 | .seek_write(buf, offset) 356 | .map_err(|err| self.error(err, ErrorKind::SeekWrite)) 357 | } 358 | } 359 | 360 | impl AsRawHandle for crate::File { 361 | fn as_raw_handle(&self) -> RawHandle { 362 | self.file().as_raw_handle() 363 | } 364 | } 365 | 366 | // can't be implemented, because the trait doesn't give us a Path 367 | // impl std::os::windows::io::FromRawHandle for crate::File { 368 | // } 369 | 370 | impl IntoRawHandle for crate::File { 371 | fn into_raw_handle(self) -> RawHandle { 372 | self.file.into_raw_handle() 373 | } 374 | } 375 | 376 | #[cfg(rustc_1_63)] 377 | mod io_safety { 378 | use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle}; 379 | 380 | impl AsHandle for crate::File { 381 | fn as_handle(&self) -> BorrowedHandle<'_> { 382 | self.file().as_handle() 383 | } 384 | } 385 | 386 | impl From for OwnedHandle { 387 | fn from(file: crate::File) -> Self { 388 | file.into_parts().0.into() 389 | } 390 | } 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 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`: The system cannot find the file specified. (os error 2) 26 | ``` 27 | 28 | # Usage 29 | 30 | fs-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; 34 | use fs_err as fs; 35 | 36 | let contents = fs::read_to_string("foo.txt")?; 37 | 38 | println!("Read foo.txt: {}", contents); 39 | 40 | # Ok::<(), std::io::Error>(()) 41 | ``` 42 | 43 | fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err 44 | compose 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 49 | use fs_err::File; 50 | 51 | let 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. 55 | let decoded: Vec = serde_json::from_reader(file)?; 56 | 57 | println!("Program config: {:?}", decoded); 58 | 59 | # Ok::<(), Box>(()) 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 | 70 | The oldest rust version this crate is tested on is **1.40**. 71 | 72 | This 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 | 74 | If 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 | 86 | mod dir; 87 | mod errors; 88 | mod file; 89 | mod open_options; 90 | pub mod os; 91 | mod path; 92 | #[cfg(feature = "tokio")] 93 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 94 | pub mod tokio; 95 | 96 | use std::fs; 97 | use std::io::{self, Read, Write}; 98 | use std::path::{Path, PathBuf}; 99 | 100 | use errors::{Error, ErrorKind, SourceDestError, SourceDestErrorKind}; 101 | 102 | pub use dir::*; 103 | pub use file::*; 104 | pub use open_options::OpenOptions; 105 | pub 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). 110 | pub fn read>(path: P) -> io::Result> { 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). 122 | pub fn read_to_string>(path: P) -> io::Result { 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). 134 | pub fn write, 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). 146 | pub fn copy(from: P, to: Q) -> io::Result 147 | where 148 | P: AsRef, 149 | Q: AsRef, 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). 160 | pub fn create_dir

(path: P) -> io::Result<()> 161 | where 162 | P: AsRef, 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). 171 | pub fn create_dir_all

(path: P) -> io::Result<()> 172 | where 173 | P: AsRef, 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). 182 | pub fn remove_dir

(path: P) -> io::Result<()> 183 | where 184 | P: AsRef, 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). 193 | pub fn remove_dir_all

(path: P) -> io::Result<()> 194 | where 195 | P: AsRef, 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). 204 | pub fn remove_file

(path: P) -> io::Result<()> 205 | where 206 | P: AsRef, 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). 215 | pub fn metadata>(path: P) -> io::Result { 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). 224 | pub fn canonicalize>(path: P) -> io::Result { 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). 232 | pub fn hard_link, Q: AsRef>(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). 242 | pub fn read_link>(path: P) -> io::Result { 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). 250 | pub fn rename, Q: AsRef>(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 \ 259 | std::os::windows::fs::{symlink_file, symlink_dir}"] 260 | pub fn soft_link, Q: AsRef>(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). 271 | pub fn symlink_metadata>(path: P) -> io::Result { 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). 280 | pub fn set_permissions>(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 | 286 | fn initial_buffer_size(file: &std::fs::File) -> usize { 287 | file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0) 288 | } 289 | 290 | pub(crate) use private::Sealed; 291 | mod 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 | } 298 | -------------------------------------------------------------------------------- /src/open_options.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io, path::PathBuf}; 2 | 3 | use crate::errors::{Error, ErrorKind}; 4 | 5 | #[derive(Clone, Debug)] 6 | /// Wrapper around [`std::fs::OpenOptions`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html) 7 | pub struct OpenOptions(fs::OpenOptions); 8 | 9 | impl OpenOptions { 10 | /// Creates a blank new set of options ready for configuration. 11 | /// 12 | /// Wrapper for [`std::fs::OpenOptions::new`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.new) 13 | #[allow(clippy::new_without_default)] 14 | pub fn new() -> Self { 15 | OpenOptions(fs::OpenOptions::new()) 16 | } 17 | 18 | /// Sets the option for read access. 19 | /// 20 | /// Wrapper for [`std::fs::OpenOptions::read`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.read) 21 | pub fn read(&mut self, read: bool) -> &mut Self { 22 | self.0.read(read); 23 | self 24 | } 25 | 26 | /// Sets the option for write access. 27 | /// 28 | /// Wrapper for [`std::fs::OpenOptions::write`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.write) 29 | pub fn write(&mut self, write: bool) -> &mut Self { 30 | self.0.write(write); 31 | self 32 | } 33 | 34 | /// Sets the option for the append mode. 35 | /// 36 | /// Wrapper for [`std::fs::OpenOptions::append`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.append) 37 | pub fn append(&mut self, append: bool) -> &mut Self { 38 | self.0.append(append); 39 | self 40 | } 41 | 42 | /// Sets the option for truncating a previous file. 43 | /// 44 | /// Wrapper for [`std::fs::OpenOptions::truncate`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.truncate) 45 | pub fn truncate(&mut self, truncate: bool) -> &mut Self { 46 | self.0.truncate(truncate); 47 | self 48 | } 49 | 50 | /// Sets the option to create a new file, or open it if it already exists. 51 | /// 52 | /// Wrapper for [`std::fs::OpenOptions::create`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create) 53 | pub fn create(&mut self, create: bool) -> &mut Self { 54 | self.0.create(create); 55 | self 56 | } 57 | 58 | /// Sets the option to create a new file, failing if it already exists. 59 | /// 60 | /// Wrapper for [`std::fs::OpenOptions::create_new`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new) 61 | pub fn create_new(&mut self, create_new: bool) -> &mut Self { 62 | self.0.create_new(create_new); 63 | self 64 | } 65 | 66 | /// Opens a file at `path` with the options specified by `self`. 67 | /// 68 | /// Wrapper for [`std::fs::OpenOptions::open`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.open) 69 | pub fn open

(&self, path: P) -> io::Result 70 | where 71 | P: Into, 72 | { 73 | let path = path.into(); 74 | match self.0.open(&path) { 75 | Ok(file) => Ok(crate::File::from_parts(file, path)), 76 | Err(source) => Err(Error::build(source, ErrorKind::OpenFile, path)), 77 | } 78 | } 79 | } 80 | 81 | /// Methods added by fs-err that are not available on 82 | /// [`std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html). 83 | impl OpenOptions { 84 | /// Constructs `Self` from [`std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html) 85 | pub fn from_options(options: fs::OpenOptions) -> Self { 86 | Self(options) 87 | } 88 | 89 | /// Returns a reference to the underlying [`std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html). 90 | /// 91 | /// Note that calling `open()` on this reference will NOT give you the improved errors from fs-err. 92 | pub fn options(&self) -> &fs::OpenOptions { 93 | &self.0 94 | } 95 | 96 | /// Returns a mutable reference to the underlying [`std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html). 97 | /// 98 | /// This allows you to change settings that don't yet have wrappers in fs-err. 99 | /// Note that calling `open()` on this reference will NOT give you the improved errors from fs-err. 100 | pub fn options_mut(&mut self) -> &mut fs::OpenOptions { 101 | &mut self.0 102 | } 103 | } 104 | 105 | #[cfg(unix)] 106 | mod unix { 107 | use crate::os::unix::fs::OpenOptionsExt; 108 | use std::os::unix::fs::OpenOptionsExt as _; 109 | impl OpenOptionsExt for crate::OpenOptions { 110 | fn mode(&mut self, mode: u32) -> &mut Self { 111 | self.options_mut().mode(mode); 112 | self 113 | } 114 | 115 | fn custom_flags(&mut self, flags: i32) -> &mut Self { 116 | self.options_mut().custom_flags(flags); 117 | self 118 | } 119 | } 120 | } 121 | 122 | #[cfg(windows)] 123 | mod windows { 124 | use crate::os::windows::fs::OpenOptionsExt; 125 | use std::os::windows::fs::OpenOptionsExt as _; 126 | 127 | impl OpenOptionsExt for crate::OpenOptions { 128 | fn access_mode(&mut self, access: u32) -> &mut Self { 129 | self.options_mut().access_mode(access); 130 | self 131 | } 132 | 133 | fn share_mode(&mut self, val: u32) -> &mut Self { 134 | self.options_mut().share_mode(val); 135 | self 136 | } 137 | fn custom_flags(&mut self, flags: u32) -> &mut Self { 138 | self.options_mut().custom_flags(flags); 139 | self 140 | } 141 | 142 | fn attributes(&mut self, val: u32) -> &mut Self { 143 | self.options_mut().attributes(val); 144 | self 145 | } 146 | 147 | fn security_qos_flags(&mut self, flags: u32) -> &mut Self { 148 | self.options_mut().security_qos_flags(flags); 149 | self 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/os.rs: -------------------------------------------------------------------------------- 1 | //! OS-specific functionality. 2 | 3 | // The std-library has a couple more platforms than just `unix` for which these apis 4 | // are defined, but we're using just `unix` here. We can always expand later. 5 | #[cfg(unix)] 6 | /// Platform-specific extensions for Unix platforms. 7 | pub mod unix; 8 | 9 | #[cfg(windows)] 10 | /// Platform-specific extensions for Windows. 11 | pub mod windows; 12 | -------------------------------------------------------------------------------- /src/os/unix.rs: -------------------------------------------------------------------------------- 1 | /// Unix-specific extensions to wrappers in `fs_err` for `std::fs` types. 2 | pub mod fs { 3 | use std::io; 4 | use std::path::Path; 5 | 6 | use crate::SourceDestError; 7 | use crate::SourceDestErrorKind; 8 | 9 | /// Creates a new symbolic link on the filesystem. 10 | /// 11 | /// Wrapper for [`std::os::unix::fs::symlink`](https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html) 12 | pub fn symlink, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 13 | let src = src.as_ref(); 14 | let dst = dst.as_ref(); 15 | std::os::unix::fs::symlink(src, dst) 16 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::Symlink, src, dst)) 17 | } 18 | 19 | /// Wrapper for [`std::os::unix::fs::FileExt`](https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html). 20 | /// 21 | /// The std traits might be extended in the future (See issue [#49961](https://github.com/rust-lang/rust/issues/49961#issuecomment-382751777)). 22 | /// This trait is sealed and can not be implemented by other crates. 23 | pub trait FileExt: crate::Sealed { 24 | /// Wrapper for [`FileExt::read_at`](https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.read_at) 25 | fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result; 26 | /// Wrapper for [`FileExt::write_at`](https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.write_at) 27 | fn write_at(&self, buf: &[u8], offset: u64) -> io::Result; 28 | } 29 | 30 | /// Wrapper for [`std::os::unix::fs::OpenOptionsExt`](https://doc.rust-lang.org/std/os/unix/fs/trait.OpenOptionsExt.html) 31 | /// 32 | /// The std traits might be extended in the future (See issue [#49961](https://github.com/rust-lang/rust/issues/49961#issuecomment-382751777)). 33 | /// This trait is sealed and can not be implemented by other crates. 34 | pub trait OpenOptionsExt: crate::Sealed { 35 | /// Wrapper for [`OpenOptionsExt::mode`](https://doc.rust-lang.org/std/os/unix/fs/trait.OpenOptionsExt.html#tymethod.mode) 36 | fn mode(&mut self, mode: u32) -> &mut Self; 37 | /// Wrapper for [`OpenOptionsExt::custom_flags`](https://doc.rust-lang.org/std/os/unix/fs/trait.OpenOptionsExt.html#tymethod.custom_flags) 38 | fn custom_flags(&mut self, flags: i32) -> &mut Self; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/os/windows.rs: -------------------------------------------------------------------------------- 1 | /// Windows-specific extensions to wrappers in `fs_err` for `std::fs` types. 2 | pub mod fs { 3 | use crate::{SourceDestError, SourceDestErrorKind}; 4 | use std::io; 5 | use std::path::Path; 6 | 7 | /// Creates a new symlink to a directory on the filesystem. 8 | /// 9 | /// Wrapper for [std::os::windows::fs::symlink_dir](https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html) 10 | pub fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 11 | let src = src.as_ref(); 12 | let dst = dst.as_ref(); 13 | std::os::windows::fs::symlink_dir(src, dst) 14 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::SymlinkDir, src, dst)) 15 | } 16 | 17 | /// Creates a new symlink to a non-directory file on the filesystem. 18 | /// 19 | /// Wrapper for [std::os::windows::fs::symlink_file](https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html) 20 | pub fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 21 | let src = src.as_ref(); 22 | let dst = dst.as_ref(); 23 | std::os::windows::fs::symlink_file(src, dst) 24 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::SymlinkFile, src, dst)) 25 | } 26 | 27 | /// Wrapper for [`std::os::windows::fs::FileExt`](https://doc.rust-lang.org/std/os/windows/fs/trait.FileExt.html). 28 | /// 29 | /// The std traits might be extended in the future (See issue [#49961](https://github.com/rust-lang/rust/issues/49961#issuecomment-382751777)). 30 | /// This trait is sealed and can not be implemented by other crates. 31 | pub trait FileExt: crate::Sealed { 32 | /// Wrapper for [`FileExt::seek_read`](https://doc.rust-lang.org/std/os/windows/fs/trait.FileExt.html#tymethod.seek_read) 33 | fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result; 34 | /// Wrapper for [`FileExt::seek_wriite`](https://doc.rust-lang.org/std/os/windows/fs/trait.FileExt.html#tymethod.seek_write) 35 | fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result; 36 | } 37 | 38 | /// Wrapper for [`std::os::windows::fs::OpenOptionsExt`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html) 39 | /// 40 | /// The std traits might be extended in the future (See issue [#49961](https://github.com/rust-lang/rust/issues/49961#issuecomment-382751777)). 41 | /// This trait is sealed and can not be implemented by other crates. 42 | pub trait OpenOptionsExt: crate::Sealed { 43 | /// Wrapper for [`OpenOptionsExt::access_mode`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.access_mode) 44 | fn access_mode(&mut self, access: u32) -> &mut Self; 45 | /// Wrapper for [`OpenOptionsExt::share_mode`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.share_mode) 46 | fn share_mode(&mut self, val: u32) -> &mut Self; 47 | /// Wrapper for [`OpenOptionsExt::custom_flags`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.custom_flags) 48 | fn custom_flags(&mut self, flags: u32) -> &mut Self; 49 | /// Wrapper for [`OpenOptionsExt::attributes`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.attributes) 50 | fn attributes(&mut self, val: u32) -> &mut Self; 51 | /// Wrapper for [`OpenOptionsExt::security_qos_flags`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.security_qos_flags) 52 | fn security_qos_flags(&mut self, flags: u32) -> &mut Self; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/path.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use crate::errors::{Error, ErrorKind}; 3 | use std::fs; 4 | use std::io; 5 | use std::path::{Path, PathBuf}; 6 | 7 | /// Defines aliases on [`Path`](https://doc.rust-lang.org/std/path/struct.Path.html) for `fs_err` functions. 8 | /// 9 | /// This trait is sealed and can not be implemented by other crates. 10 | // 11 | // Because no one else can implement it, we can add methods backwards-compatibly. 12 | pub trait PathExt: crate::Sealed { 13 | /// Returns Ok(true) if the path points at an existing entity. 14 | /// 15 | /// Wrapper for [`Path::try_exists`](https://doc.rust-lang.org/std/path/struct.Path.html#method.try_exists). 16 | #[cfg(rustc_1_63)] 17 | fn fs_err_try_exists(&self) -> io::Result; 18 | /// Given a path, query the file system to get information about a file, directory, etc. 19 | /// 20 | /// Wrapper for [`crate::metadata`]. 21 | fn fs_err_metadata(&self) -> io::Result; 22 | /// Query the metadata about a file without following symlinks. 23 | /// 24 | /// Wrapper for [`crate::symlink_metadata`]. 25 | fn fs_err_symlink_metadata(&self) -> io::Result; 26 | /// Returns the canonical, absolute form of a path with all intermediate components 27 | /// normalized and symbolic links resolved. 28 | /// 29 | /// Wrapper for [`crate::canonicalize`]. 30 | fn fs_err_canonicalize(&self) -> io::Result; 31 | /// Reads a symbolic link, returning the file that the link points to. 32 | /// 33 | /// Wrapper for [`crate::read_link`]. 34 | fn fs_err_read_link(&self) -> io::Result; 35 | /// Returns an iterator over the entries within a directory. 36 | /// 37 | /// Wrapper for [`crate::read_dir`]. 38 | fn fs_err_read_dir(&self) -> io::Result; 39 | } 40 | 41 | impl PathExt for Path { 42 | #[cfg(rustc_1_63)] 43 | fn fs_err_try_exists(&self) -> io::Result { 44 | self.try_exists() 45 | .map_err(|source| Error::build(source, ErrorKind::FileExists, self)) 46 | } 47 | 48 | fn fs_err_metadata(&self) -> io::Result { 49 | crate::metadata(self) 50 | } 51 | 52 | fn fs_err_symlink_metadata(&self) -> io::Result { 53 | crate::symlink_metadata(self) 54 | } 55 | 56 | fn fs_err_canonicalize(&self) -> io::Result { 57 | crate::canonicalize(self) 58 | } 59 | 60 | fn fs_err_read_link(&self) -> io::Result { 61 | crate::read_link(self) 62 | } 63 | 64 | fn fs_err_read_dir(&self) -> io::Result { 65 | crate::read_dir(self) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/tokio/dir_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, ErrorKind}; 2 | use std::io; 3 | use std::path::Path; 4 | 5 | /// A builder for creating directories in various manners. 6 | /// 7 | /// This is a wrapper around [`tokio::fs::DirBuilder`]. 8 | #[derive(Debug, Default)] 9 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 10 | pub struct DirBuilder { 11 | inner: tokio::fs::DirBuilder, 12 | } 13 | 14 | impl DirBuilder { 15 | /// Creates a new set of options with default mode/security settings for all 16 | /// platforms and also non-recursive. 17 | /// 18 | /// This is a wrapper version of [`tokio::fs::DirBuilder::new`] 19 | /// 20 | /// # Examples 21 | /// 22 | /// ```no_run 23 | /// use fs_err::tokio::DirBuilder; 24 | /// 25 | /// let builder = DirBuilder::new(); 26 | /// ``` 27 | pub fn new() -> Self { 28 | Default::default() 29 | } 30 | 31 | /// Indicates whether to create directories recursively (including all parent 32 | /// directories). Parents that do not exist are created with the same security and 33 | /// permissions settings. 34 | /// 35 | /// Wrapper around [`tokio::fs::DirBuilder::recursive`]. 36 | pub fn recursive(&mut self, recursive: bool) -> &mut Self { 37 | self.inner.recursive(recursive); 38 | self 39 | } 40 | 41 | /// Creates the specified directory with the configured options. 42 | /// 43 | /// Wrapper around [`tokio::fs::DirBuilder::create`]. 44 | pub async fn create(&self, path: impl AsRef) -> io::Result<()> { 45 | let path = path.as_ref(); 46 | self.inner 47 | .create(path) 48 | .await 49 | .map_err(|err| Error::build(err, ErrorKind::CreateDir, path)) 50 | } 51 | } 52 | 53 | #[cfg(unix)] 54 | impl DirBuilder { 55 | /// Sets the mode to create new directories with. 56 | /// 57 | /// Wrapper around [`tokio::fs::DirBuilder::mode`]. 58 | pub fn mode(&mut self, mode: u32) -> &mut Self { 59 | self.inner.mode(mode); 60 | self 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/tokio/file.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, ErrorKind}; 2 | use std::fs::{Metadata, Permissions}; 3 | use std::io; 4 | use std::io::{IoSlice, SeekFrom}; 5 | use std::path::{Path, PathBuf}; 6 | use std::pin::Pin; 7 | use std::task::{ready, Context, Poll}; 8 | use tokio::fs; 9 | use tokio::fs::File as TokioFile; 10 | use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; 11 | 12 | use super::OpenOptions; 13 | 14 | /// Wrapper around [`tokio::fs::File`] which adds more helpful 15 | /// information to all errors. 16 | #[derive(Debug)] 17 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 18 | pub struct File { 19 | tokio: fs::File, 20 | path: PathBuf, 21 | } 22 | 23 | impl File { 24 | /// Attempts to open a file in read-only mode. 25 | /// 26 | /// Wrapper for [`tokio::fs::File::open`]. 27 | pub async fn open(path: impl Into) -> io::Result { 28 | let path = path.into(); 29 | let f = TokioFile::open(&path) 30 | .await 31 | .map_err(|err| Error::build(err, ErrorKind::OpenFile, &path))?; 32 | Ok(File::from_parts(f, path)) 33 | } 34 | 35 | /// Opens a file in write-only mode. 36 | /// 37 | /// Wrapper for [`tokio::fs::File::create`]. 38 | pub async fn create(path: impl Into) -> io::Result { 39 | let path = path.into(); 40 | match TokioFile::create(&path).await { 41 | Ok(f) => Ok(File::from_parts(f, path)), 42 | Err(err) => Err(Error::build(err, ErrorKind::CreateFile, &path)), 43 | } 44 | } 45 | 46 | /// Opens a file in read-write mode. 47 | /// 48 | /// Wrapper for [`tokio::fs::File::create_new`]. 49 | pub async fn create_new(path: impl Into) -> Result { 50 | let path = path.into(); 51 | match fs::File::create_new(&path).await { 52 | Ok(file) => Ok(File::from_parts(file, path)), 53 | Err(err) => Err(Error::build(err, ErrorKind::CreateFile, path)), 54 | } 55 | } 56 | 57 | /// Returns a new `OpenOptions` object. 58 | /// 59 | /// Wrapper for [`tokio::fs::File::options`]. 60 | pub fn options() -> OpenOptions { 61 | OpenOptions::new() 62 | } 63 | 64 | /// Converts a [`crate::File`] to a [`tokio::fs::File`]. 65 | /// 66 | /// Wrapper for [`tokio::fs::File::from_std`]. 67 | pub fn from_std(std: crate::File) -> File { 68 | let (std, path) = std.into_parts(); 69 | File::from_parts(TokioFile::from_std(std), path) 70 | } 71 | 72 | /// Attempts to sync all OS-internal metadata to disk. 73 | /// 74 | /// Wrapper for [`tokio::fs::File::sync_all`]. 75 | pub async fn sync_all(&self) -> io::Result<()> { 76 | self.tokio 77 | .sync_all() 78 | .await 79 | .map_err(|err| self.error(err, ErrorKind::SyncFile)) 80 | } 81 | 82 | /// This function is similar to `sync_all`, except that it may not 83 | /// synchronize file metadata to the filesystem. 84 | /// 85 | /// Wrapper for [`tokio::fs::File::sync_data`]. 86 | pub async fn sync_data(&self) -> io::Result<()> { 87 | self.tokio 88 | .sync_data() 89 | .await 90 | .map_err(|err| self.error(err, ErrorKind::SyncFile)) 91 | } 92 | 93 | /// Truncates or extends the underlying file, updating the size of this file to become size. 94 | /// 95 | /// Wrapper for [`tokio::fs::File::set_len`]. 96 | pub async fn set_len(&self, size: u64) -> io::Result<()> { 97 | self.tokio 98 | .set_len(size) 99 | .await 100 | .map_err(|err| self.error(err, ErrorKind::SetLen)) 101 | } 102 | 103 | /// Queries metadata about the underlying file. 104 | /// 105 | /// Wrapper for [`tokio::fs::File::metadata`]. 106 | pub async fn metadata(&self) -> io::Result { 107 | self.tokio 108 | .metadata() 109 | .await 110 | .map_err(|err| self.error(err, ErrorKind::Metadata)) 111 | } 112 | 113 | /// Creates a new `File` instance that shares the same underlying file handle 114 | /// as the existing `File` instance. Reads, writes, and seeks will affect both 115 | /// `File` instances simultaneously. 116 | /// 117 | /// Wrapper for [`tokio::fs::File::try_clone`]. 118 | pub async fn try_clone(&self) -> io::Result { 119 | match self.tokio.try_clone().await { 120 | Ok(file) => Ok(File::from_parts(file, self.path.clone())), 121 | Err(err) => Err(self.error(err, ErrorKind::Clone)), 122 | } 123 | } 124 | 125 | /// Destructures `File` into a [`crate::File`]. This function is async to allow any 126 | /// in-flight operations to complete. 127 | /// 128 | /// Wrapper for [`tokio::fs::File::into_std`]. 129 | pub async fn into_std(self) -> crate::File { 130 | crate::File::from_parts(self.tokio.into_std().await, self.path) 131 | } 132 | 133 | /// Tries to immediately destructure `File` into a [`crate::File`]. 134 | /// 135 | /// Wrapper for [`tokio::fs::File::try_into_std`]. 136 | pub fn try_into_std(self) -> Result { 137 | match self.tokio.try_into_std() { 138 | Ok(f) => Ok(crate::File::from_parts(f, self.path)), 139 | Err(f) => Err(File::from_parts(f, self.path)), 140 | } 141 | } 142 | 143 | /// Changes the permissions on the underlying file. 144 | /// 145 | /// Wrapper for [`tokio::fs::File::set_permissions`]. 146 | pub async fn set_permissions(&self, perm: Permissions) -> io::Result<()> { 147 | self.tokio 148 | .set_permissions(perm) 149 | .await 150 | .map_err(|err| self.error(err, ErrorKind::SetPermissions)) 151 | } 152 | } 153 | 154 | /// Methods added by fs-err that are not available on 155 | /// [`tokio::fs::File`]. 156 | impl File { 157 | /// Creates a [`File`](struct.File.html) from a raw file and its path. 158 | pub fn from_parts

(file: TokioFile, path: P) -> Self 159 | where 160 | P: Into, 161 | { 162 | File { 163 | tokio: file, 164 | path: path.into(), 165 | } 166 | } 167 | 168 | /// Extract the raw file and its path from this [`File`](struct.File.html). 169 | pub fn into_parts(self) -> (TokioFile, PathBuf) { 170 | (self.tokio, self.path) 171 | } 172 | 173 | /// Returns a reference to the underlying [`tokio::fs::File`]. 174 | pub fn file(&self) -> &TokioFile { 175 | &self.tokio 176 | } 177 | 178 | /// Returns a mutable reference to the underlying [`tokio::fs::File`]. 179 | pub fn file_mut(&mut self) -> &mut TokioFile { 180 | &mut self.tokio 181 | } 182 | 183 | /// Returns a reference to the path that this file was created with. 184 | pub fn path(&self) -> &Path { 185 | &self.path 186 | } 187 | 188 | /// Wrap the error in information specific to this `File` object. 189 | fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error { 190 | Error::build(source, kind, &self.path) 191 | } 192 | } 193 | 194 | impl From for File { 195 | fn from(f: crate::File) -> Self { 196 | let (f, path) = f.into_parts(); 197 | File::from_parts(f.into(), path) 198 | } 199 | } 200 | 201 | impl From for TokioFile { 202 | fn from(f: File) -> Self { 203 | f.into_parts().0 204 | } 205 | } 206 | 207 | #[cfg(unix)] 208 | impl std::os::unix::io::AsRawFd for File { 209 | fn as_raw_fd(&self) -> std::os::unix::io::RawFd { 210 | self.tokio.as_raw_fd() 211 | } 212 | } 213 | 214 | #[cfg(windows)] 215 | impl std::os::windows::io::AsRawHandle for File { 216 | fn as_raw_handle(&self) -> std::os::windows::io::RawHandle { 217 | self.tokio.as_raw_handle() 218 | } 219 | } 220 | 221 | impl AsyncRead for File { 222 | fn poll_read( 223 | mut self: Pin<&mut Self>, 224 | cx: &mut Context<'_>, 225 | buf: &mut ReadBuf<'_>, 226 | ) -> Poll> { 227 | Poll::Ready( 228 | ready!(Pin::new(&mut self.tokio).poll_read(cx, buf)) 229 | .map_err(|err| self.error(err, ErrorKind::Read)), 230 | ) 231 | } 232 | } 233 | 234 | impl AsyncSeek for File { 235 | fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { 236 | Pin::new(&mut self.tokio) 237 | .start_seek(position) 238 | .map_err(|err| self.error(err, ErrorKind::Seek)) 239 | } 240 | 241 | fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 242 | Poll::Ready( 243 | ready!(Pin::new(&mut self.tokio).poll_complete(cx)) 244 | .map_err(|err| self.error(err, ErrorKind::Seek)), 245 | ) 246 | } 247 | } 248 | 249 | impl AsyncWrite for File { 250 | fn poll_write( 251 | mut self: Pin<&mut Self>, 252 | cx: &mut Context<'_>, 253 | buf: &[u8], 254 | ) -> Poll> { 255 | Poll::Ready( 256 | ready!(Pin::new(&mut self.tokio).poll_write(cx, buf)) 257 | .map_err(|err| self.error(err, ErrorKind::Write)), 258 | ) 259 | } 260 | 261 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 262 | Poll::Ready( 263 | ready!(Pin::new(&mut self.tokio).poll_flush(cx)) 264 | .map_err(|err| self.error(err, ErrorKind::Flush)), 265 | ) 266 | } 267 | 268 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 269 | Poll::Ready( 270 | ready!(Pin::new(&mut self.tokio).poll_shutdown(cx)) 271 | .map_err(|err| self.error(err, ErrorKind::Flush)), 272 | ) 273 | } 274 | 275 | fn poll_write_vectored( 276 | mut self: Pin<&mut Self>, 277 | cx: &mut Context<'_>, 278 | bufs: &[IoSlice<'_>], 279 | ) -> Poll> { 280 | Poll::Ready( 281 | ready!(Pin::new(&mut self.tokio).poll_write_vectored(cx, bufs)) 282 | .map_err(|err| self.error(err, ErrorKind::Write)), 283 | ) 284 | } 285 | 286 | fn is_write_vectored(&self) -> bool { 287 | self.tokio.is_write_vectored() 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/tokio/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tokio-specific wrappers that use `fs_err` error messages. 2 | 3 | use crate::errors::{Error, ErrorKind, SourceDestError, SourceDestErrorKind}; 4 | use std::fs::{Metadata, Permissions}; 5 | use std::path::{Path, PathBuf}; 6 | use tokio::io; 7 | mod dir_builder; 8 | mod file; 9 | mod open_options; 10 | mod read_dir; 11 | 12 | pub use self::open_options::OpenOptions; 13 | pub use self::read_dir::{read_dir, DirEntry, ReadDir}; 14 | pub use dir_builder::DirBuilder; 15 | pub use file::File; 16 | 17 | /// Returns the canonical, absolute form of a path with all intermediate 18 | /// components normalized and symbolic links resolved. 19 | /// 20 | /// Wrapper for [`tokio::fs::canonicalize`]. 21 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 22 | pub async fn canonicalize(path: impl AsRef) -> io::Result { 23 | let path = path.as_ref(); 24 | tokio::fs::canonicalize(path) 25 | .await 26 | .map_err(|err| Error::build(err, ErrorKind::Canonicalize, path)) 27 | } 28 | 29 | /// Copies the contents of one file to another. This function will also copy the permission bits 30 | /// of the original file to the destination file. 31 | /// This function will overwrite the contents of to. 32 | /// 33 | /// Wrapper for [`tokio::fs::copy`]. 34 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 35 | pub async fn copy(from: impl AsRef, to: impl AsRef) -> Result { 36 | let (from, to) = (from.as_ref(), to.as_ref()); 37 | tokio::fs::copy(from, to) 38 | .await 39 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::Copy, from, to)) 40 | } 41 | 42 | /// Creates a new, empty directory at the provided path. 43 | /// 44 | /// Wrapper for [`tokio::fs::create_dir`]. 45 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 46 | pub async fn create_dir(path: impl AsRef) -> io::Result<()> { 47 | let path = path.as_ref(); 48 | tokio::fs::create_dir(path) 49 | .await 50 | .map_err(|err| Error::build(err, ErrorKind::CreateDir, path)) 51 | } 52 | 53 | /// Recursively creates a directory and all of its parent components if they 54 | /// are missing. 55 | /// 56 | /// Wrapper for [`tokio::fs::create_dir_all`]. 57 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 58 | pub async fn create_dir_all(path: impl AsRef) -> io::Result<()> { 59 | let path = path.as_ref(); 60 | tokio::fs::create_dir_all(path) 61 | .await 62 | .map_err(|err| Error::build(err, ErrorKind::CreateDir, path)) 63 | } 64 | 65 | /// Creates a new hard link on the filesystem. 66 | /// 67 | /// Wrapper for [`tokio::fs::hard_link`]. 68 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 69 | pub async fn hard_link(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { 70 | let (src, dst) = (src.as_ref(), dst.as_ref()); 71 | tokio::fs::hard_link(src, dst) 72 | .await 73 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::HardLink, src, dst)) 74 | } 75 | 76 | /// Given a path, queries the file system to get information about a file, 77 | /// directory, etc. 78 | /// 79 | /// Wrapper for [`tokio::fs::metadata`]. 80 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 81 | pub async fn metadata(path: impl AsRef) -> io::Result { 82 | let path = path.as_ref(); 83 | tokio::fs::metadata(path) 84 | .await 85 | .map_err(|err| Error::build(err, ErrorKind::Metadata, path)) 86 | } 87 | 88 | /// Reads the entire contents of a file into a bytes vector. 89 | /// 90 | /// Wrapper for [`tokio::fs::read`]. 91 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 92 | pub async fn read(path: impl AsRef) -> io::Result> { 93 | let path = path.as_ref(); 94 | tokio::fs::read(path) 95 | .await 96 | .map_err(|err| Error::build(err, ErrorKind::Read, path)) 97 | } 98 | 99 | /// Reads a symbolic link, returning the file that the link points to. 100 | /// 101 | /// Wrapper for [`tokio::fs::read_link`]. 102 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 103 | pub async fn read_link(path: impl AsRef) -> io::Result { 104 | let path = path.as_ref(); 105 | tokio::fs::read_link(path) 106 | .await 107 | .map_err(|err| Error::build(err, ErrorKind::ReadLink, path)) 108 | } 109 | 110 | /// Creates a future which will open a file for reading and read the entire 111 | /// contents into a string and return said string. 112 | /// 113 | /// Wrapper for [`tokio::fs::read_to_string`]. 114 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 115 | pub async fn read_to_string(path: impl AsRef) -> io::Result { 116 | let path = path.as_ref(); 117 | tokio::fs::read_to_string(path) 118 | .await 119 | .map_err(|err| Error::build(err, ErrorKind::Read, path)) 120 | } 121 | 122 | /// Removes an existing, empty directory. 123 | /// 124 | /// Wrapper for [`tokio::fs::remove_dir`]. 125 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 126 | pub async fn remove_dir(path: impl AsRef) -> io::Result<()> { 127 | let path = path.as_ref(); 128 | tokio::fs::remove_dir(path) 129 | .await 130 | .map_err(|err| Error::build(err, ErrorKind::RemoveDir, path)) 131 | } 132 | 133 | /// Removes a directory at this path, after removing all its contents. Use carefully! 134 | /// 135 | /// Wrapper for [`tokio::fs::remove_dir_all`]. 136 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 137 | pub async fn remove_dir_all(path: impl AsRef) -> io::Result<()> { 138 | let path = path.as_ref(); 139 | tokio::fs::remove_dir_all(path) 140 | .await 141 | .map_err(|err| Error::build(err, ErrorKind::RemoveDir, path)) 142 | } 143 | 144 | /// Removes a file from the filesystem. 145 | /// 146 | /// Wrapper for [`tokio::fs::remove_file`]. 147 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 148 | pub async fn remove_file(path: impl AsRef) -> io::Result<()> { 149 | let path = path.as_ref(); 150 | tokio::fs::remove_file(path) 151 | .await 152 | .map_err(|err| Error::build(err, ErrorKind::RemoveFile, path)) 153 | } 154 | 155 | /// Renames a file or directory to a new name, replacing the original file if 156 | /// `to` already exists. 157 | /// 158 | /// Wrapper for [`tokio::fs::rename`]. 159 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 160 | pub async fn rename(from: impl AsRef, to: impl AsRef) -> io::Result<()> { 161 | let (from, to) = (from.as_ref(), to.as_ref()); 162 | tokio::fs::rename(from, to) 163 | .await 164 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::Rename, from, to)) 165 | } 166 | 167 | /// Changes the permissions found on a file or a directory. 168 | /// 169 | /// Wrapper for [`tokio::fs::set_permissions`]. 170 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 171 | pub async fn set_permissions(path: impl AsRef, perm: Permissions) -> io::Result<()> { 172 | let path = path.as_ref(); 173 | tokio::fs::set_permissions(path, perm) 174 | .await 175 | .map_err(|err| Error::build(err, ErrorKind::SetPermissions, path)) 176 | } 177 | 178 | /// Queries the file system metadata for a path. 179 | /// 180 | /// Wrapper for [`tokio::fs::symlink_metadata`]. 181 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 182 | pub async fn symlink_metadata(path: impl AsRef) -> io::Result { 183 | let path = path.as_ref(); 184 | tokio::fs::symlink_metadata(path) 185 | .await 186 | .map_err(|err| Error::build(err, ErrorKind::SymlinkMetadata, path)) 187 | } 188 | 189 | /// Creates a new symbolic link on the filesystem. 190 | /// 191 | /// Wrapper for [`tokio::fs::symlink`]. 192 | #[cfg(unix)] 193 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 194 | pub async fn symlink(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { 195 | let (src, dst) = (src.as_ref(), dst.as_ref()); 196 | tokio::fs::symlink(src, dst) 197 | .await 198 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::Symlink, src, dst)) 199 | } 200 | 201 | /// Creates a new directory symlink on the filesystem. 202 | /// 203 | /// Wrapper for [`tokio::fs::symlink_dir`]. 204 | #[cfg(windows)] 205 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 206 | pub async fn symlink_dir(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { 207 | let (src, dst) = (src.as_ref(), dst.as_ref()); 208 | tokio::fs::symlink_dir(src, dst) 209 | .await 210 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::SymlinkDir, src, dst)) 211 | } 212 | 213 | /// Creates a new file symbolic link on the filesystem. 214 | /// 215 | /// Wrapper for [`tokio::fs::symlink_file`]. 216 | #[cfg(windows)] 217 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 218 | pub async fn symlink_file(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { 219 | let (src, dst) = (src.as_ref(), dst.as_ref()); 220 | tokio::fs::symlink_file(src, dst) 221 | .await 222 | .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::SymlinkFile, src, dst)) 223 | } 224 | 225 | /// Creates a future that will open a file for writing and write the entire 226 | /// contents of `contents` to it. 227 | /// 228 | /// Wrapper for [`tokio::fs::write`]. 229 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 230 | pub async fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> io::Result<()> { 231 | let (path, contents) = (path.as_ref(), contents.as_ref()); 232 | tokio::fs::write(path, contents) 233 | .await 234 | .map_err(|err| Error::build(err, ErrorKind::Write, path)) 235 | } 236 | -------------------------------------------------------------------------------- /src/tokio/open_options.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, ErrorKind}; 2 | use crate::tokio::File; 3 | use std::io; 4 | use std::path::Path; 5 | use tokio::fs::OpenOptions as TokioOpenOptions; 6 | 7 | /// Options and flags which can be used to configure how a file is opened. 8 | /// 9 | /// This is a wrapper around [`tokio::fs::OpenOptions`]. 10 | #[derive(Clone, Debug, Default)] 11 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 12 | pub struct OpenOptions { 13 | tokio: TokioOpenOptions, 14 | } 15 | 16 | impl OpenOptions { 17 | /// Creates a blank new set of options ready for configuration. 18 | /// 19 | /// All options are initially set to `false`. 20 | /// 21 | /// This is a wrapped version of [`tokio::fs::OpenOptions::new`] 22 | /// 23 | /// # Examples 24 | /// 25 | /// ```no_run 26 | /// use fs_err::tokio::OpenOptions; 27 | /// 28 | /// let mut options = OpenOptions::new(); 29 | /// let future = options.read(true).open("foo.txt"); 30 | /// ``` 31 | pub fn new() -> OpenOptions { 32 | OpenOptions { 33 | tokio: TokioOpenOptions::new(), 34 | } 35 | } 36 | 37 | /// Sets the option for read access. 38 | /// 39 | /// Wrapper for [`tokio::fs::OpenOptions::read`]. 40 | pub fn read(&mut self, read: bool) -> &mut OpenOptions { 41 | self.tokio.read(read); 42 | self 43 | } 44 | 45 | /// Sets the option for write access. 46 | /// 47 | /// Wrapper for [`tokio::fs::OpenOptions::write`]. 48 | pub fn write(&mut self, write: bool) -> &mut OpenOptions { 49 | self.tokio.write(write); 50 | self 51 | } 52 | 53 | /// Sets the option for the append mode. 54 | /// 55 | /// Wrapper for [`tokio::fs::OpenOptions::append`]. 56 | pub fn append(&mut self, append: bool) -> &mut OpenOptions { 57 | self.tokio.append(append); 58 | self 59 | } 60 | 61 | /// Sets the option for truncating a previous file. 62 | /// 63 | /// Wrapper for [`tokio::fs::OpenOptions::truncate`]. 64 | pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions { 65 | self.tokio.truncate(truncate); 66 | self 67 | } 68 | 69 | /// Sets the option for creating a new file. 70 | /// 71 | /// Wrapper for [`tokio::fs::OpenOptions::create`]. 72 | pub fn create(&mut self, create: bool) -> &mut OpenOptions { 73 | self.tokio.create(create); 74 | self 75 | } 76 | 77 | /// Sets the option to always create a new file. 78 | /// 79 | /// Wrapper for [`tokio::fs::OpenOptions::create_new`]. 80 | pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions { 81 | self.tokio.create_new(create_new); 82 | self 83 | } 84 | 85 | /// Opens a file at `path` with the options specified by `self`. 86 | /// 87 | /// Wrapper for [`tokio::fs::OpenOptions::open`]. 88 | pub async fn open(&self, path: impl AsRef) -> io::Result { 89 | let path = path.as_ref(); 90 | self.tokio 91 | .open(path) 92 | .await 93 | .map(|f| File::from_parts(f, path)) 94 | .map_err(|err| Error::build(err, ErrorKind::OpenFile, path)) 95 | } 96 | } 97 | 98 | #[cfg(unix)] 99 | impl OpenOptions { 100 | /// Sets the mode bits that a new file will be created with. 101 | /// 102 | /// Wrapper for [`tokio::fs::OpenOptions::mode`]. 103 | pub fn mode(&mut self, mode: u32) -> &mut OpenOptions { 104 | self.tokio.mode(mode); 105 | self 106 | } 107 | 108 | /// Passes custom flags to the `flags` argument of `open`. 109 | /// 110 | /// Wrapper for [`tokio::fs::OpenOptions::custom_flags`]. 111 | pub fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions { 112 | self.tokio.custom_flags(flags); 113 | self 114 | } 115 | } 116 | 117 | impl From for OpenOptions { 118 | fn from(std: std::fs::OpenOptions) -> Self { 119 | OpenOptions { tokio: std.into() } 120 | } 121 | } 122 | 123 | impl From for OpenOptions { 124 | fn from(tokio: TokioOpenOptions) -> Self { 125 | OpenOptions { tokio } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/tokio/read_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, ErrorKind}; 2 | use std::ffi::OsString; 3 | use std::fs::{FileType, Metadata}; 4 | use std::io; 5 | use std::path::{Path, PathBuf}; 6 | use std::task::{ready, Context, Poll}; 7 | use tokio::fs; 8 | 9 | /// Returns a stream over the entries within a directory. 10 | /// 11 | /// Wrapper for [`tokio::fs::read_dir`]. 12 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 13 | pub async fn read_dir(path: impl AsRef) -> io::Result { 14 | let path = path.as_ref(); 15 | let tokio = fs::read_dir(path) 16 | .await 17 | .map_err(|err| Error::build(err, ErrorKind::ReadDir, path))?; 18 | Ok(ReadDir { 19 | tokio, 20 | path: path.to_owned(), 21 | }) 22 | } 23 | 24 | /// Reads the entries in a directory. 25 | /// 26 | /// This is a wrapper around [`tokio::fs::ReadDir`]. 27 | #[derive(Debug)] 28 | #[must_use = "streams do nothing unless polled"] 29 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 30 | pub struct ReadDir { 31 | tokio: fs::ReadDir, 32 | path: PathBuf, 33 | } 34 | 35 | impl ReadDir { 36 | /// Returns the next entry in the directory stream. 37 | /// 38 | /// Wrapper around [`tokio::fs::ReadDir::next_entry`]. 39 | pub async fn next_entry(&mut self) -> io::Result> { 40 | match self.tokio.next_entry().await { 41 | Ok(entry) => Ok(entry.map(|e| DirEntry { tokio: e })), 42 | Err(err) => Err(Error::build(err, ErrorKind::ReadDir, &self.path)), 43 | } 44 | } 45 | 46 | /// Polls for the next directory entry in the stream. 47 | /// 48 | /// Wrapper around [`tokio::fs::ReadDir::poll_next_entry`]. 49 | pub fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll>> { 50 | Poll::Ready(match ready!(self.tokio.poll_next_entry(cx)) { 51 | Ok(entry) => Ok(entry.map(|e| DirEntry { tokio: e })), 52 | Err(err) => Err(Error::build(err, ErrorKind::ReadDir, &self.path)), 53 | }) 54 | } 55 | } 56 | 57 | /// Entries returned by the [`ReadDir`] stream. 58 | /// 59 | /// This is a wrapper around [`tokio::fs::DirEntry`]. 60 | #[derive(Debug)] 61 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 62 | pub struct DirEntry { 63 | tokio: fs::DirEntry, 64 | } 65 | 66 | impl DirEntry { 67 | /// Returns the full path to the file that this entry represents. 68 | /// 69 | /// Wrapper around [`tokio::fs::DirEntry::path`]. 70 | pub fn path(&self) -> PathBuf { 71 | self.tokio.path() 72 | } 73 | 74 | /// Returns the bare file name of this directory entry without any other 75 | /// leading path component. 76 | /// 77 | /// Wrapper around [`tokio::fs::DirEntry::file_name`]. 78 | pub fn file_name(&self) -> OsString { 79 | self.tokio.file_name() 80 | } 81 | 82 | /// Returns the metadata for the file that this entry points at. 83 | /// 84 | /// Wrapper around [`tokio::fs::DirEntry::metadata`]. 85 | pub async fn metadata(&self) -> io::Result { 86 | self.tokio 87 | .metadata() 88 | .await 89 | .map_err(|err| Error::build(err, ErrorKind::Metadata, self.path())) 90 | } 91 | 92 | /// Returns the file type for the file that this entry points at. 93 | /// 94 | /// Wrapper around [`tokio::fs::DirEntry::file_type`]. 95 | pub async fn file_type(&self) -> io::Result { 96 | self.tokio 97 | .file_type() 98 | .await 99 | .map_err(|err| Error::build(err, ErrorKind::Metadata, self.path())) 100 | } 101 | } 102 | 103 | #[cfg(unix)] 104 | impl DirEntry { 105 | /// Returns the underlying `d_ino` field in the contained `dirent` structure. 106 | /// 107 | /// Wrapper around [`tokio::fs::DirEntry::ino`]. 108 | pub fn ino(&self) -> u64 { 109 | self.tokio.ino() 110 | } 111 | } 112 | --------------------------------------------------------------------------------