├── .github ├── dependabot.yml └── workflows │ ├── coverage.yml │ ├── gh-pages.yml │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── book.toml ├── booksrc ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── SUMMARY.md ├── end.md ├── tutorial1.md ├── tutorial10.md ├── tutorial11.md ├── tutorial12.md ├── tutorial13.md ├── tutorial14.md ├── tutorial2.md ├── tutorial3.md ├── tutorial4.md ├── tutorial5.md ├── tutorial6.md ├── tutorial7.md ├── tutorial8.md └── tutorial9.md ├── examples ├── example.rs ├── tutorial1.rs ├── tutorial10.rs ├── tutorial11.rs ├── tutorial12.rs ├── tutorial13.rs ├── tutorial14.rs ├── tutorial15.rs ├── tutorial2.rs ├── tutorial3.rs ├── tutorial4.rs ├── tutorial5.rs ├── tutorial6.rs ├── tutorial7.rs ├── tutorial8.rs └── tutorial9.rs ├── git-deploy-branch.sh ├── src └── lib.rs └── tests ├── test_basic.rs └── test_iter.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the master branch 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | branches: 11 | - master 12 | release: 13 | types: 14 | - created 15 | 16 | 17 | jobs: 18 | coverage: 19 | runs-on: ubuntu-latest 20 | env: 21 | CARGO_TERM_COLOR: always 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: dtolnay/rust-toolchain@master 25 | with: 26 | target: x86_64-unknown-linux-gnu 27 | toolchain: nightly 28 | components: llvm-tools-preview 29 | - name: Install cargo-llvm-cov 30 | uses: taiki-e/install-action@cargo-llvm-cov 31 | - name: Generate code coverage 32 | run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json 33 | - name: Upload coverage to Codecov 34 | uses: codecov/codecov-action@v3 35 | with: 36 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 37 | files: codecov.json 38 | fail_ci_if_error: true 39 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Build mdbook 15 | run: cargo install mdbook 16 | 17 | - name: Build cargo-readme 18 | run: cargo install cargo-readme 19 | 20 | - name: Build README.md 21 | run: cargo readme > README.md 22 | 23 | - name: Build 24 | run: mdbook build 25 | 26 | - name: Deploy 27 | uses: peaceiris/actions-gh-pages@v3 28 | with: 29 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} 30 | publish_dir: ./book 31 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the master branch 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | branches: 11 | - master 12 | release: 13 | types: 14 | - created 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | version: 22 | - 1.54.0 23 | - stable 24 | - beta 25 | - nightly 26 | steps: 27 | - uses: actions/checkout@v1 28 | - name: Install toolchain 29 | uses: dtolnay/rust-toolchain@master 30 | with: 31 | toolchain: ${{ matrix.version }} 32 | profile: minimal 33 | - name: Build 34 | run: cargo build --verbose 35 | - name: Run tests 36 | run: cargo test --verbose 37 | - name: Build --all-features 38 | run: cargo build --verbose --all-features 39 | - name: Run tests --all-features 40 | run: cargo test --verbose --all-features 41 | 42 | fmt: 43 | name: cargo fmt 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v2 47 | - uses: dtolnay/rust-toolchain@master 48 | with: 49 | components: rustfmt 50 | toolchain: stable 51 | profile: minimal 52 | override: true 53 | - uses: actions-rs/cargo@v1 54 | with: 55 | command: fmt 56 | args: --all -- --check 57 | 58 | clippy: 59 | name: cargo clippy 60 | runs-on: ubuntu-latest 61 | steps: 62 | - uses: actions/checkout@v2 63 | - uses: dtolnay/rust-toolchain@master 64 | with: 65 | components: clippy 66 | toolchain: stable 67 | profile: minimal 68 | override: true 69 | - uses: actions-rs/cargo@v1 70 | with: 71 | command: clippy 72 | args: -- -D warnings 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea/* 4 | Cargo.lock 5 | book 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chainerror" 3 | version = "1.0.0" 4 | authors = ["Harald Hoyer "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | documentation = "https://docs.rs/chainerror" 8 | homepage = "https://haraldh.github.io/chainerror/" 9 | repository = "https://github.com/haraldh/chainerror" 10 | description = "Make chaining errors easy." 11 | keywords = ["error"] 12 | categories = ["rust-patterns"] 13 | readme = "README.md" 14 | 15 | exclude = [ ".gitignore", "examples/*", "booksrc/*", "book.toml", 16 | "theme/*", "git-deploy-branch.sh", ".travis.yml" ] 17 | 18 | [badges] 19 | # See https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section 20 | github = { repository = "haraldh/chainerror", workflow = "Rust" } 21 | maintenance = { status = "actively-developed" } 22 | is-it-maintained-issue-resolution = { repository = "haraldh/chainerror" } 23 | is-it-maintained-open-issues = { repository = "haraldh/chainerror" } 24 | 25 | [package.metadata.docs.rs] 26 | cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] 27 | -------------------------------------------------------------------------------- /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 | MIT License 2 | 3 | Copyright (c) 2018 Harald Hoyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crate](https://img.shields.io/crates/v/chainerror.svg)](https://crates.io/crates/chainerror) 2 | [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/chainerror/) 3 | [![Coverage Status](https://codecov.io/gh/haraldh/chainerror/branch/master/graph/badge.svg?token=HGLJFGA11B)](https://codecov.io/gh/haraldh/chainerror) 4 | ![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg) 5 | 6 | # chainerror 7 | 8 | `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your 9 | binaries, you still have the error backtrace. 10 | 11 | Having nested function returning errors, the output doesn't tell where the error originates from. 12 | 13 | ```rust 14 | use std::path::PathBuf; 15 | 16 | type BoxedError = Box; 17 | fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { 18 | // do stuff, return other errors 19 | let _buf = std::fs::read_to_string(&path)?; 20 | // do stuff, return other errors 21 | Ok(()) 22 | } 23 | 24 | fn process_config_file() -> Result<(), BoxedError> { 25 | // do stuff, return other errors 26 | let _buf = read_config_file("foo.txt".into())?; 27 | // do stuff, return other errors 28 | Ok(()) 29 | } 30 | 31 | fn main() { 32 | if let Err(e) = process_config_file() { 33 | eprintln!("Error:\n{:?}", e); 34 | } 35 | } 36 | ``` 37 | 38 | This gives the output: 39 | ```console 40 | Error: 41 | Os { code: 2, kind: NotFound, message: "No such file or directory" } 42 | ``` 43 | and you have no idea where it comes from. 44 | 45 | 46 | With `chainerror`, you can supply a context and get a nice error backtrace: 47 | 48 | ```rust 49 | use chainerror::Context as _; 50 | use std::path::PathBuf; 51 | 52 | type BoxedError = Box; 53 | fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { 54 | // do stuff, return other errors 55 | let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; 56 | // do stuff, return other errors 57 | Ok(()) 58 | } 59 | 60 | fn process_config_file() -> Result<(), BoxedError> { 61 | // do stuff, return other errors 62 | let _buf = read_config_file("foo.txt".into()).context("read the config file")?; 63 | // do stuff, return other errors 64 | Ok(()) 65 | } 66 | 67 | fn main() { 68 | if let Err(e) = process_config_file() { 69 | eprintln!("Error:\n{:?}", e); 70 | } 71 | } 72 | ``` 73 | 74 | with the output: 75 | ```console 76 | Error: 77 | examples/simple.rs:14:51: read the config file 78 | Caused by: 79 | examples/simple.rs:7:47: Reading file: "foo.txt" 80 | Caused by: 81 | Os { code: 2, kind: NotFound, message: "No such file or directory" } 82 | ``` 83 | 84 | `chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. 85 | It encapsulates all types, which have `Display + Debug` and can store the error cause internally. 86 | 87 | Along with the `Error` struct, `chainerror` comes with some useful helper macros to save a lot of typing. 88 | 89 | `chainerror` has no dependencies! 90 | 91 | Debug information is worth it! 92 | 93 | ## Multiple Output Formats 94 | 95 | `chainerror` supports multiple output formats, which can be selected with the different format specifiers: 96 | 97 | * `{}`: Display 98 | ```console 99 | func1 error calling func2 100 | ``` 101 | 102 | * `{:#}`: Alternative Display 103 | ```console 104 | func1 error calling func2 105 | Caused by: 106 | func2 error: calling func3 107 | Caused by: 108 | (passed error) 109 | Caused by: 110 | Error reading 'foo.txt' 111 | Caused by: 112 | entity not found 113 | ``` 114 | 115 | * `{:?}`: Debug 116 | ```console 117 | examples/example.rs:50:13: func1 error calling func2 118 | Caused by: 119 | examples/example.rs:25:13: Func2Error(func2 error: calling func3) 120 | Caused by: 121 | examples/example.rs:18:13: (passed error) 122 | Caused by: 123 | examples/example.rs:13:18: Error reading 'foo.txt' 124 | Caused by: 125 | Kind(NotFound) 126 | 127 | ``` 128 | 129 | * `{:#?}`: Alternative Debug 130 | ```console 131 | Error { 132 | occurrence: Some( 133 | "examples/example.rs:50:13", 134 | ), 135 | kind: func1 error calling func2, 136 | source: Some( 137 | Error { 138 | occurrence: Some( 139 | "examples/example.rs:25:13", 140 | ), 141 | kind: Func2Error(func2 error: calling func3), 142 | source: Some( 143 | Error { 144 | occurrence: Some( 145 | "examples/example.rs:18:13", 146 | ), 147 | kind: (passed error), 148 | source: Some( 149 | Error { 150 | occurrence: Some( 151 | "examples/example.rs:13:18", 152 | ), 153 | kind: "Error reading 'foo.txt'", 154 | source: Some( 155 | Kind( 156 | NotFound, 157 | ), 158 | ), 159 | }, 160 | ), 161 | }, 162 | ), 163 | }, 164 | ), 165 | } 166 | ``` 167 | 168 | ## Tutorial 169 | 170 | Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) 171 | 172 | ## License 173 | 174 | Licensed under either of 175 | 176 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 177 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 178 | 179 | at your option. 180 | 181 | ### Contribution 182 | 183 | Unless you explicitly state otherwise, any contribution intentionally 184 | submitted for inclusion in the work by you, as defined in the Apache-2.0 185 | license, shall be dual licensed as above, without any additional terms or 186 | conditions. 187 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Harald Hoyer"] 3 | multilingual = false 4 | src = "booksrc" 5 | title = "chainerror" 6 | description = "A tutorial for the chainerror rust crate." 7 | 8 | [build] 9 | build-dir = "book" 10 | create-missing = false 11 | -------------------------------------------------------------------------------- /booksrc/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /booksrc/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /booksrc/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /booksrc/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [chainerror](README.md) 4 | 5 | - [Simple String Errors](tutorial1.md) 6 | - [Simple Chained String Errors](tutorial2.md) 7 | - [Mapping Errors](tutorial3.md) 8 | - [More Information](tutorial4.md) 9 | - [The source() of Errors](tutorial5.md) 10 | - [Downcast the Errors](tutorial6.md) 11 | - [The root cause of all Errors](tutorial7.md) 12 | - [Finding an Error cause](tutorial8.md) 13 | - [Selective Error Handling](tutorial9.md) 14 | - [ErrorKind to the rescue](tutorial10.md) 15 | - [Debug for the ErrorKind](tutorial11.md) 16 | - [Deref for the ErrorKind](tutorial12.md) 17 | - [Writing a library](tutorial13.md) 18 | - [Going back to std](tutorial14.md) 19 | 20 | [The End](end.md) 21 | -------------------------------------------------------------------------------- /booksrc/end.md: -------------------------------------------------------------------------------- 1 | # The End 2 | 3 | That's it for now… 4 | 5 | Happy error handling! 6 | 7 | To report issues, submit pull request or for the source code, examples and the book source, visit 8 | the [Git Repo](https://github.com/haraldh/chainerror). -------------------------------------------------------------------------------- /booksrc/tutorial1.md: -------------------------------------------------------------------------------- 1 | # Simple String Errors 2 | 3 | An easy way of doing error handling in rust is by returning `String` as a `Box`. 4 | 5 | If the rust `main` function returns an `Err()`, this `Err()` will be displayed with `std::fmt::Debug`. 6 | 7 | As you can see by running the example (by pressing the "Play" button in upper right of the code block), 8 | this only 9 | prints out the last `Error`. 10 | 11 | ~~~ 12 | Error: "func1 error" 13 | ~~~ 14 | 15 | The next chapters of this tutorial show how `chainerror` adds more information 16 | and improves inspecting the sources of an error. 17 | 18 | You can also run the tutorial examples in the checked out 19 | [chainerror git repo](https://github.com/haraldh/chainerror). 20 | ~~~console 21 | $ cargo run -q --example tutorial1 22 | ~~~ 23 | 24 | ~~~rust 25 | {{#include ../examples/tutorial1.rs}} 26 | ~~~ 27 | -------------------------------------------------------------------------------- /booksrc/tutorial10.md: -------------------------------------------------------------------------------- 1 | # ErrorKind to the rescue 2 | 3 | To cope with different kind of errors, we introduce the `kind` of an error `Func1ErrorKind` with an enum. 4 | 5 | Because we derive `Debug` and implement `Display` our `Func1ErrorKind` enum, this enum can be used as 6 | a `std::error::Error`. 7 | 8 | Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box>` and we can 9 | use `ChainResult<(), Func1ErrorKind>`. 10 | 11 | In `main` we can now directly use the methods of `chainerror::Error` without downcasting the error first. 12 | 13 | Also, a nice `match` on `chainerror::Error.kind()` is now possible, which returns `&T`, meaning `&Func1ErrorKind` here. 14 | 15 | ~~~rust 16 | {{#include ../examples/tutorial10.rs}} 17 | # #[allow(dead_code)] 18 | # mod chainerror { 19 | {{#rustdoc_include ../src/lib.rs:-1}} 20 | # } 21 | ~~~ 22 | -------------------------------------------------------------------------------- /booksrc/tutorial11.md: -------------------------------------------------------------------------------- 1 | # Debug for the ErrorKind 2 | 3 | One small improvement is to fix the debug output of 4 | `Func1ErrorKind`. As you probably noticed, the output doesn't say much of the enum. 5 | 6 | ~~~ 7 | Debug Error: 8 | src/main.rs:35: Func2 9 | […] 10 | ~~~ 11 | 12 | As a lazy shortcut, we implement `Debug` by calling `Display` and end up with 13 | 14 | ~~~ 15 | Debug Error: 16 | src/main.rs:40: func1 error calling func2 17 | […} 18 | ~~~ 19 | 20 | which gives us a lot more detail. 21 | 22 | To create your own Errors, you might find [crates](https://crates.io) which create enum `Display+Debug` via derive macros. 23 | 24 | Also, noteworthy is [custom_error](https://crates.io/crates/custom_error) to define your custom errors, 25 | which can then be used with `chainerror`. 26 | 27 | ~~~rust 28 | {{#include ../examples/tutorial11.rs}} 29 | # #[allow(dead_code)] 30 | # mod chainerror { 31 | {{#rustdoc_include ../src/lib.rs:-1}} 32 | # } 33 | ~~~ 34 | -------------------------------------------------------------------------------- /booksrc/tutorial12.md: -------------------------------------------------------------------------------- 1 | # Deref for the ErrorKind 2 | 3 | Because chainerror::Error implements Deref to &T, we can also match on `*e` instead of `e.kind()` 4 | or call a function with `&e` 5 | ~~~rust 6 | {{#include ../examples/tutorial12.rs}} 7 | # #[allow(dead_code)] 8 | # mod chainerror { 9 | {{#rustdoc_include ../src/lib.rs:-1}} 10 | # } 11 | ~~~ 12 | -------------------------------------------------------------------------------- /booksrc/tutorial13.md: -------------------------------------------------------------------------------- 1 | # Writing a library 2 | 3 | I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `chainerror::Error` 4 | so you can tell your library users to use the `.kind()` method as `std::io::Error` does. 5 | 6 | If you later decide to make your own `Error` implementation, your library users don't 7 | have to change much or anything. 8 | 9 | ~~~rust 10 | # #[allow(dead_code)] 11 | # #[macro_use] 12 | # pub mod chainerror { 13 | {{#rustdoc_include ../src/lib.rs:-1}} 14 | # } 15 | pub mod mycrate { 16 | use crate::chainerror::*; // omit the `crate::` part 17 | {{#include ../examples/tutorial13.rs:3:}} 18 | ~~~ 19 | -------------------------------------------------------------------------------- /booksrc/tutorial14.md: -------------------------------------------------------------------------------- 1 | # Going back to std 2 | 3 | Not using `chainerror` and going full `std` would look like this: 4 | 5 | Btw, the code size is bigger than using `chainerror` :-) 6 | 7 | ~~~rust 8 | {{#include ../examples/tutorial14.rs}} 9 | ~~~ 10 | -------------------------------------------------------------------------------- /booksrc/tutorial2.md: -------------------------------------------------------------------------------- 1 | # Simple Chained String Errors 2 | 3 | With relatively small changes and the help of the `context()` method of the `chainerror` crate 4 | the `&str` errors are now chained together. 5 | 6 | Press the play button in the upper right corner and see the nice debug output. 7 | 8 | ~~~rust 9 | {{#include ../examples/tutorial2.rs}} 10 | # #[allow(dead_code)] 11 | # mod chainerror { 12 | {{#rustdoc_include ../src/lib.rs:-1}} 13 | # } 14 | ~~~ 15 | 16 | ### What did we do here? 17 | 18 | ~~~rust,ignore 19 | {{#include ../examples/tutorial2.rs:13:15}} 20 | ~~~ 21 | 22 | The function `context(newerror)` stores `olderror` as the source/cause of `newerror` 23 | along with the `Location` of the `context()` call and returns `Err(newerror)`. 24 | 25 | `?` then returns the inner error applying `.into()`, so that we 26 | again have a `Err(Box)` as a result. 27 | 28 | The `Debug` implementation of `chainerror::Error` (which is returned by `context()`) 29 | prints the `Debug` of `T` prefixed with the stored filename and line number. 30 | 31 | `chainerror::Error` in our case is `chainerror::Error<&str>`. 32 | -------------------------------------------------------------------------------- /booksrc/tutorial3.md: -------------------------------------------------------------------------------- 1 | # Mapping Errors 2 | 3 | Now let's get more rust idiomatic by using `.context()` directly on the previous `Result`. 4 | 5 | ~~~rust 6 | {{#include ../examples/tutorial3.rs}} 7 | # #[allow(dead_code)] 8 | # mod chainerror { 9 | {{#rustdoc_include ../src/lib.rs:-1}} 10 | # } 11 | ~~~ 12 | 13 | If you compare the output to the previous example, you will see, 14 | that: 15 | 16 | ~~~ 17 | Error: examples/tutorial2.rs:20:16: func1 error 18 | ~~~ 19 | 20 | changed to just: 21 | 22 | ~~~ 23 | examples/tutorial3.rs:17:13: func1 error 24 | ~~~ 25 | 26 | This is, because we caught the error of `func1()` in `main()` and print it out ourselves. 27 | 28 | We can now control, whether to output in `Debug` or `Display` mode. 29 | Maybe depending on `--debug` as a CLI argument. 30 | -------------------------------------------------------------------------------- /booksrc/tutorial4.md: -------------------------------------------------------------------------------- 1 | # More information 2 | 3 | To give more context to the error, you want to use `format!` 4 | to extend the information in the context string. 5 | 6 | ~~~rust 7 | {{#include ../examples/tutorial4.rs}} 8 | # #[allow(dead_code)] 9 | # mod chainerror { 10 | {{#rustdoc_include ../src/lib.rs:-1}} 11 | # } 12 | ~~~ 13 | -------------------------------------------------------------------------------- /booksrc/tutorial5.md: -------------------------------------------------------------------------------- 1 | # The source() of Errors 2 | 3 | Sometimes you want to inspect the `source()` of an `Error`. 4 | `chainerror` implements `std::error::Error::source()`, so you can get the cause of an error. 5 | 6 | ~~~rust 7 | {{#include ../examples/tutorial5.rs}} 8 | # #[allow(dead_code)] 9 | # mod chainerror { 10 | {{#rustdoc_include ../src/lib.rs:-1}} 11 | # } 12 | ~~~ 13 | 14 | Note, that because we changed the output of the error in `main()` from 15 | `Debug` to `Display`, we don't see the error backtrace with filename and line number. 16 | 17 | To use the `Display` backtrace, you have to use the alternative display format output `{:#}`. 18 | -------------------------------------------------------------------------------- /booksrc/tutorial6.md: -------------------------------------------------------------------------------- 1 | # Downcast the Errors 2 | 3 | `std::error::Error` comes with some helper methods to get to the original object of the 4 | `&(dyn Error + 'static)` returned by `.source()`. 5 | 6 | ~~~rust,ignore 7 | pub fn downcast_ref(&self) -> Option<&T> 8 | pub fn downcast_mut(&mut self) -> Option<&mut T> 9 | ~~~ 10 | 11 | This is how it looks like, when using those: 12 | 13 | ~~~rust 14 | {{#include ../examples/tutorial6.rs}} 15 | # #[allow(dead_code)] 16 | # mod chainerror { 17 | {{#rustdoc_include ../src/lib.rs:-1}} 18 | # } 19 | ~~~ -------------------------------------------------------------------------------- /booksrc/tutorial7.md: -------------------------------------------------------------------------------- 1 | # The root cause of all Errors 2 | 3 | `chainerror` also has some helper methods: 4 | 5 | ~~~rust,ignore 6 | fn is_chain(&self) -> bool 7 | fn downcast_chain_ref(&self) -> Option<&chainerror::Error> 8 | fn downcast_chain_mut(&mut self) -> Option<&mut chainerror::Error> 9 | fn root_cause(&self) -> Option<&(dyn Error + 'static)> 10 | fn find_cause(&self) -> Option<&U> 11 | fn find_chain_cause(&self) -> Option<&chainerror::Error> 12 | fn kind<'a>(&'a self) -> &'a T 13 | ~~~ 14 | 15 | Using `downcast_chain_ref::()` gives a `chainerror::Error`, which can be used 16 | to call `.find_cause::()`. 17 | 18 | ~~~rust,ignore 19 | if let Some(s) = e.downcast_chain_ref::() { 20 | if let Some(ioerror) = s.find_cause::() { 21 | ~~~ 22 | 23 | or to use `.root_cause()`, which of course can be of any type implementing `std::error::Error`. 24 | 25 | ~~~rust,ignore 26 | if let Some(e) = s.root_cause() { 27 | ~~~ 28 | 29 | ~~~rust 30 | {{#include ../examples/tutorial7.rs}} 31 | # #[allow(dead_code)] 32 | # mod chainerror { 33 | {{#rustdoc_include ../src/lib.rs:-1}} 34 | # } 35 | ~~~ -------------------------------------------------------------------------------- /booksrc/tutorial8.md: -------------------------------------------------------------------------------- 1 | # Finding an Error cause 2 | 3 | To distinguish the errors occurring in various places, we can define named string errors with the 4 | "new type" pattern. 5 | 6 | ~~~rust,ignore 7 | chainerror::str_context!(Func2Error); 8 | chainerror::str_context!(Func1Error); 9 | ~~~ 10 | 11 | Instead of `chainerror::Error` we now have `struct Func1Error(String)` and `chainerror::Error`. 12 | 13 | In the `main` function you can see, how we can match the different errors. 14 | 15 | Also see: 16 | ~~~rust,ignore 17 | if let Some(f2err) = f1err.find_chain_cause::() { 18 | ~~~ 19 | as a shortcut to 20 | ~~~rust,ignore 21 | if let Some(f2err) = f1err.find_cause::>() { 22 | ~~~ 23 | hiding the `chainerror::Error` implementation detail. 24 | 25 | ~~~rust 26 | {{#include ../examples/tutorial8.rs}} 27 | # #[allow(dead_code)] 28 | # mod chainerror { 29 | {{#rustdoc_include ../src/lib.rs:-1}} 30 | # } 31 | ~~~ 32 | -------------------------------------------------------------------------------- /booksrc/tutorial9.md: -------------------------------------------------------------------------------- 1 | # Selective Error Handling 2 | 3 | What about functions returning different Error types? 4 | 5 | In this example `func1()` can return either `Func1ErrorFunc2` or `Func1ErrorIO`. 6 | 7 | We might want to `match` on `func1()` with something like: 8 | 9 | ~~~rust,ignore 10 | fn main() -> Result<(), Box> { 11 | match func1() { 12 | Err(e) if let Some(s) = e.downcast_chain_ref::() => 13 | eprintln!("Func1ErrorIO:\n{:?}", s), 14 | 15 | Err(e) if let Some(s) = e.downcast_chain_ref::() => 16 | eprintln!("Func1ErrorFunc2:\n{:?}", s), 17 | 18 | Ok(_) => {}, 19 | } 20 | Ok(()) 21 | } 22 | ~~~ 23 | 24 | but this is not valid rust code, so we end up doing it the hard way. 25 | In the next chapter, we will see, how to solve this more elegantly. 26 | 27 | ~~~rust 28 | {{#include ../examples/tutorial9.rs}} 29 | # #[allow(dead_code)] 30 | # mod chainerror { 31 | {{#rustdoc_include ../src/lib.rs:-1}} 32 | # } 33 | ~~~ 34 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context as _; 2 | use std::error::Error; 3 | use std::fmt; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | fn func4() -> Result<(), Box> { 12 | let filename = "foo.txt"; 13 | do_some_io().context(format!("Error reading '{}'", filename))?; 14 | Ok(()) 15 | } 16 | 17 | fn func3() -> Result<(), Box> { 18 | func4().annotate()?; 19 | Ok(()) 20 | } 21 | 22 | chainerror::str_context!(Func2Error); 23 | 24 | fn func2() -> chainerror::Result<(), Func2Error> { 25 | func3().context(Func2Error::new("func2 error: calling func3"))?; 26 | Ok(()) 27 | } 28 | 29 | enum Func1Error { 30 | Func2, 31 | IO(String), 32 | } 33 | 34 | impl fmt::Display for Func1Error { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 | match self { 37 | Func1Error::Func2 => write!(f, "func1 error calling func2"), 38 | Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), 39 | } 40 | } 41 | } 42 | 43 | impl fmt::Debug for Func1Error { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | write!(f, "{}", self) 46 | } 47 | } 48 | 49 | fn func1() -> chainerror::Result<(), Func1Error> { 50 | func2().context(Func1Error::Func2)?; 51 | let filename = String::from("bar.txt"); 52 | do_some_io().context(Func1Error::IO(filename))?; 53 | Ok(()) 54 | } 55 | 56 | fn main() { 57 | if let Err(e) = func1() { 58 | eprintln!("\nDisplay Error {{}}:\n{}", e); 59 | 60 | eprintln!("\nAlternative Display Error {{:#}}:\n{:#}", e); 61 | 62 | eprintln!("\nDebug Error {{:?}}:\n{:?}", e); 63 | 64 | eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); 65 | 66 | match e.kind() { 67 | Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), 68 | Func1Error::IO(filename) => { 69 | eprintln!("Main Error Report: func1 error reading '{}'", filename) 70 | } 71 | } 72 | 73 | if let Some(e) = e.find_chain_cause::() { 74 | eprintln!("\nError reported by Func2Error: {}", e) 75 | } 76 | 77 | if let Some(e) = e.root_cause() { 78 | let ioerror = e.downcast_ref::().unwrap(); 79 | eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/tutorial1.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::single_match)] 2 | #![allow(clippy::redundant_pattern_matching)] 3 | 4 | use std::error::Error; 5 | use std::io; 6 | 7 | fn do_some_io() -> Result<(), Box> { 8 | Err(io::Error::from(io::ErrorKind::NotFound))?; 9 | Ok(()) 10 | } 11 | 12 | fn func2() -> Result<(), Box> { 13 | if let Err(_) = do_some_io() { 14 | Err("func2 error")?; 15 | } 16 | Ok(()) 17 | } 18 | 19 | fn func1() -> Result<(), Box> { 20 | if let Err(_) = func2() { 21 | Err("func1 error")?; 22 | } 23 | Ok(()) 24 | } 25 | 26 | fn main() -> Result<(), Box> { 27 | func1() 28 | } 29 | -------------------------------------------------------------------------------- /examples/tutorial10.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context as _; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | chainerror::str_context!(Func2Error); 12 | 13 | fn func2() -> Result<(), Box> { 14 | let filename = "foo.txt"; 15 | do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 16 | Ok(()) 17 | } 18 | 19 | #[derive(Debug)] 20 | enum Func1ErrorKind { 21 | Func2, 22 | IO(String), 23 | } 24 | 25 | impl ::std::fmt::Display for Func1ErrorKind { 26 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 27 | match self { 28 | Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), 29 | Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), 30 | } 31 | } 32 | } 33 | impl ::std::error::Error for Func1ErrorKind {} 34 | 35 | fn func1() -> chainerror::Result<(), Func1ErrorKind> { 36 | func2().context(Func1ErrorKind::Func2)?; 37 | let filename = String::from("bar.txt"); 38 | do_some_io().context(Func1ErrorKind::IO(filename))?; 39 | Ok(()) 40 | } 41 | 42 | fn main() -> Result<(), Box> { 43 | if let Err(e) = func1() { 44 | match e.kind() { 45 | Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), 46 | Func1ErrorKind::IO(filename) => { 47 | eprintln!("Main Error Report: func1 error reading '{}'", filename) 48 | } 49 | } 50 | 51 | if let Some(e) = e.find_chain_cause::() { 52 | eprintln!("\nError reported by Func2Error: {}", e) 53 | } 54 | 55 | eprintln!("\nDebug Error:\n{:?}", e); 56 | 57 | std::process::exit(1); 58 | } 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /examples/tutorial11.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context as _; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | chainerror::str_context!(Func2Error); 12 | 13 | fn func2() -> Result<(), Box> { 14 | let filename = "foo.txt"; 15 | do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 16 | Ok(()) 17 | } 18 | 19 | enum Func1ErrorKind { 20 | Func2, 21 | IO(String), 22 | } 23 | 24 | impl ::std::fmt::Display for Func1ErrorKind { 25 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 26 | match self { 27 | Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), 28 | Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), 29 | } 30 | } 31 | } 32 | 33 | impl ::std::fmt::Debug for Func1ErrorKind { 34 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 35 | write!(f, "{}", self) 36 | } 37 | } 38 | 39 | impl ::std::error::Error for Func1ErrorKind {} 40 | 41 | fn func1() -> chainerror::Result<(), Func1ErrorKind> { 42 | func2().context(Func1ErrorKind::Func2)?; 43 | let filename = String::from("bar.txt"); 44 | do_some_io().context(Func1ErrorKind::IO(filename))?; 45 | Ok(()) 46 | } 47 | 48 | fn main() -> Result<(), Box> { 49 | if let Err(e) = func1() { 50 | match e.kind() { 51 | Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), 52 | Func1ErrorKind::IO(filename) => { 53 | eprintln!("Main Error Report: func1 error reading '{}'", filename) 54 | } 55 | } 56 | 57 | if let Some(e) = e.find_chain_cause::() { 58 | eprintln!("\nError reported by Func2Error: {}", e) 59 | } 60 | 61 | eprintln!("\nDebug Error:\n{:?}", e); 62 | 63 | std::process::exit(1); 64 | } 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /examples/tutorial12.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context as _; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | chainerror::str_context!(Func2Error); 12 | 13 | fn func2() -> Result<(), Box> { 14 | let filename = "foo.txt"; 15 | do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 16 | Ok(()) 17 | } 18 | 19 | enum Func1ErrorKind { 20 | Func2, 21 | IO(String), 22 | } 23 | 24 | impl ::std::fmt::Display for Func1ErrorKind { 25 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 26 | match self { 27 | Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), 28 | Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), 29 | } 30 | } 31 | } 32 | 33 | impl ::std::fmt::Debug for Func1ErrorKind { 34 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 35 | write!(f, "{}", self) 36 | } 37 | } 38 | 39 | impl ::std::error::Error for Func1ErrorKind {} 40 | 41 | fn func1() -> chainerror::Result<(), Func1ErrorKind> { 42 | func2().context(Func1ErrorKind::Func2)?; 43 | let filename = String::from("bar.txt"); 44 | do_some_io().context(Func1ErrorKind::IO(filename))?; 45 | Ok(()) 46 | } 47 | 48 | fn handle_func1errorkind(e: &Func1ErrorKind) { 49 | match e { 50 | Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), 51 | Func1ErrorKind::IO(ref filename) => { 52 | eprintln!("Main Error Report: func1 error reading '{}'", filename) 53 | } 54 | } 55 | } 56 | 57 | fn main() -> Result<(), Box> { 58 | if let Err(e) = func1() { 59 | match *e { 60 | Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), 61 | Func1ErrorKind::IO(ref filename) => { 62 | eprintln!("Main Error Report: func1 error reading '{}'", filename) 63 | } 64 | } 65 | 66 | handle_func1errorkind(&e); 67 | 68 | if let Some(e) = e.find_chain_cause::() { 69 | eprintln!("\nError reported by Func2Error: {}", e) 70 | } 71 | 72 | eprintln!("\nDebug Error:\n{:?}", e); 73 | 74 | std::process::exit(1); 75 | } 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /examples/tutorial13.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::single_match)] 2 | #![allow(clippy::redundant_pattern_matching)] 3 | 4 | pub mod mycrate { 5 | use chainerror::Context as _; 6 | 7 | use std::io; 8 | 9 | fn do_some_io() -> std::result::Result<(), Box> { 10 | Err(io::Error::from(io::ErrorKind::NotFound))?; 11 | Ok(()) 12 | } 13 | 14 | chainerror::str_context!(Func2Error); 15 | 16 | fn func2() -> std::result::Result<(), Box> { 17 | let filename = "foo.txt"; 18 | do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 19 | Ok(()) 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub enum ErrorKind { 24 | Func2, 25 | IO(String), 26 | } 27 | 28 | chainerror::err_kind!(Error, ErrorKind); 29 | 30 | pub type Result = std::result::Result; 31 | 32 | impl std::fmt::Display for ErrorKind { 33 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { 34 | match self { 35 | ErrorKind::Func2 => write!(f, "func1 error calling func2"), 36 | ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), 37 | } 38 | } 39 | } 40 | 41 | pub fn func1() -> Result<()> { 42 | func2().context(ErrorKind::Func2)?; 43 | let filename = String::from("bar.txt"); 44 | do_some_io().context(ErrorKind::IO(filename))?; 45 | Ok(()) 46 | } 47 | } 48 | 49 | fn main() -> Result<(), Box> { 50 | use mycrate::func1; 51 | use mycrate::ErrorKind; 52 | use std::error::Error; 53 | use std::io; 54 | 55 | if let Err(e) = func1() { 56 | match e.kind() { 57 | ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), 58 | ErrorKind::IO(ref filename) => { 59 | eprintln!("Main Error Report: func1 error reading '{}'", filename) 60 | } 61 | } 62 | 63 | eprintln!(); 64 | let mut s: &dyn Error = &e; 65 | while let Some(c) = s.source() { 66 | if let Some(ioerror) = c.downcast_ref::() { 67 | eprintln!("caused by: std::io::Error: {}", ioerror); 68 | match ioerror.kind() { 69 | io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), 70 | _ => {} 71 | } 72 | } else { 73 | eprintln!("caused by: {}", c); 74 | } 75 | s = c; 76 | } 77 | 78 | eprintln!("\nDebug Error:\n{:?}", e); 79 | 80 | std::process::exit(1); 81 | } 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /examples/tutorial14.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::single_match)] 2 | #![allow(clippy::redundant_pattern_matching)] 3 | 4 | pub mod mycrate { 5 | use std::error::Error as StdError; 6 | 7 | use self::func2mod::{do_some_io, func2}; 8 | 9 | pub mod func2mod { 10 | use std::error::Error as StdError; 11 | use std::io; 12 | 13 | pub enum ErrorKind { 14 | IO(String), 15 | } 16 | 17 | impl std::fmt::Display for ErrorKind { 18 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { 19 | match self { 20 | ErrorKind::IO(s) => std::fmt::Display::fmt(s, f), 21 | } 22 | } 23 | } 24 | 25 | impl std::fmt::Debug for ErrorKind { 26 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { 27 | match self { 28 | ErrorKind::IO(s) => std::fmt::Display::fmt(s, f), 29 | } 30 | } 31 | } 32 | 33 | macro_rules! mcontext { 34 | ( $k:expr ) => {{ 35 | |e| { 36 | Error( 37 | $k, 38 | Some(Box::from(e)), 39 | Some(concat!(file!(), ":", line!(), ": ")), 40 | ) 41 | } 42 | }}; 43 | } 44 | 45 | pub struct Error( 46 | ErrorKind, 47 | Option>, 48 | Option<&'static str>, 49 | ); 50 | 51 | impl Error { 52 | pub fn kind(&self) -> &ErrorKind { 53 | &self.0 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(e: ErrorKind) -> Self { 59 | Error(e, None, None) 60 | } 61 | } 62 | 63 | impl std::error::Error for Error { 64 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 65 | self.1.as_ref().map(|e| e.as_ref()) 66 | } 67 | } 68 | 69 | impl std::fmt::Display for Error { 70 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 71 | std::fmt::Display::fmt(&self.0, f) 72 | } 73 | } 74 | 75 | impl std::fmt::Debug for Error { 76 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 77 | if let Some(ref o) = self.2 { 78 | std::fmt::Display::fmt(o, f)?; 79 | } 80 | 81 | std::fmt::Debug::fmt(&self.0, f)?; 82 | 83 | if let Some(e) = self.source() { 84 | std::fmt::Display::fmt("\nCaused by:\n", f)?; 85 | std::fmt::Debug::fmt(&e, f)?; 86 | } 87 | Ok(()) 88 | } 89 | } 90 | 91 | pub fn do_some_io() -> std::result::Result<(), Box> { 92 | Err(io::Error::from(io::ErrorKind::NotFound))?; 93 | Ok(()) 94 | } 95 | 96 | pub fn func2() -> std::result::Result<(), Error> { 97 | let filename = "foo.txt"; 98 | do_some_io().map_err(mcontext!(ErrorKind::IO(format!( 99 | "Error reading '{}'", 100 | filename 101 | ))))?; 102 | Ok(()) 103 | } 104 | } 105 | 106 | #[derive(Debug)] 107 | pub enum ErrorKind { 108 | Func2, 109 | IO(String), 110 | } 111 | 112 | impl std::fmt::Display for ErrorKind { 113 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { 114 | match self { 115 | ErrorKind::Func2 => write!(f, "func1 error calling func2"), 116 | ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), 117 | } 118 | } 119 | } 120 | 121 | macro_rules! mcontext { 122 | ( $k:expr ) => {{ 123 | |e| { 124 | Error( 125 | $k, 126 | Some(Box::from(e)), 127 | Some(concat!(file!(), ":", line!(), ": ")), 128 | ) 129 | } 130 | }}; 131 | } 132 | 133 | pub struct Error( 134 | ErrorKind, 135 | Option>, 136 | Option<&'static str>, 137 | ); 138 | 139 | impl Error { 140 | pub fn kind(&self) -> &ErrorKind { 141 | &self.0 142 | } 143 | } 144 | 145 | impl From for Error { 146 | fn from(e: ErrorKind) -> Self { 147 | Error(e, None, None) 148 | } 149 | } 150 | 151 | impl std::error::Error for Error { 152 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 153 | self.1.as_ref().map(|e| e.as_ref()) 154 | } 155 | } 156 | 157 | impl std::fmt::Display for Error { 158 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 159 | std::fmt::Display::fmt(&self.0, f) 160 | } 161 | } 162 | 163 | impl std::fmt::Debug for Error { 164 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 165 | if let Some(ref o) = self.2 { 166 | std::fmt::Display::fmt(o, f)?; 167 | } 168 | 169 | std::fmt::Debug::fmt(&self.0, f)?; 170 | if let Some(e) = self.source() { 171 | std::fmt::Display::fmt("\nCaused by:\n", f)?; 172 | std::fmt::Debug::fmt(&e, f)?; 173 | } 174 | Ok(()) 175 | } 176 | } 177 | 178 | pub type Result = std::result::Result; 179 | 180 | pub fn func1() -> Result<()> { 181 | func2().map_err(mcontext!(ErrorKind::Func2))?; 182 | let filename = String::from("bar.txt"); 183 | do_some_io().map_err(mcontext!(ErrorKind::IO(filename)))?; 184 | Ok(()) 185 | } 186 | } 187 | 188 | fn main() -> Result<(), Box> { 189 | use mycrate::func1; 190 | use mycrate::ErrorKind; 191 | use std::error::Error; 192 | use std::io; 193 | 194 | if let Err(e) = func1() { 195 | match e.kind() { 196 | ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), 197 | ErrorKind::IO(ref filename) => { 198 | eprintln!("Main Error Report: func1 error reading '{}'", filename) 199 | } 200 | } 201 | 202 | eprintln!(); 203 | let mut s: &dyn Error = &e; 204 | while let Some(c) = s.source() { 205 | if let Some(ioerror) = c.downcast_ref::() { 206 | eprintln!("caused by: std::io::Error: {}", ioerror); 207 | match ioerror.kind() { 208 | io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), 209 | _ => {} 210 | } 211 | } else { 212 | eprintln!("caused by: {}", c); 213 | } 214 | s = c; 215 | } 216 | 217 | eprintln!("\nDebug Error:\n{:?}", e); 218 | 219 | std::process::exit(1); 220 | } 221 | Ok(()) 222 | } 223 | -------------------------------------------------------------------------------- /examples/tutorial15.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::single_match)] 2 | #![allow(clippy::redundant_pattern_matching)] 3 | 4 | pub mod mycrate { 5 | use chainerror::{Context as _, ErrorDown as _}; 6 | 7 | use std::io; 8 | 9 | fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { 10 | Err(io::Error::from(io::ErrorKind::NotFound))?; 11 | Ok(()) 12 | } 13 | 14 | chainerror::str_context!(Func2Error); 15 | 16 | fn func2() -> std::result::Result<(), Box> { 17 | let filename = "foo.txt"; 18 | do_some_io(filename).context(Func2Error(format!("Error reading '{}'", filename)))?; 19 | Ok(()) 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub enum ErrorKind { 24 | Func2, 25 | IO(String), 26 | FatalError(String), 27 | Unknown, 28 | } 29 | 30 | chainerror::err_kind!(Error, ErrorKind); 31 | pub type Result = std::result::Result; 32 | 33 | impl std::fmt::Display for ErrorKind { 34 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { 35 | match self { 36 | ErrorKind::FatalError(e) => write!(f, "fatal error {}", e), 37 | ErrorKind::Unknown => write!(f, "unknown error"), 38 | ErrorKind::Func2 => write!(f, "func1 error calling func2"), 39 | ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), 40 | } 41 | } 42 | } 43 | 44 | impl ErrorKind { 45 | fn from_io_error(e: &io::Error, f: String) -> Self { 46 | match e.kind() { 47 | io::ErrorKind::BrokenPipe => panic!("Should not happen"), 48 | io::ErrorKind::ConnectionReset => { 49 | ErrorKind::FatalError(format!("While reading `{}`: {}", f, e)) 50 | } 51 | _ => ErrorKind::IO(f), 52 | } 53 | } 54 | } 55 | 56 | impl From<&(dyn std::error::Error + 'static + Send + Sync)> for ErrorKind { 57 | fn from(e: &(dyn std::error::Error + 'static + Send + Sync)) -> Self { 58 | if let Some(_) = e.downcast_ref::() { 59 | ErrorKind::IO(String::from("Unknown filename")) 60 | } else if let Some(_) = e.downcast_inner_ref::() { 61 | ErrorKind::Func2 62 | } else { 63 | ErrorKind::Unknown 64 | } 65 | } 66 | } 67 | 68 | impl From<&std::boxed::Box> for ErrorKind { 69 | fn from(e: &std::boxed::Box) -> Self { 70 | Self::from(&**e) 71 | } 72 | } 73 | 74 | impl From<&Func2Error> for ErrorKind { 75 | fn from(_: &Func2Error) -> Self { 76 | ErrorKind::Func2 77 | } 78 | } 79 | 80 | impl From<&io::Error> for ErrorKind { 81 | fn from(e: &io::Error) -> Self { 82 | ErrorKind::IO(format!("{}", e)) 83 | } 84 | } 85 | 86 | pub fn func1() -> Result<()> { 87 | func2().map_err(|e| ErrorKind::from(&e))?; 88 | 89 | let filename = "bar.txt"; 90 | 91 | do_some_io(filename).map_context(|e| ErrorKind::from_io_error(e, filename.into()))?; 92 | do_some_io(filename).map_context(|_| ErrorKind::IO(filename.into()))?; 93 | do_some_io(filename).map_context(|e| ErrorKind::from(e))?; 94 | 95 | Ok(()) 96 | } 97 | 98 | pub fn super_func1() -> Result<()> { 99 | func1().map_context(|e| ErrorKind::from(e))?; 100 | Ok(()) 101 | } 102 | } 103 | 104 | fn main() -> Result<(), Box> { 105 | use mycrate::super_func1; 106 | use mycrate::ErrorKind; 107 | use std::error::Error; 108 | use std::io; 109 | 110 | if let Err(e) = super_func1() { 111 | match e.kind() { 112 | ErrorKind::FatalError(f) => eprintln!("Main Error Report: Fatal Error: {}", f), 113 | ErrorKind::Unknown => eprintln!("Main Error Report: Unknown error occurred"), 114 | ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), 115 | ErrorKind::IO(ref filename) => { 116 | eprintln!("Main Error Report: func1 error reading '{}'", filename) 117 | } 118 | } 119 | 120 | eprintln!(); 121 | let mut s: &dyn Error = &e; 122 | while let Some(c) = s.source() { 123 | if let Some(ioerror) = c.downcast_ref::() { 124 | eprintln!("caused by: std::io::Error: {}", ioerror); 125 | match ioerror.kind() { 126 | io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), 127 | _ => {} 128 | } 129 | } else { 130 | eprintln!("caused by: {}", c); 131 | } 132 | s = c; 133 | } 134 | 135 | eprintln!("\nDebug Error:\n{:?}", e); 136 | 137 | std::process::exit(1); 138 | } 139 | Ok(()) 140 | } 141 | -------------------------------------------------------------------------------- /examples/tutorial2.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context as _; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | fn func2() -> Result<(), Box> { 12 | if let Err(e) = do_some_io() { 13 | Err(e).context("func2 error")?; 14 | } 15 | Ok(()) 16 | } 17 | 18 | fn func1() -> Result<(), Box> { 19 | if let Err(e) = func2() { 20 | Err(e).context("func1 error")?; 21 | } 22 | Ok(()) 23 | } 24 | 25 | fn main() -> Result<(), Box> { 26 | func1() 27 | } 28 | -------------------------------------------------------------------------------- /examples/tutorial3.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context as _; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | fn func2() -> Result<(), Box> { 12 | do_some_io().context("func2 error")?; 13 | Ok(()) 14 | } 15 | 16 | fn func1() -> Result<(), Box> { 17 | func2().context("func1 error")?; 18 | Ok(()) 19 | } 20 | 21 | fn main() -> Result<(), Box> { 22 | if let Err(e) = func1() { 23 | eprintln!("{:?}", e); 24 | std::process::exit(1); 25 | } 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /examples/tutorial4.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context as _; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | fn func2() -> Result<(), Box> { 12 | let filename = "foo.txt"; 13 | do_some_io().context(format!("Error reading '{}'", filename))?; 14 | Ok(()) 15 | } 16 | 17 | fn func1() -> Result<(), Box> { 18 | func2().context("func1 error")?; 19 | Ok(()) 20 | } 21 | 22 | fn main() -> Result<(), Box> { 23 | if let Err(e) = func1() { 24 | eprintln!("{:?}", e); 25 | std::process::exit(1); 26 | } 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/tutorial5.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context as _; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | fn func2() -> Result<(), Box> { 12 | let filename = "foo.txt"; 13 | do_some_io().context(format!("Error reading '{}'", filename))?; 14 | Ok(()) 15 | } 16 | 17 | fn func1() -> Result<(), Box> { 18 | if let Err(e) = func2() { 19 | if let Some(s) = e.source() { 20 | eprintln!("func2 failed because of '{}'", s); 21 | Err(e).context("func1 error")?; 22 | } 23 | } 24 | Ok(()) 25 | } 26 | 27 | fn main() -> Result<(), Box> { 28 | if let Err(e) = func1() { 29 | eprintln!("{}", e); 30 | std::process::exit(1); 31 | } 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/tutorial6.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::single_match)] 2 | #![allow(clippy::redundant_pattern_matching)] 3 | 4 | use chainerror::Context as _; 5 | 6 | use std::error::Error; 7 | use std::io; 8 | 9 | fn do_some_io() -> Result<(), Box> { 10 | Err(io::Error::from(io::ErrorKind::NotFound))?; 11 | Ok(()) 12 | } 13 | 14 | fn func2() -> Result<(), Box> { 15 | let filename = "foo.txt"; 16 | do_some_io().context(format!("Error reading '{}'", filename))?; 17 | Ok(()) 18 | } 19 | 20 | fn func1() -> Result<(), Box> { 21 | func2().context("func1 error")?; 22 | Ok(()) 23 | } 24 | 25 | fn main() -> Result<(), Box> { 26 | if let Err(e) = func1() { 27 | eprintln!("Error: {}", e); 28 | let mut s: &(dyn Error) = e.as_ref(); 29 | while let Some(c) = s.source() { 30 | if let Some(ioerror) = c.downcast_ref::() { 31 | eprintln!("caused by: std::io::Error: {}", ioerror); 32 | match ioerror.kind() { 33 | io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), 34 | _ => {} 35 | } 36 | } else { 37 | eprintln!("caused by: {}", c); 38 | } 39 | s = c; 40 | } 41 | std::process::exit(1); 42 | } 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /examples/tutorial7.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::single_match)] 2 | #![allow(clippy::redundant_pattern_matching)] 3 | 4 | use chainerror::{Context as _, ErrorDown as _}; 5 | 6 | use std::error::Error; 7 | use std::io; 8 | 9 | fn do_some_io() -> Result<(), Box> { 10 | Err(io::Error::from(io::ErrorKind::NotFound))?; 11 | Ok(()) 12 | } 13 | 14 | fn func2() -> Result<(), Box> { 15 | let filename = "foo.txt"; 16 | do_some_io().context(format!("Error reading '{}'", filename))?; 17 | Ok(()) 18 | } 19 | 20 | fn func1() -> Result<(), Box> { 21 | func2().context("func1 error")?; 22 | Ok(()) 23 | } 24 | 25 | fn main() -> Result<(), Box> { 26 | if let Err(e) = func1() { 27 | eprintln!("Error: {}", e); 28 | if let Some(s) = e.downcast_chain_ref::() { 29 | if let Some(ioerror) = s.find_cause::() { 30 | eprintln!("caused by: std::io::Error: {}", ioerror); 31 | match ioerror.kind() { 32 | io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), 33 | _ => {} 34 | } 35 | } 36 | 37 | if let Some(e) = s.root_cause() { 38 | let ioerror = e.downcast_ref::().unwrap(); 39 | eprintln!("The root cause was: std::io::Error: {:#?}", ioerror); 40 | } 41 | } 42 | std::process::exit(1); 43 | } 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /examples/tutorial8.rs: -------------------------------------------------------------------------------- 1 | use chainerror::{Context as _, ErrorDown as _}; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | chainerror::str_context!(Func2Error); 12 | 13 | fn func2() -> Result<(), Box> { 14 | let filename = "foo.txt"; 15 | do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 16 | Ok(()) 17 | } 18 | 19 | chainerror::str_context!(Func1Error); 20 | 21 | fn func1() -> Result<(), Box> { 22 | func2().context(Func1Error::new("func1 error"))?; 23 | Ok(()) 24 | } 25 | 26 | fn main() -> Result<(), Box> { 27 | if let Err(e) = func1() { 28 | if let Some(f1err) = e.downcast_chain_ref::() { 29 | eprintln!("Func1Error: {}", f1err); 30 | 31 | if let Some(f2err) = f1err.find_cause::>() { 32 | eprintln!("Func2Error: {}", f2err); 33 | } 34 | 35 | if let Some(f2err) = f1err.find_chain_cause::() { 36 | eprintln!("Debug Func2Error:\n{:?}", f2err); 37 | } 38 | } 39 | std::process::exit(1); 40 | } 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /examples/tutorial9.rs: -------------------------------------------------------------------------------- 1 | use chainerror::{Context as _, ErrorDown}; 2 | 3 | use std::error::Error; 4 | use std::io; 5 | 6 | fn do_some_io() -> Result<(), Box> { 7 | Err(io::Error::from(io::ErrorKind::NotFound))?; 8 | Ok(()) 9 | } 10 | 11 | chainerror::str_context!(Func2Error); 12 | 13 | fn func2() -> Result<(), Box> { 14 | let filename = "foo.txt"; 15 | do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 16 | Ok(()) 17 | } 18 | 19 | chainerror::str_context!(Func1ErrorFunc2); 20 | chainerror::str_context!(Func1ErrorIO); 21 | 22 | fn func1() -> Result<(), Box> { 23 | func2().context(Func1ErrorFunc2::new("func1 error calling func2"))?; 24 | let filename = "bar.txt"; 25 | do_some_io().context(Func1ErrorIO(format!("Error reading '{}'", filename)))?; 26 | Ok(()) 27 | } 28 | 29 | fn main() -> Result<(), Box> { 30 | if let Err(e) = func1() { 31 | if let Some(s) = e.downcast_ref::>() { 32 | eprintln!("Func1ErrorIO:\n{:?}", s); 33 | } 34 | 35 | if let Some(s) = e.downcast_chain_ref::() { 36 | eprintln!("Func1ErrorFunc2:\n{:?}", s); 37 | } 38 | std::process::exit(1); 39 | } 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /git-deploy-branch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit #abort if any command fails 3 | me=$(basename "$0") 4 | 5 | help_message="\ 6 | Usage: $me [-c FILE] [] 7 | Deploy generated files to a git branch. 8 | 9 | Options: 10 | 11 | -h, --help Show this help information. 12 | -v, --verbose Increase verbosity. Useful for debugging. 13 | -e, --allow-empty Allow deployment of an empty directory. 14 | -m, --message MESSAGE Specify the message used when committing on the 15 | deploy branch. 16 | -n, --no-hash Don't append the source commit's hash to the deploy 17 | commit's message. 18 | -c, --config-file PATH Override default & environment variables' values 19 | with those in set in the file at 'PATH'. Must be the 20 | first option specified. 21 | 22 | Variables: 23 | 24 | GIT_DEPLOY_DIR Folder path containing the files to deploy. 25 | GIT_DEPLOY_BRANCH Commit deployable files to this branch. 26 | GIT_DEPLOY_REPO Push the deploy branch to this repository. 27 | 28 | These variables have default values defined in the script. The defaults can be 29 | overridden by environment variables. Any environment variables are overridden 30 | by values set in a '.env' file (if it exists), and in turn by those set in a 31 | file specified by the '--config-file' option." 32 | 33 | parse_args() { 34 | # Set args from a local environment file. 35 | if [ -e ".env" ]; then 36 | source .env 37 | fi 38 | 39 | # Set args from file specified on the command-line. 40 | if [[ $1 = "-c" || $1 = "--config-file" ]]; then 41 | source "$2" 42 | shift 2 43 | fi 44 | 45 | # Parse arg flags 46 | # If something is exposed as an environment variable, set/overwrite it 47 | # here. Otherwise, set/overwrite the internal variable instead. 48 | while : ; do 49 | if [[ $1 = "-h" || $1 = "--help" ]]; then 50 | echo "$help_message" 51 | return 0 52 | elif [[ $1 = "-v" || $1 = "--verbose" ]]; then 53 | verbose=true 54 | shift 55 | elif [[ $1 = "-e" || $1 = "--allow-empty" ]]; then 56 | allow_empty=true 57 | shift 58 | elif [[ ( $1 = "-m" || $1 = "--message" ) && -n $2 ]]; then 59 | commit_message=$2 60 | shift 2 61 | elif [[ $1 = "-n" || $1 = "--no-hash" ]]; then 62 | GIT_DEPLOY_APPEND_HASH=false 63 | shift 64 | else 65 | break 66 | fi 67 | done 68 | 69 | # Set internal option vars from the environment and arg flags. All internal 70 | # vars should be declared here, with sane defaults if applicable. 71 | 72 | # Source directory & target branch. 73 | deploy_directory=${GIT_DEPLOY_DIR:-dist} 74 | deploy_branch=${GIT_DEPLOY_BRANCH:-gh-pages} 75 | 76 | #if no user identity is already set in the current git environment, use this: 77 | default_username=${GIT_DEPLOY_USERNAME:-deploy.sh} 78 | default_email=${GIT_DEPLOY_EMAIL:-} 79 | 80 | #repository to deploy to. must be readable and writable. 81 | repo=${GIT_DEPLOY_REPO:-origin} 82 | 83 | #append commit hash to the end of message by default 84 | append_hash=${GIT_DEPLOY_APPEND_HASH:-true} 85 | } 86 | 87 | main() { 88 | parse_args "$@" 89 | 90 | enable_expanded_output 91 | 92 | if ! git diff --exit-code --quiet --cached; then 93 | echo Aborting due to uncommitted changes in the index >&2 94 | return 1 95 | fi 96 | 97 | commit_title=`git log -n 1 --format="%s" HEAD` 98 | commit_hash=` git log -n 1 --format="%H" HEAD` 99 | 100 | #default commit message uses last title if a custom one is not supplied 101 | if [[ -z $commit_message ]]; then 102 | commit_message="publish: $commit_title" 103 | fi 104 | 105 | #append hash to commit message unless no hash flag was found 106 | if [ $append_hash = true ]; then 107 | commit_message="$commit_message"$'\n\n'"generated from commit $commit_hash" 108 | fi 109 | 110 | previous_branch=`git rev-parse --abbrev-ref HEAD` 111 | 112 | if [ ! -d "$deploy_directory" ]; then 113 | echo "Deploy directory '$deploy_directory' does not exist. Aborting." >&2 114 | return 1 115 | fi 116 | 117 | # must use short form of flag in ls for compatibility with OS X and BSD 118 | if [[ -z `ls -A "$deploy_directory" 2> /dev/null` && -z $allow_empty ]]; then 119 | echo "Deploy directory '$deploy_directory' is empty. Aborting. If you're sure you want to deploy an empty tree, use the --allow-empty / -e flag." >&2 120 | return 1 121 | fi 122 | 123 | if git ls-remote --exit-code $repo "refs/heads/$deploy_branch" ; then 124 | # deploy_branch exists in $repo; make sure we have the latest version 125 | 126 | disable_expanded_output 127 | git fetch --force $repo $deploy_branch:$deploy_branch 128 | enable_expanded_output 129 | fi 130 | 131 | # check if deploy_branch exists locally 132 | if git show-ref --verify --quiet "refs/heads/$deploy_branch" 133 | then incremental_deploy 134 | else initial_deploy 135 | fi 136 | 137 | restore_head 138 | } 139 | 140 | initial_deploy() { 141 | git --work-tree "$deploy_directory" checkout --orphan $deploy_branch 142 | git --work-tree "$deploy_directory" add --all 143 | commit+push 144 | } 145 | 146 | incremental_deploy() { 147 | #make deploy_branch the current branch 148 | git symbolic-ref HEAD refs/heads/$deploy_branch 149 | #put the previously committed contents of deploy_branch into the index 150 | git --work-tree "$deploy_directory" reset --mixed --quiet 151 | git --work-tree "$deploy_directory" add --all 152 | 153 | set +o errexit 154 | diff=$(git --work-tree "$deploy_directory" diff --exit-code --quiet HEAD --)$? 155 | set -o errexit 156 | case $diff in 157 | 0) echo No changes to files in $deploy_directory. Skipping commit.;; 158 | 1) commit+push;; 159 | *) 160 | echo git diff exited with code $diff. Aborting. Staying on branch $deploy_branch so you can debug. To switch back to master, use: git symbolic-ref HEAD refs/heads/master && git reset --mixed >&2 161 | return $diff 162 | ;; 163 | esac 164 | } 165 | 166 | commit+push() { 167 | set_user_id 168 | git --work-tree "$deploy_directory" commit -m "$commit_message" 169 | 170 | disable_expanded_output 171 | #--quiet is important here to avoid outputting the repo URL, which may contain a secret token 172 | git push --quiet $repo $deploy_branch 173 | enable_expanded_output 174 | } 175 | 176 | #echo expanded commands as they are executed (for debugging) 177 | enable_expanded_output() { 178 | if [ $verbose ]; then 179 | set -o xtrace 180 | set +o verbose 181 | fi 182 | } 183 | 184 | #this is used to avoid outputting the repo URL, which may contain a secret token 185 | disable_expanded_output() { 186 | if [ $verbose ]; then 187 | set +o xtrace 188 | set -o verbose 189 | fi 190 | } 191 | 192 | set_user_id() { 193 | if [[ -z `git config user.name` ]]; then 194 | git config user.name "$default_username" 195 | fi 196 | if [[ -z `git config user.email` ]]; then 197 | git config user.email "$default_email" 198 | fi 199 | } 200 | 201 | restore_head() { 202 | if [[ $previous_branch = "HEAD" ]]; then 203 | #we weren't on any branch before, so just set HEAD back to the commit it was on 204 | git update-ref --no-deref HEAD $commit_hash $deploy_branch 205 | else 206 | git symbolic-ref HEAD refs/heads/$previous_branch 207 | fi 208 | 209 | git reset --mixed 210 | } 211 | 212 | filter() { 213 | sed -e "s|$repo|\$repo|g" 214 | } 215 | 216 | sanitize() { 217 | "$@" 2> >(filter 1>&2) | filter 218 | } 219 | 220 | [[ $1 = --source-only ]] || main "$@" 221 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![deny(clippy::all)] 3 | #![allow(clippy::needless_doctest_main)] 4 | #![deny(missing_docs)] 5 | 6 | use std::any::TypeId; 7 | use std::error::Error as StdError; 8 | use std::fmt::{Debug, Display, Formatter}; 9 | use std::panic::Location; 10 | 11 | /// chains an inner error kind `T` with a causing error 12 | pub struct Error { 13 | occurrence: Option, 14 | kind: T, 15 | error_cause: Option>, 16 | } 17 | 18 | /// convenience type alias 19 | pub type Result = std::result::Result>; 20 | 21 | impl Error { 22 | /// Use the `context()` or `map_context()` Result methods instead of calling this directly 23 | #[inline] 24 | pub fn new( 25 | kind: T, 26 | error_cause: Option>, 27 | occurrence: Option, 28 | ) -> Self { 29 | Self { 30 | occurrence, 31 | kind, 32 | error_cause, 33 | } 34 | } 35 | 36 | /// return the root cause of the error chain, if any exists 37 | pub fn root_cause(&self) -> Option<&(dyn StdError + 'static)> { 38 | self.iter().last() 39 | } 40 | 41 | /// Find the first error cause of type U, if any exists 42 | /// 43 | /// # Examples 44 | /// 45 | /// ```rust 46 | /// use chainerror::Context as _; 47 | /// use chainerror::ErrorDown as _; 48 | /// use std::error::Error; 49 | /// use std::io; 50 | /// 51 | /// fn do_some_io() -> Result<(), Box> { 52 | /// Err(io::Error::from(io::ErrorKind::NotFound))?; 53 | /// Ok(()) 54 | /// } 55 | /// 56 | /// chainerror::str_context!(Func2Error); 57 | /// 58 | /// fn func2() -> Result<(), Box> { 59 | /// let filename = "foo.txt"; 60 | /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 61 | /// Ok(()) 62 | /// } 63 | /// 64 | /// chainerror::str_context!(Func1Error); 65 | /// 66 | /// fn func1() -> Result<(), Box> { 67 | /// func2().context(Func1Error::new("func1 error"))?; 68 | /// Ok(()) 69 | /// } 70 | /// 71 | /// if let Err(e) = func1() { 72 | /// if let Some(f1err) = e.downcast_chain_ref::() { 73 | /// assert!(f1err.find_cause::().is_some()); 74 | /// 75 | /// assert!(f1err.find_chain_cause::().is_some()); 76 | /// } 77 | /// # else { 78 | /// # panic!(); 79 | /// # } 80 | /// } 81 | /// # else { 82 | /// # unreachable!(); 83 | /// # } 84 | /// ``` 85 | #[inline] 86 | pub fn find_cause(&self) -> Option<&U> { 87 | self.iter() 88 | .filter_map(::downcast_ref::) 89 | .next() 90 | } 91 | 92 | /// Find the first error cause of type [`Error`](Error), if any exists 93 | /// 94 | /// Same as `find_cause`, but hides the [`Error`](Error) implementation internals 95 | /// 96 | /// # Examples 97 | /// 98 | /// ```rust 99 | /// # chainerror::str_context!(FooError); 100 | /// # let err = chainerror::Error::new(String::new(), None, None); 101 | /// // Instead of writing 102 | /// err.find_cause::>(); 103 | /// 104 | /// // leave out the chainerror::Error implementation detail 105 | /// err.find_chain_cause::(); 106 | /// ``` 107 | #[inline] 108 | pub fn find_chain_cause(&self) -> Option<&Error> { 109 | self.iter() 110 | .filter_map(::downcast_ref::>) 111 | .next() 112 | } 113 | 114 | /// Find the first error cause of type [`Error`](Error) or `U`, if any exists and return `U` 115 | /// 116 | /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error`](Error) implementation internals 117 | /// 118 | /// # Examples 119 | /// 120 | /// ```rust 121 | /// # chainerror::str_context!(FooErrorKind); 122 | /// # let err = chainerror::Error::new(String::new(), None, None); 123 | /// // Instead of writing 124 | /// err.find_cause::>(); 125 | /// // and/or 126 | /// err.find_chain_cause::(); 127 | /// // and/or 128 | /// err.find_cause::(); 129 | /// 130 | /// // leave out the chainerror::Error implementation detail 131 | /// err.find_kind_or_cause::(); 132 | /// ``` 133 | #[inline] 134 | pub fn find_kind_or_cause(&self) -> Option<&U> { 135 | self.iter() 136 | .filter_map(|e| { 137 | e.downcast_ref::>() 138 | .map(|e| e.kind()) 139 | .or_else(|| e.downcast_ref::()) 140 | }) 141 | .next() 142 | } 143 | 144 | /// Return a reference to T of [`Error`](Error) 145 | /// 146 | /// # Examples 147 | /// 148 | /// ```rust 149 | /// use chainerror::Context as _; 150 | /// use std::error::Error; 151 | /// use std::io; 152 | /// 153 | /// fn do_some_io() -> Result<(), Box> { 154 | /// Err(io::Error::from(io::ErrorKind::NotFound))?; 155 | /// Ok(()) 156 | /// } 157 | /// 158 | /// chainerror::str_context!(Func2Error); 159 | /// 160 | /// fn func2() -> Result<(), Box> { 161 | /// let filename = "foo.txt"; 162 | /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 163 | /// Ok(()) 164 | /// } 165 | /// 166 | /// #[derive(Debug)] 167 | /// enum Func1ErrorKind { 168 | /// Func2, 169 | /// IO(String), 170 | /// } 171 | /// 172 | /// /// impl ::std::fmt::Display for Func1ErrorKind {…} 173 | /// # impl ::std::fmt::Display for Func1ErrorKind { 174 | /// # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 175 | /// # match self { 176 | /// # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), 177 | /// # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), 178 | /// # } 179 | /// # } 180 | /// # } 181 | /// 182 | /// fn func1() -> chainerror::Result<(), Func1ErrorKind> { 183 | /// func2().context(Func1ErrorKind::Func2)?; 184 | /// do_some_io().context(Func1ErrorKind::IO("bar.txt".into()))?; 185 | /// Ok(()) 186 | /// } 187 | /// 188 | /// if let Err(e) = func1() { 189 | /// match e.kind() { 190 | /// Func1ErrorKind::Func2 => {} 191 | /// Func1ErrorKind::IO(filename) => panic!(), 192 | /// } 193 | /// } 194 | /// # else { 195 | /// # unreachable!(); 196 | /// # } 197 | /// ``` 198 | #[inline] 199 | pub fn kind(&self) -> &T { 200 | &self.kind 201 | } 202 | 203 | /// Returns an Iterator over all error causes/sources 204 | /// 205 | /// # Example 206 | #[inline] 207 | pub fn iter(&self) -> impl Iterator { 208 | ErrorIter { 209 | current: Some(self), 210 | } 211 | } 212 | } 213 | 214 | /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) 215 | pub trait Context>> { 216 | /// Decorate the error with a `kind` of type `T` and the source `Location` 217 | fn context(self, kind: T) -> std::result::Result>; 218 | 219 | /// Decorate the error just with the source `Location` 220 | fn annotate(self) -> std::result::Result>; 221 | 222 | /// Decorate the `error` with a `kind` of type `T` produced with a `FnOnce(&error)` and the source `Location` 223 | fn map_context T>( 224 | self, 225 | op: F, 226 | ) -> std::result::Result>; 227 | } 228 | 229 | /// Convenience type to just decorate the error with the source `Location` 230 | pub struct AnnotatedError(()); 231 | 232 | impl Display for AnnotatedError { 233 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 234 | write!(f, "(passed error)") 235 | } 236 | } 237 | 238 | impl Debug for AnnotatedError { 239 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 240 | write!(f, "(passed error)") 241 | } 242 | } 243 | 244 | impl>> Context 245 | for std::result::Result 246 | { 247 | #[track_caller] 248 | #[inline] 249 | fn context(self, kind: T) -> std::result::Result> { 250 | match self { 251 | Ok(t) => Ok(t), 252 | Err(error_cause) => Err(Error::new( 253 | kind, 254 | Some(error_cause.into()), 255 | Some(Location::caller().to_string()), 256 | )), 257 | } 258 | } 259 | 260 | #[track_caller] 261 | #[inline] 262 | fn annotate(self) -> std::result::Result> { 263 | match self { 264 | Ok(t) => Ok(t), 265 | Err(error_cause) => Err(Error::new( 266 | AnnotatedError(()), 267 | Some(error_cause.into()), 268 | Some(Location::caller().to_string()), 269 | )), 270 | } 271 | } 272 | 273 | #[track_caller] 274 | #[inline] 275 | fn map_context T>( 276 | self, 277 | op: F, 278 | ) -> std::result::Result> { 279 | match self { 280 | Ok(t) => Ok(t), 281 | Err(error_cause) => { 282 | let kind = op(&error_cause); 283 | Err(Error::new( 284 | kind, 285 | Some(error_cause.into()), 286 | Some(Location::caller().to_string()), 287 | )) 288 | } 289 | } 290 | } 291 | } 292 | 293 | /// An iterator over all error causes/sources 294 | pub struct ErrorIter<'a> { 295 | current: Option<&'a (dyn StdError + 'static)>, 296 | } 297 | 298 | impl<'a> Iterator for ErrorIter<'a> { 299 | type Item = &'a (dyn StdError + 'static); 300 | 301 | #[inline] 302 | fn next(&mut self) -> Option { 303 | let current = self.current; 304 | self.current = self.current.and_then(StdError::source); 305 | current 306 | } 307 | } 308 | 309 | impl std::ops::Deref for Error { 310 | type Target = T; 311 | 312 | #[inline] 313 | fn deref(&self) -> &Self::Target { 314 | &self.kind 315 | } 316 | } 317 | 318 | /// Convenience trait to hide the [`Error`](Error) implementation internals 319 | pub trait ErrorDown { 320 | /// Test if of type `Error` 321 | fn is_chain(&self) -> bool; 322 | /// Downcast to a reference of `Error` 323 | fn downcast_chain_ref(&self) -> Option<&Error>; 324 | /// Downcast to a mutable reference of `Error` 325 | fn downcast_chain_mut(&mut self) -> Option<&mut Error>; 326 | /// Downcast to T of `Error` 327 | fn downcast_inner_ref(&self) -> Option<&T>; 328 | /// Downcast to T mutable reference of `Error` 329 | fn downcast_inner_mut(&mut self) -> Option<&mut T>; 330 | } 331 | 332 | impl ErrorDown for Error { 333 | #[inline] 334 | fn is_chain(&self) -> bool { 335 | TypeId::of::() == TypeId::of::() 336 | } 337 | 338 | #[inline] 339 | fn downcast_chain_ref(&self) -> Option<&Error> { 340 | if self.is_chain::() { 341 | // Use transmute when we've verified the types match 342 | unsafe { Some(std::mem::transmute::<&Error, &Error>(self)) } 343 | } else { 344 | None 345 | } 346 | } 347 | 348 | #[inline] 349 | fn downcast_chain_mut(&mut self) -> Option<&mut Error> { 350 | if self.is_chain::() { 351 | // Use transmute when we've verified the types match 352 | unsafe { Some(std::mem::transmute::<&mut Error, &mut Error>(self)) } 353 | } else { 354 | None 355 | } 356 | } 357 | #[inline] 358 | fn downcast_inner_ref(&self) -> Option<&T> { 359 | if self.is_chain::() { 360 | // Use transmute when we've verified the types match 361 | unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) } 362 | } else { 363 | None 364 | } 365 | } 366 | 367 | #[inline] 368 | fn downcast_inner_mut(&mut self) -> Option<&mut T> { 369 | if self.is_chain::() { 370 | // Use transmute when we've verified the types match 371 | unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) } 372 | } else { 373 | None 374 | } 375 | } 376 | } 377 | 378 | impl ErrorDown for dyn StdError + 'static { 379 | #[inline] 380 | fn is_chain(&self) -> bool { 381 | self.is::>() 382 | } 383 | 384 | #[inline] 385 | fn downcast_chain_ref(&self) -> Option<&Error> { 386 | self.downcast_ref::>() 387 | } 388 | 389 | #[inline] 390 | fn downcast_chain_mut(&mut self) -> Option<&mut Error> { 391 | self.downcast_mut::>() 392 | } 393 | 394 | #[inline] 395 | fn downcast_inner_ref(&self) -> Option<&T> { 396 | self.downcast_ref::() 397 | .or_else(|| self.downcast_ref::>().map(|e| e.kind())) 398 | } 399 | 400 | #[inline] 401 | fn downcast_inner_mut(&mut self) -> Option<&mut T> { 402 | if self.is::() { 403 | return self.downcast_mut::(); 404 | } 405 | 406 | self.downcast_mut::>() 407 | .and_then(|e| e.downcast_inner_mut::()) 408 | } 409 | } 410 | 411 | impl ErrorDown for dyn StdError + 'static + Send { 412 | #[inline] 413 | fn is_chain(&self) -> bool { 414 | self.is::>() 415 | } 416 | 417 | #[inline] 418 | fn downcast_chain_ref(&self) -> Option<&Error> { 419 | self.downcast_ref::>() 420 | } 421 | 422 | #[inline] 423 | fn downcast_chain_mut(&mut self) -> Option<&mut Error> { 424 | self.downcast_mut::>() 425 | } 426 | 427 | #[inline] 428 | fn downcast_inner_ref(&self) -> Option<&T> { 429 | self.downcast_ref::() 430 | .or_else(|| self.downcast_ref::>().map(|e| e.kind())) 431 | } 432 | 433 | #[inline] 434 | fn downcast_inner_mut(&mut self) -> Option<&mut T> { 435 | if self.is::() { 436 | return self.downcast_mut::(); 437 | } 438 | 439 | self.downcast_mut::>() 440 | .and_then(|e| e.downcast_inner_mut::()) 441 | } 442 | } 443 | 444 | impl ErrorDown for dyn StdError + 'static + Send + Sync { 445 | #[inline] 446 | fn is_chain(&self) -> bool { 447 | self.is::>() 448 | } 449 | 450 | #[inline] 451 | fn downcast_chain_ref(&self) -> Option<&Error> { 452 | self.downcast_ref::>() 453 | } 454 | 455 | #[inline] 456 | fn downcast_chain_mut(&mut self) -> Option<&mut Error> { 457 | self.downcast_mut::>() 458 | } 459 | 460 | #[inline] 461 | fn downcast_inner_ref(&self) -> Option<&T> { 462 | self.downcast_ref::() 463 | .or_else(|| self.downcast_ref::>().map(|e| e.kind())) 464 | } 465 | 466 | #[inline] 467 | fn downcast_inner_mut(&mut self) -> Option<&mut T> { 468 | if self.is::() { 469 | return self.downcast_mut::(); 470 | } 471 | 472 | self.downcast_mut::>() 473 | .and_then(|e| e.downcast_inner_mut::()) 474 | } 475 | } 476 | 477 | impl StdError for Error { 478 | #[inline] 479 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 480 | self.error_cause 481 | .as_ref() 482 | .map(|e| e.as_ref() as &(dyn StdError + 'static)) 483 | } 484 | } 485 | 486 | impl StdError for &mut Error { 487 | #[inline] 488 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 489 | self.error_cause 490 | .as_ref() 491 | .map(|e| e.as_ref() as &(dyn StdError + 'static)) 492 | } 493 | } 494 | 495 | impl Display for Error { 496 | #[inline] 497 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 498 | write!(f, "{}", self.kind)?; 499 | 500 | if f.alternate() { 501 | if let Some(e) = self.source() { 502 | write!(f, "\nCaused by:\n {:#}", &e)?; 503 | } 504 | } 505 | 506 | Ok(()) 507 | } 508 | } 509 | 510 | impl Debug for Error { 511 | #[inline] 512 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 513 | if f.alternate() { 514 | let mut f = f.debug_struct(&format!("Error<{}>", std::any::type_name::())); 515 | 516 | let f = f 517 | .field("occurrence", &self.occurrence) 518 | .field("kind", &self.kind) 519 | .field("source", &self.source()); 520 | 521 | f.finish() 522 | } else { 523 | if let Some(ref o) = self.occurrence { 524 | write!(f, "{}: ", o)?; 525 | } 526 | 527 | if TypeId::of::() == TypeId::of::() 528 | || TypeId::of::<&str>() == TypeId::of::() 529 | { 530 | Display::fmt(&self.kind, f)?; 531 | } else { 532 | Debug::fmt(&self.kind, f)?; 533 | } 534 | 535 | if let Some(e) = self.source() { 536 | write!(f, "\nCaused by:\n{:?}", &e)?; 537 | } 538 | Ok(()) 539 | } 540 | } 541 | } 542 | 543 | impl From for Error 544 | where 545 | T: 'static + Display + Debug, 546 | { 547 | #[track_caller] 548 | #[inline] 549 | fn from(e: T) -> Error { 550 | Error::new(e, None, Some(Location::caller().to_string())) 551 | } 552 | } 553 | /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T 554 | /// 555 | /// # Examples 556 | /// 557 | /// ```rust 558 | /// # use chainerror::Context as _; 559 | /// # use chainerror::ErrorDown as _; 560 | /// # use std::error::Error; 561 | /// # use std::io; 562 | /// # use std::result::Result; 563 | /// # fn do_some_io() -> Result<(), Box> { 564 | /// # Err(io::Error::from(io::ErrorKind::NotFound))?; 565 | /// # Ok(()) 566 | /// # } 567 | /// chainerror::str_context!(Func2Error); 568 | /// 569 | /// fn func2() -> chainerror::Result<(), Func2Error> { 570 | /// let filename = "foo.txt"; 571 | /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; 572 | /// Ok(()) 573 | /// } 574 | /// 575 | /// chainerror::str_context!(Func1Error); 576 | /// 577 | /// fn func1() -> Result<(), Box> { 578 | /// func2().context(Func1Error::new("func1 error"))?; 579 | /// Ok(()) 580 | /// } 581 | /// # if let Err(e) = func1() { 582 | /// # if let Some(f1err) = e.downcast_chain_ref::() { 583 | /// # assert!(f1err.find_cause::>().is_some()); 584 | /// # assert!(f1err.find_chain_cause::().is_some()); 585 | /// # } else { 586 | /// # panic!(); 587 | /// # } 588 | /// # } else { 589 | /// # unreachable!(); 590 | /// # } 591 | /// ``` 592 | #[macro_export] 593 | macro_rules! str_context { 594 | ($e:ident) => { 595 | #[derive(Clone)] 596 | pub struct $e(pub String); 597 | impl $e { 598 | #[allow(dead_code)] 599 | pub fn new>(s: S) -> Self { 600 | $e(s.into()) 601 | } 602 | } 603 | impl ::std::fmt::Display for $e { 604 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 605 | write!(f, "{}", self.0) 606 | } 607 | } 608 | impl ::std::fmt::Debug for $e { 609 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 610 | write!(f, "{}({})", stringify!($e), self.0) 611 | } 612 | } 613 | impl ::std::error::Error for $e {} 614 | }; 615 | } 616 | 617 | /// Derive an Error for an ErrorKind, which wraps a [`Error`](Error) and implements a `kind()` method 618 | /// 619 | /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) 620 | /// method. 621 | /// 622 | /// Error::kind() returns the ErrorKind 623 | /// Error::source() returns the parent error 624 | /// 625 | /// # Examples 626 | /// 627 | /// ```rust 628 | /// use chainerror::Context as _; 629 | /// use std::io; 630 | /// 631 | /// fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { 632 | /// return Err(io::Error::from(io::ErrorKind::NotFound)); 633 | /// } 634 | /// 635 | /// #[derive(Debug, Clone)] 636 | /// pub enum ErrorKind { 637 | /// IO(String), 638 | /// FatalError(String), 639 | /// Unknown, 640 | /// } 641 | /// 642 | /// chainerror::err_kind!(Error, ErrorKind); 643 | /// 644 | /// impl std::fmt::Display for ErrorKind { 645 | /// fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { 646 | /// match self { 647 | /// ErrorKind::FatalError(e) => write!(f, "fatal error {}", e), 648 | /// ErrorKind::Unknown => write!(f, "unknown error"), 649 | /// ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), 650 | /// } 651 | /// } 652 | /// } 653 | /// 654 | /// impl ErrorKind { 655 | /// fn from_io_error(e: &io::Error, f: String) -> Self { 656 | /// match e.kind() { 657 | /// io::ErrorKind::BrokenPipe => panic!("Should not happen"), 658 | /// io::ErrorKind::ConnectionReset => { 659 | /// ErrorKind::FatalError(format!("While reading `{}`: {}", f, e)) 660 | /// } 661 | /// _ => ErrorKind::IO(f), 662 | /// } 663 | /// } 664 | /// } 665 | /// 666 | /// impl From<&io::Error> for ErrorKind { 667 | /// fn from(e: &io::Error) -> Self { 668 | /// ErrorKind::IO(format!("{}", e)) 669 | /// } 670 | /// } 671 | /// 672 | /// pub fn func1() -> std::result::Result<(), Error> { 673 | /// let filename = "bar.txt"; 674 | /// 675 | /// do_some_io(filename).map_context(|e| ErrorKind::from_io_error(e, filename.into()))?; 676 | /// do_some_io(filename).map_context(|e| ErrorKind::IO(filename.into()))?; 677 | /// do_some_io(filename).map_context(|e| ErrorKind::from(e))?; 678 | /// Ok(()) 679 | /// } 680 | /// 681 | /// # fn main() { 682 | /// # if let Err(e) = func1() { 683 | /// # eprintln!("Error:\n{:?}", e); 684 | /// # } 685 | /// # } 686 | /// ``` 687 | #[macro_export] 688 | macro_rules! err_kind { 689 | ($e:ident, $k:ident) => { 690 | pub struct $e($crate::Error<$k>); 691 | 692 | impl $e { 693 | pub fn kind(&self) -> &$k { 694 | self.0.kind() 695 | } 696 | } 697 | 698 | impl From<$k> for $e { 699 | fn from(e: $k) -> Self { 700 | $e($crate::Error::new(e, None, None)) 701 | } 702 | } 703 | 704 | impl From<$crate::Error<$k>> for $e { 705 | fn from(e: $crate::Error<$k>) -> Self { 706 | $e(e) 707 | } 708 | } 709 | 710 | impl From<&$e> for $k 711 | where 712 | $k: Clone, 713 | { 714 | fn from(e: &$e) -> Self { 715 | e.kind().clone() 716 | } 717 | } 718 | 719 | impl std::error::Error for $e { 720 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 721 | self.0.source() 722 | } 723 | } 724 | 725 | impl std::fmt::Display for $e { 726 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 727 | std::fmt::Display::fmt(&self.0, f) 728 | } 729 | } 730 | 731 | impl std::fmt::Debug for $e { 732 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 733 | std::fmt::Debug::fmt(&self.0, f) 734 | } 735 | } 736 | }; 737 | } 738 | 739 | #[cfg(test)] 740 | mod tests { 741 | use super::Context as _; 742 | use super::*; 743 | use std::io; 744 | 745 | #[test] 746 | fn test_error_chain_with_multiple_causes() { 747 | // Create a chain of errors 748 | let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); 749 | 750 | str_context!(Level3Error); 751 | str_context!(Level2Error); 752 | str_context!(Level1Error); 753 | 754 | let err = Result::<(), _>::Err(io_error.into()) 755 | .context(Level3Error("level 3".into())) 756 | .context(Level2Error("level 2".into())) 757 | .context(Level1Error("level 1".into())) 758 | .unwrap_err(); 759 | 760 | // Test the error chain 761 | assert!(err.is_chain::()); 762 | assert!(err.find_chain_cause::().is_some()); 763 | assert!(err.find_chain_cause::().is_some()); 764 | assert!(err.find_chain_cause::().is_some()); 765 | } 766 | 767 | #[test] 768 | fn test_error_root_cause() { 769 | let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); 770 | 771 | str_context!(WrapperError); 772 | let err = Result::<(), _>::Err(io_error.into()) 773 | .context(WrapperError("wrapper".into())) 774 | .unwrap_err(); 775 | 776 | let root = err.root_cause().unwrap(); 777 | assert!(root.is_chain::()); 778 | } 779 | 780 | #[test] 781 | fn test_error_display_and_debug() { 782 | str_context!(CustomError); 783 | let err = Error::new( 784 | CustomError("test error".into()), 785 | None, 786 | Some("src/lib.rs:100".into()), 787 | ); 788 | 789 | // Test Display formatting 790 | assert_eq!(format!("{}", err), "test error"); 791 | 792 | // Test alternate Display formatting 793 | assert_eq!(format!("{:#}", err), "test error"); 794 | 795 | // Test Debug formatting 796 | let debug_output = format!("{:?}", err); 797 | assert!(debug_output.contains("test error")); 798 | assert!(debug_output.contains("src/lib.rs:100")); 799 | } 800 | 801 | #[test] 802 | fn test_error_annotation() { 803 | let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); 804 | let err = Result::<(), _>::Err(io_error.into()) 805 | .annotate() 806 | .unwrap_err(); 807 | 808 | assert!(err.source().is_some()); 809 | err.source() 810 | .unwrap() 811 | .downcast_inner_ref::() 812 | .unwrap(); 813 | } 814 | 815 | #[test] 816 | fn test_map_context() { 817 | let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); 818 | 819 | str_context!(MappedError); 820 | let err = Result::<(), _>::Err(io_error.into()) 821 | .map_context(|e| MappedError(format!("Mapped: {}", e))) 822 | .unwrap_err(); 823 | 824 | assert!(err.is_chain::()); 825 | assert!(err.find_chain_cause::().is_some()); 826 | } 827 | 828 | #[test] 829 | fn test_error_downcasting() { 830 | str_context!(OriginalError); 831 | let original = Error::new(OriginalError("test".into()), None, None); 832 | 833 | let error: Box = Box::new(original); 834 | 835 | // Test downcast_chain_ref 836 | assert!(error.is_chain::()); 837 | assert!(error.downcast_chain_ref::().is_some()); 838 | 839 | // Test downcast_inner_ref 840 | let inner = error.downcast_inner_ref::(); 841 | assert!(inner.is_some()); 842 | } 843 | 844 | #[derive(Debug, Clone)] 845 | enum TestErrorKind { 846 | Basic(String), 847 | Complex { message: String }, 848 | } 849 | 850 | impl Display for TestErrorKind { 851 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 852 | match self { 853 | TestErrorKind::Basic(msg) => write!(f, "Basic error: {}", msg), 854 | TestErrorKind::Complex { message } => write!(f, "Complex error: {}", message), 855 | } 856 | } 857 | } 858 | 859 | #[test] 860 | fn test_err_kind_macro() { 861 | err_kind!(TestError, TestErrorKind); 862 | 863 | let err = TestError::from(TestErrorKind::Basic("test".into())); 864 | assert!(matches!(err.kind(), TestErrorKind::Basic(_))); 865 | // The annotated error should display "(passed error)" even in a chain 866 | assert_eq!(format!("{}", err), "Basic error: test"); 867 | assert_eq!(format!("{:?}", err), "Basic(\"test\")"); 868 | 869 | let complex_err = TestError::from(TestErrorKind::Complex { 870 | message: "test".into(), 871 | }); 872 | assert!(matches!(complex_err.kind(), TestErrorKind::Complex { .. })); 873 | // The annotated error should display "(passed error)" even in a chain 874 | assert_eq!(format!("{}", complex_err), "Complex error: test"); 875 | assert_eq!( 876 | format!("{:?}", complex_err), 877 | "Complex { message: \"test\" }" 878 | ); 879 | } 880 | #[test] 881 | fn test_annotated_error_display_and_debug() { 882 | let annotated = AnnotatedError(()); 883 | 884 | // Test Display formatting 885 | assert_eq!(format!("{}", annotated), "(passed error)"); 886 | 887 | // Test Debug formatting 888 | assert_eq!(format!("{:?}", annotated), "(passed error)"); 889 | 890 | // Test with error chain 891 | let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); 892 | let err = Result::<(), _>::Err(io_error.into()) 893 | .annotate() 894 | .unwrap_err(); 895 | 896 | // The annotated error should display "(passed error)" even in a chain 897 | assert_eq!(format!("{}", err), "(passed error)"); 898 | assert!(format!("{:?}", err).contains("(passed error)")); 899 | 900 | // Verify the error chain is preserved 901 | assert!(err.source().is_some()); 902 | assert!(err.source().unwrap().is_chain::()); 903 | } 904 | 905 | // Helper error types for testing 906 | #[derive(Debug)] 907 | struct TestError(String); 908 | 909 | impl std::fmt::Display for TestError { 910 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 911 | write!(f, "{}", self.0) 912 | } 913 | } 914 | 915 | impl std::error::Error for TestError {} 916 | 917 | #[test] 918 | fn test_downcast_chain_operations() { 919 | // Create a test error chain 920 | let original_error = Error::new( 921 | TestError("test message".to_string()), 922 | None, 923 | Some("test location".to_string()), 924 | ); 925 | 926 | // Test is_chain 927 | assert!(original_error.is_chain::()); 928 | assert!(!original_error.is_chain::()); 929 | 930 | // Test downcast_chain_ref 931 | let downcast_ref = original_error.downcast_chain_ref::(); 932 | assert!(downcast_ref.is_some()); 933 | let downcast_kind = downcast_ref.unwrap().kind(); 934 | assert_eq!(format!("{}", downcast_kind), "test message"); 935 | assert_eq!( 936 | format!("{:?}", downcast_kind), 937 | "TestError(\"test message\")" 938 | ); 939 | 940 | // Test invalid downcast_chain_ref 941 | let invalid_downcast = original_error.downcast_chain_ref::(); 942 | assert!(invalid_downcast.is_none()); 943 | 944 | // Test downcast_chain_mut 945 | let mut mutable_error = original_error; 946 | let downcast_mut = mutable_error.downcast_chain_mut::(); 947 | assert!(downcast_mut.is_some()); 948 | assert_eq!(downcast_mut.unwrap().kind().0, "test message"); 949 | 950 | // Test invalid downcast_chain_mut 951 | let invalid_downcast_mut = mutable_error.downcast_chain_mut::(); 952 | assert!(invalid_downcast_mut.is_none()); 953 | } 954 | 955 | #[test] 956 | fn test_downcast_inner_operations() { 957 | // Create a test error 958 | let mut error = Error::new( 959 | TestError("inner test".to_string()), 960 | None, 961 | Some("test location".to_string()), 962 | ); 963 | 964 | // Test downcast_inner_ref 965 | let inner_ref = error.downcast_inner_ref::(); 966 | assert!(inner_ref.is_some()); 967 | assert_eq!(inner_ref.unwrap().0, "inner test"); 968 | // Test invalid downcast_inner_ref 969 | let invalid_inner = error.downcast_inner_ref::(); 970 | assert!(invalid_inner.is_none()); 971 | 972 | // Test downcast_inner_mut 973 | let inner_mut = error.downcast_inner_mut::(); 974 | assert!(inner_mut.is_some()); 975 | assert_eq!(inner_mut.unwrap().0, "inner test"); 976 | 977 | // Test invalid downcast_inner_mut 978 | let invalid_inner_mut = error.downcast_inner_mut::(); 979 | assert!(invalid_inner_mut.is_none()); 980 | } 981 | 982 | #[test] 983 | fn test_error_down_for_dyn_error() { 984 | // Create a boxed error 985 | let error: Box = Box::new(Error::new( 986 | TestError("dyn test".to_string()), 987 | None, 988 | Some("test location".to_string()), 989 | )); 990 | 991 | // Test is_chain through trait object 992 | assert!(error.is_chain::()); 993 | assert!(!error.is_chain::()); 994 | 995 | // Test downcast_chain_ref through trait object 996 | let chain_ref = error.downcast_chain_ref::(); 997 | assert!(chain_ref.is_some()); 998 | assert_eq!(chain_ref.unwrap().kind().0, "dyn test"); 999 | 1000 | // Test downcast_inner_ref through trait object 1001 | let inner_ref = error.downcast_inner_ref::(); 1002 | assert!(inner_ref.is_some()); 1003 | assert_eq!(inner_ref.unwrap().0, "dyn test"); 1004 | } 1005 | 1006 | #[test] 1007 | fn test_error_down_with_sync_send() { 1008 | // Create a boxed error with Send + Sync 1009 | let error: Box = Box::new(Error::new( 1010 | TestError("sync test".to_string()), 1011 | None, 1012 | Some("test location".to_string()), 1013 | )); 1014 | 1015 | // Test operations on Send + Sync error 1016 | assert!(error.is_chain::()); 1017 | assert!(error.downcast_chain_ref::().is_some()); 1018 | assert!(error.downcast_inner_ref::().is_some()); 1019 | 1020 | // Test invalid downcasts 1021 | assert!(!error.is_chain::()); 1022 | assert!(error.downcast_chain_ref::().is_none()); 1023 | assert!(error.downcast_inner_ref::().is_none()); 1024 | } 1025 | } 1026 | -------------------------------------------------------------------------------- /tests/test_basic.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context; 2 | 3 | #[test] 4 | fn test_basic() { 5 | use std::path::PathBuf; 6 | type BoxedError = Box; 7 | fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { 8 | // do stuff, return other errors 9 | let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; 10 | // do stuff, return other errors 11 | Ok(()) 12 | } 13 | fn process_config_file() -> Result<(), BoxedError> { 14 | // do stuff, return other errors 15 | read_config_file("_non_existent.txt".into()).context("read the config file")?; 16 | // do stuff, return other errors 17 | Ok(()) 18 | } 19 | 20 | if let Err(e) = process_config_file() { 21 | let os_notfound_error = std::io::Error::from_raw_os_error(2); 22 | let s = format!("{:?}", e); 23 | let lines = s.lines().collect::>(); 24 | assert_eq!(lines.len(), 5); 25 | assert!(lines[0].starts_with("tests/test_basic.rs:")); 26 | assert_eq!(lines[1], "Caused by:"); 27 | assert!(lines[2].starts_with("tests/test_basic.rs:")); 28 | assert_eq!(lines[3], "Caused by:"); 29 | assert_eq!(lines[4], format!("{:?}", os_notfound_error)); 30 | } else { 31 | panic!(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/test_iter.rs: -------------------------------------------------------------------------------- 1 | use chainerror::Context; 2 | use std::error::Error; 3 | use std::io; 4 | 5 | #[test] 6 | fn test_iter() -> Result<(), Box> { 7 | use std::fmt::Write; 8 | let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); 9 | let err = err.context("1"); 10 | let err = err.context("2"); 11 | let err = err.context("3"); 12 | let err = err.context("4"); 13 | let err = err.context("5"); 14 | let err = err.context("6"); 15 | let err = err.err().unwrap(); 16 | 17 | let mut res = String::new(); 18 | 19 | for e in err.iter() { 20 | write!(res, "{}", e)?; 21 | } 22 | assert_eq!(res, "654321entity not found"); 23 | 24 | let io_error: Option<&io::Error> = err 25 | .iter() 26 | .filter_map(::downcast_ref::) 27 | .next(); 28 | 29 | assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); 30 | 31 | Ok(()) 32 | } 33 | 34 | #[test] 35 | fn test_iter_alternate() -> Result<(), Box> { 36 | let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); 37 | let err = err.context("1"); 38 | let err = err.context("2"); 39 | let err = err.context("3"); 40 | let err = err.context("4"); 41 | let err = err.context("5"); 42 | let err = err.context("6"); 43 | let err = err.err().unwrap(); 44 | 45 | let res = format!("{:#}", err); 46 | 47 | assert_eq!(res, format!("6\nCaused by:\n 5\nCaused by:\n 4\nCaused by:\n 3\nCaused by:\n 2\nCaused by:\n 1\nCaused by:\n {:#}", io::Error::from(io::ErrorKind::NotFound))); 48 | 49 | let io_error: Option<&io::Error> = err 50 | .iter() 51 | .filter_map(::downcast_ref::) 52 | .next(); 53 | 54 | assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); 55 | 56 | Ok(()) 57 | } 58 | 59 | #[test] 60 | fn test_find_cause() -> Result<(), Box> { 61 | let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); 62 | let err = err.context("1"); 63 | let err = err.context("2"); 64 | let err = err.context("3"); 65 | let err = err.context("4"); 66 | let err = err.context("5"); 67 | let err = err.context("6"); 68 | let err = err.err().unwrap(); 69 | 70 | let io_error: Option<&io::Error> = err.find_cause::(); 71 | 72 | assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); 73 | 74 | Ok(()) 75 | } 76 | 77 | #[test] 78 | fn test_root_cause() -> Result<(), Box> { 79 | let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); 80 | let err = err.context("1"); 81 | let err = err.context("2"); 82 | let err = err.context("3"); 83 | let err = err.context("4"); 84 | let err = err.context("5"); 85 | let err = err.context("6"); 86 | let err = err.err().unwrap(); 87 | 88 | let err: Option<&(dyn std::error::Error + 'static)> = err.root_cause(); 89 | let io_error: Option<&io::Error> = err.and_then(::downcast_ref::); 90 | 91 | assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); 92 | 93 | Ok(()) 94 | } 95 | --------------------------------------------------------------------------------