├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── color-eyre ├── .github │ └── workflows │ │ └── ci.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples │ ├── custom_filter.rs │ ├── custom_section.rs │ ├── debug_perf.rs │ ├── github_issue.rs │ ├── multiple_errors.rs │ ├── panic_compose.rs │ ├── panic_hook.rs │ ├── theme.rs │ ├── theme_test_helper.rs │ └── usage.rs ├── pictures │ ├── custom_section.png │ ├── full.png │ ├── minimal.png │ └── short.png ├── scripts │ ├── default.nix │ └── fix_html_examples.py ├── src │ ├── config.rs │ ├── fmt.rs │ ├── handler.rs │ ├── lib.rs │ ├── private.rs │ ├── section │ │ ├── github.rs │ │ ├── help.rs │ │ └── mod.rs │ └── writers.rs └── tests │ ├── bt_disabled.rs │ ├── bt_enabled.rs │ ├── data │ ├── theme_error_control.txt │ ├── theme_error_control_location.txt │ ├── theme_error_control_minimal.txt │ ├── theme_error_control_spantrace.txt │ ├── theme_panic_control.txt │ └── theme_panic_control_no_spantrace.txt │ ├── install.rs │ ├── location_disabled.rs │ ├── theme.rs │ └── wasm.rs ├── color-spantrace ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── examples │ └── color-spantrace-usage.rs ├── pictures │ ├── full.png │ └── minimal.png ├── src │ └── lib.rs └── tests │ ├── data │ └── theme_control.txt │ └── themes.rs └── eyre ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── examples ├── custom_handler.rs └── eyre-usage.rs ├── src ├── backtrace.rs ├── chain.rs ├── context.rs ├── error.rs ├── error │ └── pyo3_compat.rs ├── fmt.rs ├── kind.rs ├── lib.rs ├── macros.rs ├── option.rs ├── ptr.rs └── wrapper.rs └── tests ├── common └── mod.rs ├── compiletest.rs ├── drop └── mod.rs ├── generic_member_access.rs ├── test_autotrait.rs ├── test_boxed.rs ├── test_chain.rs ├── test_context.rs ├── test_context_access.rs ├── test_convert.rs ├── test_downcast.rs ├── test_fmt.rs ├── test_location.rs ├── test_macros.rs ├── test_no_install.rs ├── test_option.rs ├── test_pyo3.rs ├── test_repr.rs └── test_source.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | text eol=lf 2 | 3 | * text 4 | 5 | *.png binary 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: {} 6 | 7 | name: Continuous integration 8 | 9 | env: 10 | MIRIFLAGS: -Zmiri-strict-provenance 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'push' && github.run_number }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | check: 18 | name: Check 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | rust: 23 | - stable 24 | steps: 25 | - uses: actions/checkout@v1 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: ${{ matrix.rust }} 29 | override: true 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: check 33 | args: --all 34 | 35 | test-matrix: 36 | name: Test Suite 37 | runs-on: ubuntu-latest 38 | strategy: 39 | matrix: 40 | rust: 41 | - stable 42 | - beta 43 | - nightly 44 | features: 45 | - # default 46 | - --no-default-features 47 | - --no-default-features --features track-caller 48 | - --no-default-features --features auto-install 49 | - --features pyo3 50 | - --all-features 51 | steps: 52 | - uses: actions/checkout@v1 53 | - uses: actions-rs/toolchain@v1 54 | with: 55 | toolchain: ${{ matrix.rust }} 56 | override: true 57 | - uses: actions-rs/cargo@v1 58 | with: 59 | command: test 60 | args: --all ${{ matrix.features }} 61 | 62 | test-msrv: 63 | name: Test Suite 64 | runs-on: ubuntu-latest 65 | strategy: 66 | matrix: 67 | features: 68 | - # default 69 | - --no-default-features 70 | - --no-default-features --features track-caller 71 | - --no-default-features --features auto-install 72 | # skip `--features pyo3` and `--all-features` because pyo3 doesn't support this msrv 73 | steps: 74 | - uses: actions/checkout@v1 75 | - uses: actions-rs/toolchain@v1 76 | with: 77 | toolchain: 1.65 78 | override: true 79 | - uses: actions-rs/cargo@v1 80 | with: 81 | command: test 82 | args: --all ${{ matrix.features }} 83 | 84 | test-os: 85 | name: Test Suite 86 | runs-on: ${{ matrix.os }} 87 | strategy: 88 | matrix: 89 | os: [ubuntu-latest, windows-latest, macOS-latest] 90 | steps: 91 | - uses: actions/checkout@v1 92 | - uses: actions-rs/toolchain@v1 93 | with: 94 | toolchain: stable 95 | profile: minimal 96 | override: true 97 | - uses: actions-rs/cargo@v1 98 | with: 99 | command: test 100 | args: --all 101 | 102 | fmt: 103 | name: Rustfmt 104 | runs-on: ubuntu-latest 105 | strategy: 106 | matrix: 107 | rust: 108 | - stable 109 | steps: 110 | - uses: actions/checkout@v1 111 | - uses: actions-rs/toolchain@v1 112 | with: 113 | toolchain: ${{ matrix.rust }} 114 | override: true 115 | - run: rustup component add rustfmt 116 | - uses: actions-rs/cargo@v1 117 | with: 118 | command: fmt 119 | args: --check 120 | 121 | clippy: 122 | name: Clippy 123 | runs-on: ubuntu-latest 124 | strategy: 125 | matrix: 126 | rust: 127 | - stable 128 | steps: 129 | - uses: actions/checkout@v1 130 | - uses: actions-rs/toolchain@v1 131 | with: 132 | toolchain: ${{ matrix.rust }} 133 | override: true 134 | - run: rustup component add clippy 135 | - uses: actions-rs/cargo@v1 136 | with: 137 | command: clippy 138 | args: --all-targets --all-features -- -D clippy::style -D clippy::suspicious -D clippy::complexity 139 | miri: 140 | name: Miri 141 | runs-on: ubuntu-latest 142 | steps: 143 | - uses: actions/checkout@v1 144 | - uses: actions-rs/toolchain@v1 145 | with: 146 | toolchain: nightly 147 | components: miri 148 | override: true 149 | - uses: actions-rs/cargo@v1 150 | with: 151 | command: miri 152 | args: test 153 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Welcome to the eyre contributing guide 2 | 3 | Thank you for investing your time in contributing to our project! Eyre is a 4 | community owned and maintained project dedicated to improving the error 5 | handling and error reporting experience of users of the Rust programming 6 | language. 7 | 8 | Check out our community's[^1] [Code of 9 | Conduct](https://www.rust-lang.org/policies/code-of-conduct) and feel free to 10 | say hi on [Discord] if you'd like. It's a nice place to chat about eyre 11 | development, ask questions, and get to know the other contributors and users in 12 | a less formal setting. 13 | 14 | ## The Eyre Organization 15 | 16 | The Eyre Organization is the group of people responsible for stewarding the 17 | Eyre project. It handles things like merging pull requests, choosing project 18 | direction, managing bugs / issues / feature requests, controlling access to 19 | secrets, defining and enforcing best practices, etc. 20 | 21 | The eyre organization's governance is based on and inspired by 22 | [sociocracy](https://www.sociocracyforall.org/sociocracy/), the Rust Project, 23 | and the Bevy Organization. Many thanks to their great examples and resources. 24 | 25 | Note that you *do not* need to be a member of the Eyre Organization to 26 | contribute to Eyre. Community contributors (this means you) can freely open 27 | issues, submit pull requests, and review pull requests. 28 | 29 | ### New contributor guide 30 | 31 | To get an overview of the project, read the [README](README.md). Here are some 32 | resources to help you get started with open source contributions: 33 | 34 | - [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github) 35 | - [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git) 36 | - [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) 37 | - [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests) 38 | 39 | Your first PR will be merged in no time! 40 | 41 | No matter how you're helping: thank you for contributing to Eyre! 42 | 43 | ### Classifying PRs 44 | 45 | We use labels to organize our issues and PRs. 46 | 47 | Each [label](https://github.com/eyre-rs/eyre/labels) has a prefix denoting its category: 48 | 49 | * A: Area or subcrate (e.g. A-eyre, A-color-eyre, A-color-spantrace) 50 | * C: Category (e.g. C-Breaking-Change, C-Code-Quality, C-Docs) 51 | * P: Priority (e.g. P-Urgent, P-Important) 52 | * S: Status (e.g. S-Blocked, S-Controversial, S-Needs-Design) 53 | * Misc (e.g. "good first issue", "help wanted", "duplicate", "invalid", "wontfix") 54 | 55 | ## Making changes to Eyre 56 | 57 | Most changes don't require much process. If your change is relatively straightforward, just do the following: 58 | 59 | 1. A community member (that's you!) creates one of the following: 60 | * [GitHub Discussions]: An informal discussion with the community. This is 61 | the place to start if you want to propose a feature or specific 62 | implementation and gathering community wisdom and advice before jumping 63 | to solutions. 64 | * [Issue](https://github.com/eyre-rs/eyre/issues): A formal way for us to 65 | track a bug or feature. Please look for duplicates before opening a new 66 | issue and consider starting with a Discussion. 67 | * [Pull Request](https://github.com/eyre-rs/eyre/pulls) (or PR for short): 68 | A request to merge code changes. This starts our "review process". You 69 | are welcome to start with a pull request, but consider starting with an 70 | Issue or Discussion for larger changes (or if you aren't certain about a 71 | design). We don't want anyone to waste their time on code that didn't 72 | have a chance to be merged! But conversely, sometimes PRs are the most 73 | efficient way to propose a change. Just use your own judgement here. 74 | 2. Other community members review and comment in an ad-hoc fashion. Active 75 | subject matter experts may be pulled into a thread using `@mentions`. If 76 | your PR has been quiet for a while and is ready for review, feel free to 77 | leave a message to "bump" the thread, or bring it up on [Discord] 78 | 3. Once they're content with the pull request (design, code quality, 79 | documentation, tests), individual reviewers leave "Approved" reviews. 80 | 4. After consensus has been reached (typically two approvals from the community 81 | or one for extremely simple changes) and CI passes, the 82 | [S-Ready-For-Final-Review](https://github.com/eyre-rs/eyre/issues?q=is%3Aopen+is%3Aissue+label%3AS-Ready-For-Final-Review) 83 | label is added. 84 | 5. When they find time, someone with merge rights performs a final code review 85 | and queue the PR for merging. 86 | 87 | ## How you can help 88 | 89 | If you've made it to this page, you're probably already convinced that Eyre is 90 | a project you'd like to see thrive. But how can *you* help? 91 | 92 | No matter your experience level with Eyre or Rust or your level of commitment, 93 | there are ways to meaningfully contribute. Take a look at the sections that 94 | follow to pick a route (or five) that appeal to you. 95 | 96 | If you ever find yourself at a loss for what to do, or in need of mentorship or 97 | advice on how to contribute to Eyre, feel free to ask in [Discord] and one of 98 | our more experienced community members will be happy to help. 99 | 100 | ### Writing Handlers 101 | 102 | You can improve Eyre's ecosystem by building your own 103 | [EyreHandler](https://docs.rs/eyre/0.6.8/eyre/trait.EyreHandler.html) crates 104 | like [color-eyre](https://github.com/eyre-rs/color-eyre/). The customizable 105 | reporting of `eyre` is it's secret sauce, using that customizability in 106 | creative ways and sharing your work is one of the best ways you can inspire 107 | others and help grow our community. 108 | 109 | ### Fixing bugs 110 | 111 | Bugs in Eyre are filed on the issue tracker using the [`C-bug`](https://github.com/eyre-rs/eyre/issues?q=is%3Aissue+is%3Aopen+label%3AC-Bug) label. 112 | 113 | If you're looking for an easy place to start, take a look at the [`good first 114 | issue`](https://github.com/eyre-rs/eyre/labels/good%20first%20issue) label, and 115 | feel free to ask questions on that issue's thread in question or on [Discord]. 116 | You don't need anyone's permission to try fixing a bug or adding a simple 117 | feature, but stating that you'd like to tackle an issue can be helpful to avoid 118 | duplicated work. 119 | 120 | When you make a pull request that fixes an issue, include a line that says 121 | `Fixes #X` (or "Closes"), where `X` is the issue number. This will cause the 122 | issue in question to be closed when your PR is merged. 123 | 124 | General improvements to code quality are also welcome! 125 | Eyre can always be safer, better tested, and more idiomatic. 126 | 127 | ### Writing docs 128 | 129 | This is incredibly valuable, easily distributed work, but requires a bit of guidance: 130 | 131 | * Inaccurate documentation is worse than no documentation: prioritize fixing 132 | broken docs. 133 | * Code documentation (doc examples and in the examples folder) is easier to 134 | maintain because the compiler will tell us when it breaks. 135 | * Inline documentation should be technical and to the point. Link relevant 136 | examples or other explanations if broader context is useful. 137 | 138 | ### Reviewing others' work 139 | 140 | Reviewing others work with the aim of improving it is one of the most helpful 141 | things you can do. You don't need to be an Elder Rustacean to be useful here: 142 | anyone can catch missing tests, unclear docs, logic errors, and so on. If you 143 | have specific skills (e.g. advanced familiarity with `unsafe` code, rendering 144 | knowledge or web development experience) or personal experience with a problem, 145 | try to prioritize those areas to ensure we can get appropriate expertise where 146 | we need it. 147 | 148 | Focus on giving constructive, actionable feedback that results in real 149 | improvements to code quality or end-user experience. If you don't understand 150 | why an approach was taken, please ask! 151 | 152 | Provide actual code suggestions when that is helpful. Small changes work well 153 | as comments or in-line suggestions on specific lines of codes. Larger changes 154 | deserve a comment in the main thread, or a pull request to the original 155 | author's branch (but please mention that you've made one). 156 | 157 | Once you're happy with the work and feel you're reasonably qualified to assess 158 | quality in this particular area, leave your `Approved` review on the PR. If 159 | you're new to GitHub, check out the [Pull Request Review 160 | documentation](https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews). 161 | Anyone can leave reviews ... no special permissions are required! 162 | 163 | There are three main places you can check for things to review: 164 | 165 | 1. Pull request which are ready and in need of more reviews on 166 | [eyre](https://github.com/eyre-rs/eyre/pulls?q=is%3Aopen+is%3Apr+-label%3AS-Ready-For-Final-Review+-draft%3A%3Atrue+-label%3AS-Needs-RFC+-reviewed-by%3A%40me+-author%3A%40me) 167 | 2. Pull requests on [eyre](https://github.com/eyre-rs/eyre/pulls) and the 168 | [color-eyre](https://github.com/eyre-rs/color-eyre/pulls) repos. 169 | 170 | Not even our Circle Members are exempt from reviews! By giving feedback on this 171 | work (and related supporting work), you can help us make sure our releases are 172 | both high-quality and timely. 173 | 174 | Finally, if nothing brings you more satisfaction than seeing every last issue 175 | labeled and all resolved issues closed, feel free to message any Eyre Circle 176 | Member (currently @yaahc) for the triage role to help us keep things tidy. This 177 | role only requires good faith and a basic understanding of our development 178 | process. 179 | 180 | [Discord]: https://discord.gg/z94RqmUTKB 181 | [^1]: Okay, I'll admit it, it's really just the Rust Project's CoC :sweat_smile: 182 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "color-eyre", 4 | "color-spantrace", 5 | "eyre" 6 | ] 7 | 8 | [workspace.package] 9 | authors = ["Jane Lusby "] 10 | edition = "2018" 11 | license = "MIT OR Apache-2.0" 12 | repository = "https://github.com/eyre-rs/eyre" 13 | readme = "README.md" 14 | rust-version = "1.65.0" 15 | 16 | [workspace.dependencies] 17 | indenter = "0.3.0" 18 | once_cell = "1.18.0" 19 | owo-colors = "4.0" 20 | autocfg = "1.0" 21 | 22 | [profile.dev.package.backtrace] 23 | opt-level = 3 24 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /color-eyre/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: {} 6 | 7 | name: Continuous integration 8 | 9 | jobs: 10 | check: 11 | name: Check 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | rust: 16 | - stable 17 | steps: 18 | - uses: actions/checkout@v1 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: ${{ matrix.rust }} 22 | override: true 23 | - uses: actions-rs/cargo@v1 24 | with: 25 | command: check 26 | 27 | test-features: 28 | name: Test Features 29 | runs-on: ubuntu-latest 30 | strategy: 31 | matrix: 32 | features: 33 | - 34 | - --all-features 35 | - --no-default-features 36 | - --no-default-features --features issue-url 37 | - --no-default-features --features capture-spantrace 38 | - --no-default-features --features track-caller 39 | steps: 40 | - uses: actions/checkout@v1 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: stable 44 | override: true 45 | - uses: actions-rs/cargo@v1 46 | with: 47 | command: test 48 | args: ${{ matrix.features }} 49 | 50 | test-versions: 51 | name: Test Versions 52 | runs-on: ubuntu-latest 53 | strategy: 54 | matrix: 55 | target: 56 | - x86_64-unknown-linux-gnu 57 | - wasm32-unknown-unknown 58 | rust: 59 | - stable 60 | - beta 61 | - nightly 62 | steps: 63 | - uses: actions/checkout@v1 64 | - uses: actions-rs/toolchain@v1 65 | with: 66 | target: ${{ matrix.target }} 67 | toolchain: ${{ matrix.rust }} 68 | override: true 69 | - name: install test runner for wasm 70 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 71 | if: ${{ matrix.target == 'wasm32-unknown-unknown' }} 72 | - uses: actions-rs/cargo@v1 73 | with: 74 | command: test 75 | target: ${{ matrix.target }} 76 | toolchain: ${{ matrix.rust }} 77 | if: ${{ matrix.target != 'wasm32-unknown-unknown' }} 78 | - name: run wasm tests 79 | run: wasm-pack test --node 80 | if: ${{ matrix.target == 'wasm32-unknown-unknown' }} 81 | 82 | test-os: 83 | name: Test Operating Systems 84 | runs-on: ${{ matrix.os }} 85 | strategy: 86 | matrix: 87 | os: [ubuntu-latest, windows-latest, macOS-latest] 88 | steps: 89 | - uses: actions/checkout@v1 90 | - uses: actions-rs/toolchain@v1 91 | with: 92 | toolchain: stable 93 | profile: minimal 94 | override: true 95 | - uses: actions-rs/cargo@v1 96 | with: 97 | command: test 98 | 99 | fmt: 100 | name: Rustfmt 101 | runs-on: ubuntu-latest 102 | strategy: 103 | matrix: 104 | rust: 105 | - stable 106 | steps: 107 | - uses: actions/checkout@v1 108 | - uses: actions-rs/toolchain@v1 109 | with: 110 | toolchain: ${{ matrix.rust }} 111 | override: true 112 | - run: rustup component add rustfmt 113 | - uses: actions-rs/cargo@v1 114 | with: 115 | command: fmt 116 | args: --all -- --check 117 | 118 | clippy: 119 | name: Clippy 120 | runs-on: ubuntu-latest 121 | strategy: 122 | matrix: 123 | rust: 124 | - stable 125 | steps: 126 | - uses: actions/checkout@v1 127 | - uses: actions-rs/toolchain@v1 128 | with: 129 | toolchain: ${{ matrix.rust }} 130 | override: true 131 | - run: rustup component add clippy 132 | - uses: actions-rs/cargo@v1 133 | with: 134 | command: clippy 135 | args: -- -D warnings 136 | -------------------------------------------------------------------------------- /color-eyre/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | 9 | ## [Unreleased] - ReleaseDate 10 | 11 | ## [0.6.2] - 2022-07-11 12 | ### Added 13 | - Option to disable display of location section in error reports 14 | 15 | ## [0.6.1] - 2022-02-24 16 | ### Changed 17 | - Collapsed backtrace help text into fewer lines 18 | 19 | ## [0.6.0] - 2022-01-12 20 | ### Changed 21 | - Updated dependencies to match newest tracing versions 22 | 23 | ## [0.5.11] - 2021-04-13 24 | 25 | ## [0.5.10] - 2020-12-02 26 | ### Added 27 | - Support custom themes 28 | 29 | ## [0.5.9] - 2020-12-02 30 | ### Fixed 31 | - Bumped color-spantrace dependency version to fix a panic 32 | 33 | ## [0.5.8] - 2020-11-23 34 | ### Added 35 | - Exposed internal interfaces for the panic handler so that it can be wrapped 36 | by consumers to customize the behaviour of the panic hook. 37 | 38 | ## [0.5.7] - 2020-11-05 39 | ### Fixed 40 | - Added missing `cfg`s that caused compiler errors when only enabling the 41 | `issue-url` feature 42 | 43 | ## [0.5.6] - 2020-10-02 44 | ### Added 45 | - Add support for track caller added in eyre 0.6.1 and print original 46 | callsites of errors in all `eyre::Reports` by default 47 | 48 | ## [0.5.5] - 2020-09-21 49 | ### Added 50 | - add `issue_filter` method to `HookBuilder` for disabling issue generation 51 | based on the error encountered. 52 | 53 | ## [0.5.4] - 2020-09-17 54 | ### Added 55 | - Add new "issue-url" feature for generating issue creation links in error 56 | reports pre-populated with information about the error 57 | 58 | ## [0.5.3] - 2020-09-14 59 | ### Added 60 | - add `panic_section` method to `HookBuilder` for overriding the printer for 61 | the panic message at the start of panic reports 62 | 63 | ## [0.5.2] - 2020-08-31 64 | ### Added 65 | - make it so all `Section` trait methods can be called on `Report` in 66 | addition to the already supported usage on `Result>` 67 | - panic_section to `HookBuilder` to add custom sections to panic reports 68 | - display_env_section to `HookBuilder` to disable the output indicating what 69 | environment variables can be set to manipulate the error reports 70 | ### Changed 71 | - switched from ansi_term to owo-colors for colorizing output, allowing for 72 | better compatibility with the Display trait 73 | 74 | 75 | [Unreleased]: https://github.com/eyre-rs/color-eyre/compare/v0.6.2...HEAD 76 | [0.6.2]: https://github.com/eyre-rs/color-eyre/compare/v0.6.1...v0.6.2 77 | [0.6.1]: https://github.com/eyre-rs/color-eyre/compare/v0.6.0...v0.6.1 78 | [0.6.0]: https://github.com/eyre-rs/color-eyre/compare/v0.5.11...v0.6.0 79 | [0.5.11]: https://github.com/eyre-rs/color-eyre/compare/v0.5.10...v0.5.11 80 | [0.5.10]: https://github.com/eyre-rs/color-eyre/compare/v0.5.9...v0.5.10 81 | [0.5.9]: https://github.com/eyre-rs/color-eyre/compare/v0.5.8...v0.5.9 82 | [0.5.8]: https://github.com/eyre-rs/color-eyre/compare/v0.5.7...v0.5.8 83 | [0.5.7]: https://github.com/eyre-rs/color-eyre/compare/v0.5.6...v0.5.7 84 | [0.5.6]: https://github.com/eyre-rs/color-eyre/compare/v0.5.5...v0.5.6 85 | [0.5.5]: https://github.com/eyre-rs/color-eyre/compare/v0.5.4...v0.5.5 86 | [0.5.4]: https://github.com/eyre-rs/color-eyre/compare/v0.5.3...v0.5.4 87 | [0.5.3]: https://github.com/eyre-rs/color-eyre/compare/v0.5.2...v0.5.3 88 | [0.5.2]: https://github.com/eyre-rs/color-eyre/releases/tag/v0.5.2 89 | -------------------------------------------------------------------------------- /color-eyre/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "color-eyre" 3 | version = "0.6.5" 4 | description = "An error report handler for panics and eyre::Reports for colorful, consistent, and well formatted error reports for all kinds of errors." 5 | documentation = "https://docs.rs/color-eyre" 6 | 7 | authors = { workspace = true } 8 | edition = { workspace = true } 9 | license = { workspace = true } 10 | repository = { workspace = true } 11 | readme = { workspace = true } 12 | rust-version = { workspace = true } 13 | 14 | [features] 15 | default = ["track-caller", "capture-spantrace"] 16 | capture-spantrace = ["tracing-error", "color-spantrace"] 17 | issue-url = ["url"] 18 | track-caller = [] 19 | 20 | [dependencies] 21 | eyre = { version = "1", path = "../eyre" } 22 | tracing-error = { version = "0.2.0", optional = true } 23 | backtrace = { version = "0.3.59" } 24 | indenter = { workspace = true } 25 | owo-colors = { workspace = true } 26 | color-spantrace = { version = "0.3", path = "../color-spantrace", optional = true } 27 | once_cell = { workspace = true } 28 | url = { version = "2.1.1", optional = true } 29 | 30 | [dev-dependencies] 31 | tracing-subscriber = { version = "0.3.0", features = ["env-filter"] } 32 | tracing = "0.1.13" 33 | pretty_assertions = "1.0.0" 34 | thiserror = "1.0.19" 35 | ansi-parser = "0.8.0" 36 | 37 | [target.'cfg(target_arch = "wasm32")'.dev-dependencies] 38 | wasm-bindgen-test = "0.3.15" 39 | 40 | [package.metadata.docs.rs] 41 | all-features = true 42 | rustdoc-args = ["--cfg", "docsrs"] 43 | 44 | [package.metadata.release] 45 | dev-version = false 46 | 47 | [[package.metadata.release.pre-release-replacements]] 48 | file = "CHANGELOG.md" 49 | search = "Unreleased" 50 | replace="{{version}}" 51 | 52 | [[package.metadata.release.pre-release-replacements]] 53 | file = "src/lib.rs" 54 | search = "#!\\[doc\\(html_root_url.*" 55 | replace = "#![doc(html_root_url = \"https://docs.rs/{{crate_name}}/{{version}}\")]" 56 | exactly = 1 57 | 58 | [[package.metadata.release.pre-release-replacements]] 59 | file = "CHANGELOG.md" 60 | search = "\\.\\.\\.HEAD" 61 | replace="...{{tag_name}}" 62 | exactly = 1 63 | 64 | [[package.metadata.release.pre-release-replacements]] 65 | file = "CHANGELOG.md" 66 | search = "ReleaseDate" 67 | replace="{{date}}" 68 | 69 | [[package.metadata.release.pre-release-replacements]] 70 | file="CHANGELOG.md" 71 | search="" 72 | replace="\n\n## [Unreleased] - ReleaseDate" 73 | exactly=1 74 | 75 | [[package.metadata.release.pre-release-replacements]] 76 | file="CHANGELOG.md" 77 | search="" 78 | replace="\n[Unreleased]: https://github.com/eyre-rs/{{crate_name}}/compare/{{tag_name}}...HEAD" 79 | exactly=1 80 | 81 | [[example]] 82 | name = "color-eyre-usage" 83 | path = "examples/usage.rs" 84 | -------------------------------------------------------------------------------- /color-eyre/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /color-eyre/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /color-eyre/README.md: -------------------------------------------------------------------------------- 1 | # color-eyre 2 | 3 | [![Build Status][actions-badge]][actions-url] 4 | [![Latest Version][version-badge]][version-url] 5 | [![Rust Documentation][docs-badge]][docs-url] 6 | 7 | [actions-badge]: https://github.com/eyre-rs/eyre/workflows/Continuous%20integration/badge.svg 8 | [actions-url]: https://github.com/eyre-rs/eyre/actions?query=workflow%3A%22Continuous+integration%22 9 | [version-badge]: https://img.shields.io/crates/v/color-eyre.svg 10 | [version-url]: https://crates.io/crates/color-eyre 11 | [docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg 12 | [docs-url]: https://docs.rs/color-eyre 13 | 14 | An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well 15 | formatted error reports for all kinds of errors. 16 | 17 | ## TLDR 18 | 19 | `color_eyre` helps you build error reports that look like this: 20 | 21 | ![custom section example](./pictures/custom_section.png) 22 | 23 | ## Setup 24 | 25 | Add the following to your toml file: 26 | 27 | ```toml 28 | [dependencies] 29 | color-eyre = "0.6" 30 | ``` 31 | 32 | And install the panic and error report handlers: 33 | 34 | ```rust 35 | use color_eyre::eyre::Result; 36 | 37 | fn main() -> Result<()> { 38 | color_eyre::install()?; 39 | 40 | // ... 41 | # Ok(()) 42 | } 43 | ``` 44 | 45 | ### Disabling tracing support 46 | 47 | If you don't plan on using `tracing_error` and `SpanTrace` you can disable the 48 | tracing integration to cut down on unused dependencies: 49 | 50 | ```toml 51 | [dependencies] 52 | color-eyre = { version = "0.6", default-features = false } 53 | ``` 54 | 55 | ### Disabling SpanTrace capture by default 56 | 57 | color-eyre defaults to capturing span traces. This is because `SpanTrace` 58 | capture is significantly cheaper than `Backtrace` capture. However, like 59 | backtraces, span traces are most useful for debugging applications, and it's 60 | not uncommon to want to disable span trace capture by default to keep noise out. 61 | 62 | To disable span trace capture you must explicitly set one of the env variables 63 | that regulate `SpanTrace` capture to `"0"`: 64 | 65 | ```rust 66 | if std::env::var("RUST_SPANTRACE").is_err() { 67 | std::env::set_var("RUST_SPANTRACE", "0"); 68 | } 69 | ``` 70 | 71 | ### Improving perf on debug builds 72 | 73 | In debug mode `color-eyre` behaves noticably worse than `eyre`. This is caused 74 | by the fact that `eyre` uses `std::backtrace::Backtrace` instead of 75 | `backtrace::Backtrace`. The std version of backtrace is precompiled with 76 | optimizations, this means that whether or not you're in debug mode doesn't 77 | matter much for how expensive backtrace capture is, it will always be in the 78 | 10s of milliseconds to capture. A debug version of `backtrace::Backtrace` 79 | however isn't so lucky, and can take an order of magnitude more time to capture 80 | a backtrace compared to its std counterpart. 81 | 82 | Cargo [profile 83 | overrides](https://doc.rust-lang.org/cargo/reference/profiles.html#overrides) 84 | can be used to mitigate this problem. By configuring your project to always 85 | build `backtrace` with optimizations you should get the same performance from 86 | `color-eyre` that you're used to with `eyre`. To do so add the following to 87 | your Cargo.toml: 88 | 89 | ```toml 90 | [profile.dev.package.backtrace] 91 | opt-level = 3 92 | ``` 93 | 94 | ## Features 95 | 96 | ### Multiple report format verbosity levels 97 | 98 | `color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace` 99 | and `Backtrace`, minimal, short, and full. Take the below snippets of the output produced by [`examples/usage.rs`]: 100 | 101 | --- 102 | 103 | Running `cargo run --example usage` without `RUST_LIB_BACKTRACE` set will produce a minimal 104 | report like this: 105 | 106 | ![minimal report format](./pictures/minimal.png) 107 | 108 |
109 | 110 | Running `RUST_LIB_BACKTRACE=1 cargo run --example usage` tells `color-eyre` to use the short 111 | format, which additionally capture a [`backtrace::Backtrace`]: 112 | 113 | ![short report format](./pictures/short.png) 114 | 115 |
116 | 117 | Finally, running `RUST_LIB_BACKTRACE=full cargo run --example usage` tells `color-eyre` to use 118 | the full format, which in addition to the above will attempt to include source lines where the 119 | error originated from, assuming it can find them on the disk. 120 | 121 | ![full report format](./pictures/full.png) 122 | 123 | ### Custom `Section`s for error reports via [`Section`] trait 124 | 125 | The `section` module provides helpers for adding extra sections to error 126 | reports. Sections are disinct from error messages and are displayed 127 | independently from the chain of errors. Take this example of adding sections 128 | to contain `stderr` and `stdout` from a failed command, taken from 129 | [`examples/custom_section.rs`]: 130 | 131 | ```rust 132 | use color_eyre::{eyre::eyre, SectionExt, Section, eyre::Report}; 133 | use std::process::Command; 134 | use tracing::instrument; 135 | 136 | trait Output { 137 | fn output2(&mut self) -> Result; 138 | } 139 | 140 | impl Output for Command { 141 | #[instrument] 142 | fn output2(&mut self) -> Result { 143 | let output = self.output()?; 144 | 145 | let stdout = String::from_utf8_lossy(&output.stdout); 146 | 147 | if !output.status.success() { 148 | let stderr = String::from_utf8_lossy(&output.stderr); 149 | Err(eyre!("cmd exited with non-zero status code")) 150 | .with_section(move || stdout.trim().to_string().header("Stdout:")) 151 | .with_section(move || stderr.trim().to_string().header("Stderr:")) 152 | } else { 153 | Ok(stdout.into()) 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | --- 160 | 161 | Here we have an function that, if the command exits unsuccessfully, creates a 162 | report indicating the failure and attaches two sections, one for `stdout` and 163 | one for `stderr`. 164 | 165 | Running `cargo run --example custom_section` shows us how these sections are 166 | included in the output: 167 | 168 | ![custom section example](./pictures/custom_section.png) 169 | 170 | Only the `Stderr:` section actually gets included. The `cat` command fails, 171 | so stdout ends up being empty and is skipped in the final report. This gives 172 | us a short and concise error report indicating exactly what was attempted and 173 | how it failed. 174 | 175 | ### Aggregating multiple errors into one report 176 | 177 | It's not uncommon for programs like batched task runners or parsers to want 178 | to return an error with multiple sources. The current version of the error 179 | trait does not support this use case very well, though there is [work being 180 | done](https://github.com/rust-lang/rfcs/pull/2895) to improve this. 181 | 182 | For now however one way to work around this is to compose errors outside the 183 | error trait. `color-eyre` supports such composition in its error reports via 184 | the `Section` trait. 185 | 186 | For an example of how to aggregate errors check out [`examples/multiple_errors.rs`]. 187 | 188 | ### Custom configuration for `color-backtrace` for setting custom filters and more 189 | 190 | The pretty printing for backtraces and span traces isn't actually provided by 191 | `color-eyre`, but instead comes from its dependencies [`color-backtrace`] and 192 | [`color-spantrace`]. `color-backtrace` in particular has many more features 193 | than are exported by `color-eyre`, such as customized color schemes, panic 194 | hooks, and custom frame filters. The custom frame filters are particularly 195 | useful when combined with `color-eyre`, so to enable their usage we provide 196 | the `install` fn for setting up a custom `BacktracePrinter` with custom 197 | filters installed. 198 | 199 | For an example of how to setup custom filters, check out [`examples/custom_filter.rs`]. 200 | 201 | [`eyre`]: https://docs.rs/eyre 202 | [`tracing-error`]: https://docs.rs/tracing-error 203 | [`color-backtrace`]: https://docs.rs/color-backtrace 204 | [`eyre::EyreHandler`]: https://docs.rs/eyre/*/eyre/trait.EyreHandler.html 205 | [`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html 206 | [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html 207 | [`color-spantrace`]: https://github.com/eyre-rs/eyre/tree/master/color-spantrace 208 | [`Section`]: https://docs.rs/color-eyre/*/color_eyre/section/trait.Section.html 209 | [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html 210 | [`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html 211 | [`Handler`]: https://docs.rs/color-eyre/*/color_eyre/struct.Handler.html 212 | [`examples/usage.rs`]: https://github.com/eyre-rs/color-eyre/blob/master/examples/usage.rs 213 | [`examples/custom_filter.rs`]: https://github.com/eyre-rs/eyre/blob/master/color-eyre/examples/custom_filter.rs 214 | [`examples/custom_section.rs`]: https://github.com/eyre-rs/eyre/blob/master/color-eyre/examples/custom_section.rs 215 | [`examples/multiple_errors.rs`]: https://github.com/eyre-rs/eyre/blob/master/color-eyre/examples/multiple_errors.rs 216 | 217 | #### License 218 | 219 | 220 | Licensed under either of Apache License, Version 221 | 2.0 or MIT license at your option. 222 | 223 | 224 |
225 | 226 | 227 | Unless you explicitly state otherwise, any contribution intentionally submitted 228 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 229 | be dual licensed as above, without any additional terms or conditions. 230 | 231 | -------------------------------------------------------------------------------- /color-eyre/examples/custom_filter.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::{eyre::Report, eyre::WrapErr, Section}; 2 | use tracing::{info, instrument}; 3 | 4 | #[instrument] 5 | fn main() -> Result<(), Report> { 6 | std::env::set_var("RUST_BACKTRACE", "1"); 7 | #[cfg(feature = "capture-spantrace")] 8 | install_tracing(); 9 | 10 | color_eyre::config::HookBuilder::default() 11 | .add_frame_filter(Box::new(|frames| { 12 | let filters = &["custom_filter::main"]; 13 | 14 | frames.retain(|frame| { 15 | !filters.iter().any(|f| { 16 | let name = if let Some(name) = frame.name.as_ref() { 17 | name.as_str() 18 | } else { 19 | return true; 20 | }; 21 | 22 | name.starts_with(f) 23 | }) 24 | }); 25 | })) 26 | .install() 27 | .unwrap(); 28 | 29 | read_config() 30 | } 31 | 32 | #[cfg(feature = "capture-spantrace")] 33 | fn install_tracing() { 34 | use tracing_error::ErrorLayer; 35 | use tracing_subscriber::prelude::*; 36 | use tracing_subscriber::{fmt, EnvFilter}; 37 | 38 | let fmt_layer = fmt::layer().with_target(false); 39 | let filter_layer = EnvFilter::try_from_default_env() 40 | .or_else(|_| EnvFilter::try_new("info")) 41 | .unwrap(); 42 | 43 | tracing_subscriber::registry() 44 | .with(filter_layer) 45 | .with(fmt_layer) 46 | .with(ErrorLayer::default()) 47 | .init(); 48 | } 49 | 50 | #[instrument] 51 | fn read_file(path: &str) -> Result<(), Report> { 52 | info!("Reading file"); 53 | Ok(std::fs::read_to_string(path).map(drop)?) 54 | } 55 | 56 | #[instrument] 57 | fn read_config() -> Result<(), Report> { 58 | read_file("fake_file") 59 | .wrap_err("Unable to read config") 60 | .suggestion("try using a file that exists next time") 61 | } 62 | -------------------------------------------------------------------------------- /color-eyre/examples/custom_section.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::{ 2 | eyre::Report, 3 | eyre::{eyre, WrapErr}, 4 | Section, SectionExt, 5 | }; 6 | use std::process::Command; 7 | use tracing::instrument; 8 | 9 | trait Output { 10 | fn output2(&mut self) -> Result; 11 | } 12 | 13 | impl Output for Command { 14 | #[instrument] 15 | fn output2(&mut self) -> Result { 16 | let output = self.output()?; 17 | 18 | let stdout = String::from_utf8_lossy(&output.stdout); 19 | 20 | if !output.status.success() { 21 | let stderr = String::from_utf8_lossy(&output.stderr); 22 | Err(eyre!("cmd exited with non-zero status code")) 23 | .with_section(move || stdout.trim().to_string().header("Stdout:")) 24 | .with_section(move || stderr.trim().to_string().header("Stderr:")) 25 | } else { 26 | Ok(stdout.into()) 27 | } 28 | } 29 | } 30 | 31 | #[instrument] 32 | fn main() -> Result<(), Report> { 33 | #[cfg(feature = "capture-spantrace")] 34 | install_tracing(); 35 | color_eyre::install()?; 36 | 37 | read_config().map(drop) 38 | } 39 | 40 | #[cfg(feature = "capture-spantrace")] 41 | fn install_tracing() { 42 | use tracing_error::ErrorLayer; 43 | use tracing_subscriber::prelude::*; 44 | use tracing_subscriber::{fmt, EnvFilter}; 45 | 46 | let fmt_layer = fmt::layer().with_target(false); 47 | let filter_layer = EnvFilter::try_from_default_env() 48 | .or_else(|_| EnvFilter::try_new("info")) 49 | .unwrap(); 50 | 51 | tracing_subscriber::registry() 52 | .with(filter_layer) 53 | .with(fmt_layer) 54 | .with(ErrorLayer::default()) 55 | .init(); 56 | } 57 | 58 | #[instrument] 59 | fn read_file(path: &str) -> Result { 60 | Command::new("cat").arg(path).output2() 61 | } 62 | 63 | #[instrument] 64 | fn read_config() -> Result { 65 | read_file("fake_file") 66 | .wrap_err("Unable to read config") 67 | .suggestion("try using a file that exists next time") 68 | } 69 | -------------------------------------------------------------------------------- /color-eyre/examples/debug_perf.rs: -------------------------------------------------------------------------------- 1 | //! example for manually testing the perf of color-eyre in debug vs release 2 | 3 | use color_eyre::{ 4 | eyre::Report, 5 | eyre::{eyre, WrapErr}, 6 | Section, 7 | }; 8 | use tracing::instrument; 9 | 10 | fn main() -> Result<(), Report> { 11 | #[cfg(feature = "capture-spantrace")] 12 | install_tracing(); 13 | color_eyre::install()?; 14 | 15 | time_report(); 16 | 17 | Ok(()) 18 | } 19 | 20 | #[instrument] 21 | fn time_report() { 22 | time_report_inner() 23 | } 24 | 25 | #[instrument] 26 | fn time_report_inner() { 27 | let start = std::time::Instant::now(); 28 | let report = Err::<(), Report>(eyre!("fake error")) 29 | .wrap_err("wrapped error") 30 | .suggestion("try using a file that exists next time") 31 | .unwrap_err(); 32 | 33 | println!("Error: {:?}", report); 34 | drop(report); 35 | let end = std::time::Instant::now(); 36 | 37 | dbg!(end - start); 38 | } 39 | 40 | #[cfg(feature = "capture-spantrace")] 41 | fn install_tracing() { 42 | use tracing_error::ErrorLayer; 43 | use tracing_subscriber::prelude::*; 44 | use tracing_subscriber::{fmt, EnvFilter}; 45 | 46 | let fmt_layer = fmt::layer().with_target(false); 47 | let filter_layer = EnvFilter::try_from_default_env() 48 | .or_else(|_| EnvFilter::try_new("info")) 49 | .unwrap(); 50 | 51 | tracing_subscriber::registry() 52 | .with(filter_layer) 53 | .with(fmt_layer) 54 | .with(ErrorLayer::default()) 55 | .init(); 56 | } 57 | -------------------------------------------------------------------------------- /color-eyre/examples/github_issue.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_imports)] 2 | use color_eyre::eyre; 3 | use eyre::{Report, Result}; 4 | use tracing::instrument; 5 | 6 | #[instrument] 7 | #[cfg(feature = "issue-url")] 8 | fn main() -> Result<(), Report> { 9 | #[cfg(feature = "capture-spantrace")] 10 | install_tracing(); 11 | 12 | color_eyre::config::HookBuilder::default() 13 | .issue_url(concat!(env!("CARGO_PKG_REPOSITORY"), "/issues/new")) 14 | .add_issue_metadata("version", env!("CARGO_PKG_VERSION")) 15 | .issue_filter(|kind| match kind { 16 | color_eyre::ErrorKind::NonRecoverable(_) => false, 17 | color_eyre::ErrorKind::Recoverable(_) => true, 18 | }) 19 | .install()?; 20 | 21 | let report = read_config().unwrap_err(); 22 | eprintln!("Error: {:?}", report); 23 | 24 | read_config2(); 25 | 26 | Ok(()) 27 | } 28 | 29 | #[cfg(not(feature = "issue-url"))] 30 | fn main() { 31 | unimplemented!("this example requires the \"issue-url\" feature") 32 | } 33 | 34 | #[cfg(feature = "capture-spantrace")] 35 | fn install_tracing() { 36 | use tracing_error::ErrorLayer; 37 | use tracing_subscriber::prelude::*; 38 | use tracing_subscriber::{fmt, EnvFilter}; 39 | 40 | let fmt_layer = fmt::layer().with_target(false); 41 | let filter_layer = EnvFilter::try_from_default_env() 42 | .or_else(|_| EnvFilter::try_new("info")) 43 | .unwrap(); 44 | 45 | tracing_subscriber::registry() 46 | .with(filter_layer) 47 | .with(fmt_layer) 48 | .with(ErrorLayer::default()) 49 | .init(); 50 | } 51 | 52 | #[instrument] 53 | fn read_file(path: &str) -> Result { 54 | Ok(std::fs::read_to_string(path)?) 55 | } 56 | 57 | #[instrument] 58 | fn read_config() -> Result<()> { 59 | read_file("fake_file")?; 60 | 61 | Ok(()) 62 | } 63 | 64 | #[instrument] 65 | fn read_file2(path: &str) { 66 | if let Err(e) = std::fs::read_to_string(path) { 67 | panic!("{}", e); 68 | } 69 | } 70 | 71 | #[instrument] 72 | fn read_config2() { 73 | read_file2("fake_file") 74 | } 75 | -------------------------------------------------------------------------------- /color-eyre/examples/multiple_errors.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::{eyre::eyre, eyre::Report, Section}; 2 | use thiserror::Error; 3 | 4 | fn main() -> Result<(), Report> { 5 | color_eyre::install()?; 6 | let errors = get_errors(); 7 | join_errors(errors) 8 | } 9 | 10 | fn join_errors(results: Vec>) -> Result<(), Report> { 11 | if results.iter().all(|r| r.is_ok()) { 12 | return Ok(()); 13 | } 14 | 15 | let err = results 16 | .into_iter() 17 | .filter(Result::is_err) 18 | .map(Result::unwrap_err) 19 | .fold(eyre!("encountered multiple errors"), |report, e| { 20 | report.error(e) 21 | }); 22 | 23 | Err(err) 24 | } 25 | 26 | /// Helper function to generate errors 27 | fn get_errors() -> Vec> { 28 | vec![ 29 | Err(SourceError { 30 | source: StrError("The task you ran encountered an error"), 31 | msg: "The task could not be completed", 32 | }), 33 | Err(SourceError { 34 | source: StrError("The machine you're connecting to is actively on fire"), 35 | msg: "The machine is unreachable", 36 | }), 37 | Err(SourceError { 38 | source: StrError("The file you're parsing is literally written in c++ instead of rust, what the hell"), 39 | msg: "The file could not be parsed", 40 | }), 41 | ] 42 | } 43 | 44 | /// Arbitrary error type for demonstration purposes 45 | #[derive(Debug, Error)] 46 | #[error("{0}")] 47 | struct StrError(&'static str); 48 | 49 | /// Arbitrary error type for demonstration purposes with a source error 50 | #[derive(Debug, Error)] 51 | #[error("{msg}")] 52 | struct SourceError { 53 | msg: &'static str, 54 | source: StrError, 55 | } 56 | -------------------------------------------------------------------------------- /color-eyre/examples/panic_compose.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::eyre::Report; 2 | use tracing::instrument; 3 | 4 | #[instrument] 5 | fn main() -> Result<(), Report> { 6 | #[cfg(feature = "capture-spantrace")] 7 | install_tracing(); 8 | 9 | let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default().into_hooks(); 10 | 11 | eyre_hook.install()?; 12 | 13 | std::panic::set_hook(Box::new(move |pi| { 14 | tracing::error!("{}", panic_hook.panic_report(pi)); 15 | })); 16 | 17 | read_config(); 18 | 19 | Ok(()) 20 | } 21 | 22 | #[cfg(feature = "capture-spantrace")] 23 | fn install_tracing() { 24 | use tracing_error::ErrorLayer; 25 | use tracing_subscriber::prelude::*; 26 | use tracing_subscriber::{fmt, EnvFilter}; 27 | 28 | let fmt_layer = fmt::layer().with_target(false); 29 | let filter_layer = EnvFilter::try_from_default_env() 30 | .or_else(|_| EnvFilter::try_new("info")) 31 | .unwrap(); 32 | 33 | tracing_subscriber::registry() 34 | .with(filter_layer) 35 | .with(fmt_layer) 36 | .with(ErrorLayer::default()) 37 | .init(); 38 | } 39 | 40 | #[instrument] 41 | fn read_file(path: &str) { 42 | if let Err(e) = std::fs::read_to_string(path) { 43 | panic!("{}", e); 44 | } 45 | } 46 | 47 | #[instrument] 48 | fn read_config() { 49 | read_file("fake_file") 50 | } 51 | -------------------------------------------------------------------------------- /color-eyre/examples/panic_hook.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::eyre::Report; 2 | use tracing::instrument; 3 | 4 | #[instrument] 5 | fn main() -> Result<(), Report> { 6 | #[cfg(feature = "capture-spantrace")] 7 | install_tracing(); 8 | 9 | color_eyre::config::HookBuilder::default() 10 | .panic_section("consider reporting the bug on github") 11 | .install()?; 12 | 13 | read_config(); 14 | 15 | Ok(()) 16 | } 17 | 18 | #[cfg(feature = "capture-spantrace")] 19 | fn install_tracing() { 20 | use tracing_error::ErrorLayer; 21 | use tracing_subscriber::prelude::*; 22 | use tracing_subscriber::{fmt, EnvFilter}; 23 | 24 | let fmt_layer = fmt::layer().with_target(false); 25 | let filter_layer = EnvFilter::try_from_default_env() 26 | .or_else(|_| EnvFilter::try_new("info")) 27 | .unwrap(); 28 | 29 | tracing_subscriber::registry() 30 | .with(filter_layer) 31 | .with(fmt_layer) 32 | .with(ErrorLayer::default()) 33 | .init(); 34 | } 35 | 36 | #[instrument] 37 | fn read_file(path: &str) { 38 | if let Err(e) = std::fs::read_to_string(path) { 39 | panic!("{}", e); 40 | } 41 | } 42 | 43 | #[instrument] 44 | fn read_config() { 45 | read_file("fake_file") 46 | } 47 | -------------------------------------------------------------------------------- /color-eyre/examples/theme.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::{config::Theme, eyre::Report, owo_colors::style, Section}; 2 | 3 | /// To experiment with theme values, edit `theme()` below and execute `cargo run --example theme` 4 | fn theme() -> Theme { 5 | Theme::dark() 6 | // ^ use `new` to derive from a blank theme, or `light` to derive from a light theme. 7 | // Now configure your theme (see the docs for all options): 8 | .line_number(style().blue()) 9 | .help_info_suggestion(style().red()) 10 | } 11 | 12 | #[derive(Debug, thiserror::Error)] 13 | #[error("{0}")] 14 | struct TestError(&'static str); 15 | 16 | #[tracing::instrument] 17 | fn get_error(msg: &'static str) -> Report { 18 | fn create_report(msg: &'static str) -> Report { 19 | Report::msg(msg) 20 | .note("note") 21 | .warning("warning") 22 | .suggestion("suggestion") 23 | .error(TestError("error")) 24 | } 25 | 26 | // Using `Option` to add dependency code. 27 | // See https://github.com/eyre-rs/color-eyre/blob/4ddaeb2126ed8b14e4e6aa03d7eef49eb8561cf0/src/config.rs#L56 28 | None::> 29 | .ok_or_else(|| create_report(msg)) 30 | .unwrap_err() 31 | } 32 | 33 | fn main() { 34 | setup(); 35 | println!("{:?}", get_error("test")); 36 | } 37 | 38 | fn setup() { 39 | std::env::set_var("RUST_LIB_BACKTRACE", "full"); 40 | 41 | #[cfg(feature = "capture-spantrace")] 42 | { 43 | use tracing_subscriber::prelude::*; 44 | use tracing_subscriber::{fmt, EnvFilter}; 45 | 46 | let fmt_layer = fmt::layer().with_target(false); 47 | let filter_layer = EnvFilter::try_from_default_env() 48 | .or_else(|_| EnvFilter::try_new("info")) 49 | .unwrap(); 50 | 51 | tracing_subscriber::registry() 52 | .with(filter_layer) 53 | .with(fmt_layer) 54 | .with(tracing_error::ErrorLayer::default()) 55 | .init(); 56 | } 57 | 58 | color_eyre::config::HookBuilder::new() 59 | .theme(theme()) 60 | .install() 61 | .expect("Failed to install `color_eyre`"); 62 | } 63 | -------------------------------------------------------------------------------- /color-eyre/examples/theme_test_helper.rs: -------------------------------------------------------------------------------- 1 | //! Nothing interesting here. This is just a small helper used in a test. 2 | 3 | //! This needs to be an "example" until binaries can declare separate dependencies (see https://github.com/rust-lang/cargo/issues/1982) 4 | 5 | //! See "tests/theme.rs" for more information. 6 | 7 | use color_eyre::{eyre::Report, Section}; 8 | 9 | #[rustfmt::skip] 10 | #[derive(Debug, thiserror::Error)] 11 | #[error("{0}")] 12 | struct TestError(&'static str); 13 | 14 | #[rustfmt::skip] 15 | fn get_error(msg: &'static str) -> Report { 16 | 17 | #[rustfmt::skip] 18 | #[inline(never)] 19 | fn create_report(msg: &'static str) -> Report { 20 | Report::msg(msg) 21 | .note("note") 22 | .warning("warning") 23 | .suggestion("suggestion") 24 | .error(TestError("error")) 25 | } 26 | 27 | // Getting regular `Report`. Using `Option` to trigger `is_dependency_code`. 28 | // See https://github.com/eyre-rs/color-eyre/blob/4ddaeb2126ed8b14e4e6aa03d7eef49eb8561cf0/src/config.rs#L56 29 | None::>.ok_or_else(|| create_report(msg)).unwrap_err() 30 | } 31 | 32 | fn main() { 33 | setup(); 34 | let msg = "test"; 35 | let span = tracing::info_span!("get_error", msg); 36 | let _guard = span.enter(); 37 | let error = get_error(msg); 38 | std::panic::panic_any(error) 39 | } 40 | 41 | fn setup() { 42 | std::env::set_var("RUST_BACKTRACE", "1"); 43 | 44 | #[cfg(feature = "capture-spantrace")] 45 | { 46 | use tracing_subscriber::prelude::*; 47 | use tracing_subscriber::{fmt, EnvFilter}; 48 | 49 | let fmt_layer = fmt::layer().with_target(false); 50 | let filter_layer = EnvFilter::try_from_default_env() 51 | .or_else(|_| EnvFilter::try_new("info")) 52 | .unwrap(); 53 | 54 | tracing_subscriber::registry() 55 | .with(filter_layer) 56 | .with(fmt_layer) 57 | .with(tracing_error::ErrorLayer::default()) 58 | .init(); 59 | } 60 | 61 | color_eyre::install().expect("Failed to install `color_eyre`"); 62 | } 63 | -------------------------------------------------------------------------------- /color-eyre/examples/usage.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::{eyre::Report, eyre::WrapErr, Section}; 2 | use tracing::{info, instrument}; 3 | 4 | #[instrument] 5 | fn main() -> Result<(), Report> { 6 | #[cfg(feature = "capture-spantrace")] 7 | install_tracing(); 8 | 9 | color_eyre::install()?; 10 | 11 | read_config() 12 | } 13 | 14 | #[cfg(feature = "capture-spantrace")] 15 | fn install_tracing() { 16 | use tracing_error::ErrorLayer; 17 | use tracing_subscriber::prelude::*; 18 | use tracing_subscriber::{fmt, EnvFilter}; 19 | 20 | let fmt_layer = fmt::layer().with_target(false); 21 | let filter_layer = EnvFilter::try_from_default_env() 22 | .or_else(|_| EnvFilter::try_new("info")) 23 | .unwrap(); 24 | 25 | tracing_subscriber::registry() 26 | .with(filter_layer) 27 | .with(fmt_layer) 28 | .with(ErrorLayer::default()) 29 | .init(); 30 | } 31 | 32 | #[instrument] 33 | fn read_file(path: &str) -> Result<(), Report> { 34 | info!("Reading file"); 35 | Ok(std::fs::read_to_string(path).map(drop)?) 36 | } 37 | 38 | #[instrument] 39 | fn read_config() -> Result<(), Report> { 40 | read_file("fake_file") 41 | .wrap_err("Unable to read config") 42 | .suggestion("try using a file that exists next time") 43 | } 44 | -------------------------------------------------------------------------------- /color-eyre/pictures/custom_section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyre-rs/eyre/dbaf9ed0eba704ab9b87a1ec5ff247a1c136f67a/color-eyre/pictures/custom_section.png -------------------------------------------------------------------------------- /color-eyre/pictures/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyre-rs/eyre/dbaf9ed0eba704ab9b87a1ec5ff247a1c136f67a/color-eyre/pictures/full.png -------------------------------------------------------------------------------- /color-eyre/pictures/minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyre-rs/eyre/dbaf9ed0eba704ab9b87a1ec5ff247a1c136f67a/color-eyre/pictures/minimal.png -------------------------------------------------------------------------------- /color-eyre/pictures/short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyre-rs/eyre/dbaf9ed0eba704ab9b87a1ec5ff247a1c136f67a/color-eyre/pictures/short.png -------------------------------------------------------------------------------- /color-eyre/scripts/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | let 3 | inherit (pkgs) stdenv lib python38; 4 | 5 | py = python38.withPackages (pypkgs: with pypkgs; [ beautifulsoup4 ]); 6 | 7 | in stdenv.mkDerivation { 8 | pname = "color-eyre-scripts"; 9 | version = "0.0.0"; 10 | 11 | src = ./.; 12 | buildInputs = [ py ]; 13 | } 14 | -------------------------------------------------------------------------------- /color-eyre/scripts/fix_html_examples.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3.8 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | from argparse import FileType, ArgumentParser 7 | import enum 8 | import sys 9 | 10 | from bs4 import BeautifulSoup, Tag 11 | 12 | 13 | class LineType(enum.Enum): 14 | OUTER_DOC = enum.auto() 15 | INNER_DOC = enum.auto() 16 | SOURCE = enum.auto() 17 | 18 | @classmethod 19 | def from_line(cls, line: str) -> (LineType, str): 20 | if line.startswith("//!"): 21 | return (cls.OUTER_DOC, line[len("//!") :]) 22 | elif line.startswith("///"): 23 | return (cls.INNER_DOC, line[len("///") :]) 24 | else: 25 | return (cls.SOURCE, line) 26 | 27 | def prefix(self) -> str: 28 | if self == LineType.OUTER_DOC: 29 | return "//!" 30 | elif self == LineType.INNER_DOC: 31 | return "///" 32 | else: 33 | return "" 34 | 35 | 36 | def fix_gnome_html(fh: file) -> str: 37 | """Tweaks for fixing "Copy as HTML" output from gnome-terminal 38 | 39 | Reads source from a Rust file. 40 | """ 41 | 42 | anything_changed = False 43 | line_type = LineType.SOURCE 44 | 45 | # Lines of current HTML
 chunk
 46 |     pre_chunk = []
 47 |     # Lines of processed file
 48 |     ret = []
 49 | 
 50 |     for (line_type, stripped_line), line in map(
 51 |         lambda line: (LineType.from_line(line), line), fh.readlines()
 52 |     ):
 53 |         if line_type == LineType.SOURCE:
 54 |             ret.append(line)
 55 |         elif stripped_line.lstrip().startswith(""):
 58 |             pre_chunk.append(stripped_line)
 59 |             if any(" str:
 77 |     """Fixes an individual 
 tag from Gnome.
 78 | 
 79 |     Optionally prepends a given prefix to each line in the returned output.
 80 |     """
 81 |     soup = BeautifulSoup(html, "html.parser")
 82 | 
 83 |     for pre in soup.find_all("pre"):
 84 |         for tag in pre.find_all("font"):
 85 |             #  -> 
 86 |             tag.name = "span"
 87 |             color = tag.attrs.pop("color")
 88 |             tag["style"] = f"color: {color}"
 89 | 
 90 |     return "".join(prefix + line for line in str(soup).splitlines(keepends=True))
 91 | 
 92 | 
 93 | def main():
 94 |     parser = ArgumentParser(
 95 |         description="""Convert HTML from Gnome terminal's 'Copy as HTML' feature
 96 |         to use modern  tags and inline CSS.
 97 | 
 98 |         This script is idempotent, i.e. multiple invocations will not change
 99 |         the output past the first invocation."""
100 |     )
101 |     parser.add_argument(
102 |         "file",
103 |         nargs="+",
104 |         type=FileType("r+", encoding="utf-8"),
105 |         help="""Rust file to update 
 blocks in.""",
106 |     )
107 |     args = parser.parse_args()
108 |     for fh in args.file:
109 |         if not fh.name.endswith(".rs"):
110 |             print(
111 |                 "This script only fixes Rust source files; you probably didn't mean to include",
112 |                 fh.name,
113 |                 "so I'll skip processing it.",
114 |             )
115 |         new_content = fix_gnome_html(fh)
116 |         if new_content is not None:
117 |             print("Updated example colored output in", fh.name)
118 |             fh.seek(0)
119 |             fh.write(new_content)
120 |         else:
121 |             print("Nothing to fix in", fh.name)
122 |         fh.close()
123 | 
124 | 
125 | if __name__ == "__main__":
126 |     main()
127 | 


--------------------------------------------------------------------------------
/color-eyre/src/fmt.rs:
--------------------------------------------------------------------------------
 1 | //! Module for new types that isolate complext formatting
 2 | use std::fmt;
 3 | 
 4 | use owo_colors::OwoColorize;
 5 | 
 6 | pub(crate) struct LocationSection<'a>(
 7 |     pub(crate) Option<&'a std::panic::Location<'a>>,
 8 |     pub(crate) crate::config::Theme,
 9 | );
10 | 
11 | impl fmt::Display for LocationSection<'_> {
12 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13 |         let theme = self.1;
14 |         // If known, print panic location.
15 |         if let Some(loc) = self.0 {
16 |             write!(f, "{}", loc.file().style(theme.panic_file))?;
17 |             write!(f, ":")?;
18 |             write!(f, "{}", loc.line().style(theme.panic_line_number))?;
19 |         } else {
20 |             write!(f, "")?;
21 |         }
22 | 
23 |         Ok(())
24 |     }
25 | }
26 | 


--------------------------------------------------------------------------------
/color-eyre/src/handler.rs:
--------------------------------------------------------------------------------
  1 | use crate::{
  2 |     config::BacktraceFormatter,
  3 |     section::help::HelpInfo,
  4 |     writers::{EnvSection, WriterExt},
  5 |     Handler,
  6 | };
  7 | use backtrace::Backtrace;
  8 | use indenter::{indented, Format};
  9 | use std::fmt::Write;
 10 | #[cfg(feature = "capture-spantrace")]
 11 | use tracing_error::{ExtractSpanTrace, SpanTrace};
 12 | 
 13 | impl std::fmt::Debug for Handler {
 14 |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 15 |         f.write_str("redacted")
 16 |     }
 17 | }
 18 | 
 19 | impl Handler {
 20 |     /// Return a reference to the captured `Backtrace` type
 21 |     pub fn backtrace(&self) -> Option<&Backtrace> {
 22 |         self.backtrace.as_ref()
 23 |     }
 24 | 
 25 |     /// Return a reference to the captured `SpanTrace` type
 26 |     #[cfg(feature = "capture-spantrace")]
 27 |     #[cfg_attr(docsrs, doc(cfg(feature = "capture-spantrace")))]
 28 |     pub fn span_trace(&self) -> Option<&SpanTrace> {
 29 |         self.span_trace.as_ref()
 30 |     }
 31 | 
 32 |     pub(crate) fn format_backtrace<'a>(
 33 |         &'a self,
 34 |         trace: &'a backtrace::Backtrace,
 35 |     ) -> BacktraceFormatter<'a> {
 36 |         BacktraceFormatter {
 37 |             filters: &self.filters,
 38 |             inner: trace,
 39 |             theme: self.theme,
 40 |         }
 41 |     }
 42 | }
 43 | 
 44 | impl eyre::EyreHandler for Handler {
 45 |     fn debug(
 46 |         &self,
 47 |         error: &(dyn std::error::Error + 'static),
 48 |         f: &mut core::fmt::Formatter<'_>,
 49 |     ) -> core::fmt::Result {
 50 |         if f.alternate() {
 51 |             return core::fmt::Debug::fmt(error, f);
 52 |         }
 53 | 
 54 |         #[cfg(feature = "capture-spantrace")]
 55 |         let errors = || {
 56 |             eyre::Chain::new(error)
 57 |                 .filter(|e| e.span_trace().is_none())
 58 |                 .enumerate()
 59 |         };
 60 | 
 61 |         #[cfg(not(feature = "capture-spantrace"))]
 62 |         let errors = || eyre::Chain::new(error).enumerate();
 63 | 
 64 |         for (n, error) in errors() {
 65 |             writeln!(f)?;
 66 |             write!(indented(f).ind(n), "{}", self.theme.error.style(error))?;
 67 |         }
 68 | 
 69 |         let mut separated = f.header("\n\n");
 70 | 
 71 |         #[cfg(feature = "track-caller")]
 72 |         if self.display_location_section {
 73 |             write!(
 74 |                 separated.ready(),
 75 |                 "{}",
 76 |                 crate::SectionExt::header(
 77 |                     crate::fmt::LocationSection(self.location, self.theme),
 78 |                     "Location:"
 79 |                 )
 80 |             )?;
 81 |         }
 82 | 
 83 |         for section in self
 84 |             .sections
 85 |             .iter()
 86 |             .filter(|s| matches!(s, HelpInfo::Error(_, _)))
 87 |         {
 88 |             write!(separated.ready(), "{}", section)?;
 89 |         }
 90 | 
 91 |         for section in self
 92 |             .sections
 93 |             .iter()
 94 |             .filter(|s| matches!(s, HelpInfo::Custom(_)))
 95 |         {
 96 |             write!(separated.ready(), "{}", section)?;
 97 |         }
 98 | 
 99 |         #[cfg(feature = "capture-spantrace")]
100 |         let span_trace = self
101 |             .span_trace
102 |             .as_ref()
103 |             .or_else(|| get_deepest_spantrace(error));
104 | 
105 |         #[cfg(feature = "capture-spantrace")]
106 |         {
107 |             if let Some(span_trace) = span_trace {
108 |                 write!(
109 |                     &mut separated.ready(),
110 |                     "{}",
111 |                     crate::writers::FormattedSpanTrace(span_trace)
112 |                 )?;
113 |             }
114 |         }
115 | 
116 |         if !self.suppress_backtrace {
117 |             if let Some(backtrace) = self.backtrace.as_ref() {
118 |                 let fmted_bt = self.format_backtrace(backtrace);
119 | 
120 |                 write!(
121 |                     indented(&mut separated.ready())
122 |                         .with_format(Format::Uniform { indentation: "  " }),
123 |                     "{}",
124 |                     fmted_bt
125 |                 )?;
126 |             }
127 |         }
128 | 
129 |         let f = separated.ready();
130 |         let mut h = f.header("\n");
131 |         let mut f = h.in_progress();
132 | 
133 |         for section in self
134 |             .sections
135 |             .iter()
136 |             .filter(|s| !matches!(s, HelpInfo::Custom(_) | HelpInfo::Error(_, _)))
137 |         {
138 |             write!(&mut f, "{}", section)?;
139 |             f = h.ready();
140 |         }
141 | 
142 |         if self.display_env_section {
143 |             let env_section = EnvSection {
144 |                 bt_captured: &self.backtrace.is_some(),
145 |                 #[cfg(feature = "capture-spantrace")]
146 |                 span_trace,
147 |             };
148 | 
149 |             write!(&mut separated.ready(), "{}", env_section)?;
150 |         }
151 | 
152 |         #[cfg(feature = "issue-url")]
153 |         if self.issue_url.is_some() && (*self.issue_filter)(crate::ErrorKind::Recoverable(error)) {
154 |             let url = self.issue_url.as_ref().unwrap();
155 |             let mut payload = String::from("Error: ");
156 |             for (n, error) in errors() {
157 |                 writeln!(&mut payload)?;
158 |                 write!(indented(&mut payload).ind(n), "{}", error)?;
159 |             }
160 | 
161 |             let issue_section = crate::section::github::IssueSection::new(url, &payload)
162 |                 .with_backtrace(self.backtrace.as_ref())
163 |                 .with_metadata(&self.issue_metadata);
164 | 
165 |             #[cfg(feature = "capture-spantrace")]
166 |             let issue_section = issue_section.with_span_trace(span_trace);
167 | 
168 |             write!(&mut separated.ready(), "{}", issue_section)?;
169 |         }
170 | 
171 |         Ok(())
172 |     }
173 | 
174 |     #[cfg(feature = "track-caller")]
175 |     fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {
176 |         self.location = Some(location);
177 |     }
178 | }
179 | 
180 | #[cfg(feature = "capture-spantrace")]
181 | pub(crate) fn get_deepest_spantrace<'a>(
182 |     error: &'a (dyn std::error::Error + 'static),
183 | ) -> Option<&'a SpanTrace> {
184 |     eyre::Chain::new(error)
185 |         .rev()
186 |         .flat_map(|error| error.span_trace())
187 |         .next()
188 | }
189 | 


--------------------------------------------------------------------------------
/color-eyre/src/private.rs:
--------------------------------------------------------------------------------
1 | use crate::eyre::Report;
2 | pub trait Sealed {}
3 | 
4 | impl Sealed for std::result::Result where E: Into {}
5 | impl Sealed for Report {}
6 | 


--------------------------------------------------------------------------------
/color-eyre/src/section/github.rs:
--------------------------------------------------------------------------------
  1 | use crate::writers::DisplayExt;
  2 | use backtrace::Backtrace;
  3 | use std::{fmt, panic::Location};
  4 | #[cfg(feature = "capture-spantrace")]
  5 | use tracing_error::SpanTrace;
  6 | use url::Url;
  7 | 
  8 | type Display<'a> = Box;
  9 | 
 10 | pub(crate) struct IssueSection<'a> {
 11 |     url: &'a str,
 12 |     msg: &'a str,
 13 |     location: Option<&'a Location<'a>>,
 14 |     backtrace: Option<&'a Backtrace>,
 15 |     #[cfg(feature = "capture-spantrace")]
 16 |     span_trace: Option<&'a SpanTrace>,
 17 |     metadata: &'a [(String, Display<'a>)],
 18 | }
 19 | 
 20 | impl<'a> IssueSection<'a> {
 21 |     pub(crate) fn new(url: &'a str, msg: &'a str) -> Self {
 22 |         IssueSection {
 23 |             url,
 24 |             msg,
 25 |             location: None,
 26 |             backtrace: None,
 27 |             #[cfg(feature = "capture-spantrace")]
 28 |             span_trace: None,
 29 |             metadata: &[],
 30 |         }
 31 |     }
 32 | 
 33 |     pub(crate) fn with_location(mut self, location: impl Into>>) -> Self {
 34 |         self.location = location.into();
 35 |         self
 36 |     }
 37 | 
 38 |     pub(crate) fn with_backtrace(mut self, backtrace: impl Into>) -> Self {
 39 |         self.backtrace = backtrace.into();
 40 |         self
 41 |     }
 42 | 
 43 |     #[cfg(feature = "capture-spantrace")]
 44 |     pub(crate) fn with_span_trace(mut self, span_trace: impl Into>) -> Self {
 45 |         self.span_trace = span_trace.into();
 46 |         self
 47 |     }
 48 | 
 49 |     pub(crate) fn with_metadata(mut self, metadata: &'a [(String, Display<'a>)]) -> Self {
 50 |         self.metadata = metadata;
 51 |         self
 52 |     }
 53 | }
 54 | 
 55 | impl fmt::Display for IssueSection<'_> {
 56 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 57 |         let location = self
 58 |             .location
 59 |             .map(|loc| ("location".to_string(), Box::new(loc) as _));
 60 |         let metadata = self.metadata.iter().chain(location.as_ref());
 61 |         let metadata = MetadataSection { metadata }.to_string();
 62 |         let mut body = Body::new();
 63 |         body.push_section("Error", ConsoleSection(self.msg))?;
 64 | 
 65 |         if !self.metadata.is_empty() {
 66 |             body.push_section("Metadata", metadata)?;
 67 |         }
 68 | 
 69 |         #[cfg(feature = "capture-spantrace")]
 70 |         if let Some(st) = self.span_trace {
 71 |             body.push_section(
 72 |                 "SpanTrace",
 73 |                 Collapsed(ConsoleSection(st.with_header("SpanTrace:\n"))),
 74 |             )?;
 75 |         }
 76 | 
 77 |         if let Some(bt) = self.backtrace {
 78 |             body.push_section(
 79 |                 "Backtrace",
 80 |                 Collapsed(ConsoleSection(
 81 |                     DisplayFromDebug(bt).with_header("Backtrace:\n"),
 82 |                 )),
 83 |             )?;
 84 |         }
 85 | 
 86 |         let url_result = Url::parse_with_params(
 87 |             self.url,
 88 |             &[("title", ""), ("body", &body.body)],
 89 |         );
 90 | 
 91 |         let url: &dyn fmt::Display = match &url_result {
 92 |             Ok(url_struct) => url_struct,
 93 |             Err(_) => &self.url,
 94 |         };
 95 | 
 96 |         url.with_header("Consider reporting this error using this URL: ")
 97 |             .fmt(f)
 98 |     }
 99 | }
100 | 
101 | struct Body {
102 |     body: String,
103 | }
104 | 
105 | impl Body {
106 |     fn new() -> Self {
107 |         Body {
108 |             body: String::new(),
109 |         }
110 |     }
111 |     fn push_section(&mut self, header: &'static str, section: T) -> fmt::Result
112 |     where
113 |         T: fmt::Display,
114 |     {
115 |         use std::fmt::Write;
116 | 
117 |         let separator = if self.body.is_empty() { "" } else { "\n\n" };
118 |         let header = header
119 |             .with_header("## ")
120 |             .with_header(separator)
121 |             .with_footer("\n");
122 | 
123 |         write!(&mut self.body, "{}", section.with_header(header))
124 |     }
125 | }
126 | 
127 | struct MetadataSection {
128 |     metadata: T,
129 | }
130 | 
131 | impl<'a, T> MetadataSection
132 | where
133 |     T: IntoIterator)>,
134 | {
135 |     // This is implemented as a free functions so it can consume the `metadata`
136 |     // iterator, rather than being forced to leave it unmodified if its behind a
137 |     // `&self` shared reference via the Display trait
138 |     #[allow(clippy::inherent_to_string, clippy::wrong_self_convention)]
139 |     fn to_string(self) -> String {
140 |         use std::fmt::Write;
141 | 
142 |         let mut out = String::new();
143 |         let f = &mut out;
144 | 
145 |         writeln!(f, "|key|value|").expect("writing to a string doesn't panic");
146 |         writeln!(f, "|--|--|").expect("writing to a string doesn't panic");
147 | 
148 |         for (key, value) in self.metadata {
149 |             writeln!(f, "|**{}**|{}|", key, value).expect("writing to a string doesn't panic");
150 |         }
151 | 
152 |         out
153 |     }
154 | }
155 | 
156 | struct ConsoleSection(T);
157 | 
158 | impl fmt::Display for ConsoleSection
159 | where
160 |     T: fmt::Display,
161 | {
162 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 |         (&self.0).with_header("```\n").with_footer("\n```").fmt(f)
164 |     }
165 | }
166 | 
167 | struct Collapsed(T);
168 | 
169 | impl fmt::Display for Collapsed
170 | where
171 |     T: fmt::Display,
172 | {
173 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 |         (&self.0)
175 |             .with_header("\n
\n\n") 176 | .with_footer("\n
") 177 | .fmt(f) 178 | } 179 | } 180 | 181 | struct DisplayFromDebug(T); 182 | 183 | impl fmt::Display for DisplayFromDebug 184 | where 185 | T: fmt::Debug, 186 | { 187 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 188 | self.0.fmt(f) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /color-eyre/src/section/help.rs: -------------------------------------------------------------------------------- 1 | //! Provides an extension trait for attaching `Section` to error reports. 2 | use crate::{ 3 | config::Theme, 4 | eyre::{Report, Result}, 5 | Section, 6 | }; 7 | use indenter::indented; 8 | use owo_colors::OwoColorize; 9 | use std::fmt::Write; 10 | use std::fmt::{self, Display}; 11 | 12 | impl Section for Report { 13 | type Return = Report; 14 | 15 | fn note(mut self, note: D) -> Self::Return 16 | where 17 | D: Display + Send + Sync + 'static, 18 | { 19 | if let Some(handler) = self.handler_mut().downcast_mut::() { 20 | handler 21 | .sections 22 | .push(HelpInfo::Note(Box::new(note), handler.theme)); 23 | } 24 | 25 | self 26 | } 27 | 28 | fn with_note(mut self, note: F) -> Self::Return 29 | where 30 | D: Display + Send + Sync + 'static, 31 | F: FnOnce() -> D, 32 | { 33 | if let Some(handler) = self.handler_mut().downcast_mut::() { 34 | handler 35 | .sections 36 | .push(HelpInfo::Note(Box::new(note()), handler.theme)); 37 | } 38 | 39 | self 40 | } 41 | 42 | fn warning(mut self, warning: D) -> Self::Return 43 | where 44 | D: Display + Send + Sync + 'static, 45 | { 46 | if let Some(handler) = self.handler_mut().downcast_mut::() { 47 | handler 48 | .sections 49 | .push(HelpInfo::Warning(Box::new(warning), handler.theme)); 50 | } 51 | 52 | self 53 | } 54 | 55 | fn with_warning(mut self, warning: F) -> Self::Return 56 | where 57 | D: Display + Send + Sync + 'static, 58 | F: FnOnce() -> D, 59 | { 60 | if let Some(handler) = self.handler_mut().downcast_mut::() { 61 | handler 62 | .sections 63 | .push(HelpInfo::Warning(Box::new(warning()), handler.theme)); 64 | } 65 | 66 | self 67 | } 68 | 69 | fn suggestion(mut self, suggestion: D) -> Self::Return 70 | where 71 | D: Display + Send + Sync + 'static, 72 | { 73 | if let Some(handler) = self.handler_mut().downcast_mut::() { 74 | handler 75 | .sections 76 | .push(HelpInfo::Suggestion(Box::new(suggestion), handler.theme)); 77 | } 78 | 79 | self 80 | } 81 | 82 | fn with_suggestion(mut self, suggestion: F) -> Self::Return 83 | where 84 | D: Display + Send + Sync + 'static, 85 | F: FnOnce() -> D, 86 | { 87 | if let Some(handler) = self.handler_mut().downcast_mut::() { 88 | handler 89 | .sections 90 | .push(HelpInfo::Suggestion(Box::new(suggestion()), handler.theme)); 91 | } 92 | 93 | self 94 | } 95 | 96 | fn with_section(mut self, section: F) -> Self::Return 97 | where 98 | D: Display + Send + Sync + 'static, 99 | F: FnOnce() -> D, 100 | { 101 | if let Some(handler) = self.handler_mut().downcast_mut::() { 102 | let section = Box::new(section()); 103 | handler.sections.push(HelpInfo::Custom(section)); 104 | } 105 | 106 | self 107 | } 108 | 109 | fn section(mut self, section: D) -> Self::Return 110 | where 111 | D: Display + Send + Sync + 'static, 112 | { 113 | if let Some(handler) = self.handler_mut().downcast_mut::() { 114 | let section = Box::new(section); 115 | handler.sections.push(HelpInfo::Custom(section)); 116 | } 117 | 118 | self 119 | } 120 | 121 | fn error(mut self, error: E2) -> Self::Return 122 | where 123 | E2: std::error::Error + Send + Sync + 'static, 124 | { 125 | if let Some(handler) = self.handler_mut().downcast_mut::() { 126 | let error = error.into(); 127 | handler.sections.push(HelpInfo::Error(error, handler.theme)); 128 | } 129 | 130 | self 131 | } 132 | 133 | fn with_error(mut self, error: F) -> Self::Return 134 | where 135 | F: FnOnce() -> E2, 136 | E2: std::error::Error + Send + Sync + 'static, 137 | { 138 | if let Some(handler) = self.handler_mut().downcast_mut::() { 139 | let error = error().into(); 140 | handler.sections.push(HelpInfo::Error(error, handler.theme)); 141 | } 142 | 143 | self 144 | } 145 | 146 | fn suppress_backtrace(mut self, suppress: bool) -> Self::Return { 147 | if let Some(handler) = self.handler_mut().downcast_mut::() { 148 | handler.suppress_backtrace = suppress; 149 | } 150 | 151 | self 152 | } 153 | } 154 | 155 | impl Section for Result 156 | where 157 | E: Into, 158 | { 159 | type Return = Result; 160 | 161 | fn note(self, note: D) -> Self::Return 162 | where 163 | D: Display + Send + Sync + 'static, 164 | { 165 | self.map_err(|error| error.into()) 166 | .map_err(|report| report.note(note)) 167 | } 168 | 169 | fn with_note(self, note: F) -> Self::Return 170 | where 171 | D: Display + Send + Sync + 'static, 172 | F: FnOnce() -> D, 173 | { 174 | self.map_err(|error| error.into()) 175 | .map_err(|report| report.note(note())) 176 | } 177 | 178 | fn warning(self, warning: D) -> Self::Return 179 | where 180 | D: Display + Send + Sync + 'static, 181 | { 182 | self.map_err(|error| error.into()) 183 | .map_err(|report| report.warning(warning)) 184 | } 185 | 186 | fn with_warning(self, warning: F) -> Self::Return 187 | where 188 | D: Display + Send + Sync + 'static, 189 | F: FnOnce() -> D, 190 | { 191 | self.map_err(|error| error.into()) 192 | .map_err(|report| report.warning(warning())) 193 | } 194 | 195 | fn suggestion(self, suggestion: D) -> Self::Return 196 | where 197 | D: Display + Send + Sync + 'static, 198 | { 199 | self.map_err(|error| error.into()) 200 | .map_err(|report| report.suggestion(suggestion)) 201 | } 202 | 203 | fn with_suggestion(self, suggestion: F) -> Self::Return 204 | where 205 | D: Display + Send + Sync + 'static, 206 | F: FnOnce() -> D, 207 | { 208 | self.map_err(|error| error.into()) 209 | .map_err(|report| report.suggestion(suggestion())) 210 | } 211 | 212 | fn with_section(self, section: F) -> Self::Return 213 | where 214 | D: Display + Send + Sync + 'static, 215 | F: FnOnce() -> D, 216 | { 217 | self.map_err(|error| error.into()) 218 | .map_err(|report| report.section(section())) 219 | } 220 | 221 | fn section(self, section: D) -> Self::Return 222 | where 223 | D: Display + Send + Sync + 'static, 224 | { 225 | self.map_err(|error| error.into()) 226 | .map_err(|report| report.section(section)) 227 | } 228 | 229 | fn error(self, error: E2) -> Self::Return 230 | where 231 | E2: std::error::Error + Send + Sync + 'static, 232 | { 233 | self.map_err(|error| error.into()) 234 | .map_err(|report| report.error(error)) 235 | } 236 | 237 | fn with_error(self, error: F) -> Self::Return 238 | where 239 | F: FnOnce() -> E2, 240 | E2: std::error::Error + Send + Sync + 'static, 241 | { 242 | self.map_err(|error| error.into()) 243 | .map_err(|report| report.error(error())) 244 | } 245 | 246 | fn suppress_backtrace(self, suppress: bool) -> Self::Return { 247 | self.map_err(|error| error.into()) 248 | .map_err(|report| report.suppress_backtrace(suppress)) 249 | } 250 | } 251 | 252 | pub(crate) enum HelpInfo { 253 | Error(Box, Theme), 254 | Custom(Box), 255 | Note(Box, Theme), 256 | Warning(Box, Theme), 257 | Suggestion(Box, Theme), 258 | } 259 | 260 | impl Display for HelpInfo { 261 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 262 | match self { 263 | HelpInfo::Note(note, theme) => { 264 | write!(f, "{}: {}", "Note".style(theme.help_info_note), note) 265 | } 266 | HelpInfo::Warning(warning, theme) => write!( 267 | f, 268 | "{}: {}", 269 | "Warning".style(theme.help_info_warning), 270 | warning 271 | ), 272 | HelpInfo::Suggestion(suggestion, theme) => write!( 273 | f, 274 | "{}: {}", 275 | "Suggestion".style(theme.help_info_suggestion), 276 | suggestion 277 | ), 278 | HelpInfo::Custom(section) => write!(f, "{}", section), 279 | HelpInfo::Error(error, theme) => { 280 | // a lot here 281 | let errors = std::iter::successors( 282 | Some(error.as_ref() as &(dyn std::error::Error + 'static)), 283 | |e| e.source(), 284 | ); 285 | 286 | write!(f, "Error:")?; 287 | for (n, error) in errors.enumerate() { 288 | writeln!(f)?; 289 | write!(indented(f).ind(n), "{}", error.style(theme.help_info_error))?; 290 | } 291 | 292 | Ok(()) 293 | } 294 | } 295 | } 296 | } 297 | 298 | impl fmt::Debug for HelpInfo { 299 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 300 | match self { 301 | HelpInfo::Note(note, ..) => f 302 | .debug_tuple("Note") 303 | .field(&format_args!("{}", note)) 304 | .finish(), 305 | HelpInfo::Warning(warning, ..) => f 306 | .debug_tuple("Warning") 307 | .field(&format_args!("{}", warning)) 308 | .finish(), 309 | HelpInfo::Suggestion(suggestion, ..) => f 310 | .debug_tuple("Suggestion") 311 | .field(&format_args!("{}", suggestion)) 312 | .finish(), 313 | HelpInfo::Custom(custom, ..) => f 314 | .debug_tuple("CustomSection") 315 | .field(&format_args!("{}", custom)) 316 | .finish(), 317 | HelpInfo::Error(error, ..) => f.debug_tuple("Error").field(error).finish(), 318 | } 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /color-eyre/src/writers.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{lib_verbosity, panic_verbosity, Verbosity}; 2 | use fmt::Write; 3 | use std::fmt::{self, Display}; 4 | #[cfg(feature = "capture-spantrace")] 5 | use tracing_error::{SpanTrace, SpanTraceStatus}; 6 | 7 | #[allow(explicit_outlives_requirements)] 8 | pub(crate) struct HeaderWriter<'a, H, W> 9 | where 10 | H: ?Sized, 11 | { 12 | inner: W, 13 | header: &'a H, 14 | started: bool, 15 | } 16 | 17 | pub(crate) trait WriterExt: Sized { 18 | fn header(self, header: &H) -> HeaderWriter<'_, H, Self>; 19 | } 20 | 21 | impl WriterExt for W { 22 | fn header(self, header: &H) -> HeaderWriter<'_, H, Self> { 23 | HeaderWriter { 24 | inner: self, 25 | header, 26 | started: false, 27 | } 28 | } 29 | } 30 | 31 | #[cfg(feature = "issue-url")] 32 | pub(crate) trait DisplayExt: Sized + Display { 33 | fn with_header(self, header: H) -> Header; 34 | fn with_footer(self, footer: F) -> Footer; 35 | } 36 | 37 | #[cfg(feature = "issue-url")] 38 | impl DisplayExt for T 39 | where 40 | T: Display, 41 | { 42 | fn with_footer(self, footer: F) -> Footer { 43 | Footer { body: self, footer } 44 | } 45 | 46 | fn with_header(self, header: H) -> Header { 47 | Header { 48 | body: self, 49 | h: header, 50 | } 51 | } 52 | } 53 | 54 | pub(crate) struct ReadyHeaderWriter<'a, 'b, H: ?Sized, W>(&'b mut HeaderWriter<'a, H, W>); 55 | 56 | impl<'a, H: ?Sized, W> HeaderWriter<'a, H, W> { 57 | pub(crate) fn ready(&mut self) -> ReadyHeaderWriter<'a, '_, H, W> { 58 | self.started = false; 59 | 60 | ReadyHeaderWriter(self) 61 | } 62 | 63 | pub(crate) fn in_progress(&mut self) -> ReadyHeaderWriter<'a, '_, H, W> { 64 | self.started = true; 65 | 66 | ReadyHeaderWriter(self) 67 | } 68 | } 69 | 70 | impl fmt::Write for ReadyHeaderWriter<'_, '_, H, W> 71 | where 72 | H: Display, 73 | W: fmt::Write, 74 | { 75 | fn write_str(&mut self, s: &str) -> fmt::Result { 76 | if !self.0.started && !s.is_empty() { 77 | self.0.inner.write_fmt(format_args!("{}", self.0.header))?; 78 | self.0.started = true; 79 | } 80 | 81 | self.0.inner.write_str(s) 82 | } 83 | } 84 | 85 | #[cfg(feature = "issue-url")] 86 | pub(crate) struct FooterWriter { 87 | inner: W, 88 | had_output: bool, 89 | } 90 | 91 | #[cfg(feature = "issue-url")] 92 | impl fmt::Write for FooterWriter 93 | where 94 | W: fmt::Write, 95 | { 96 | fn write_str(&mut self, s: &str) -> fmt::Result { 97 | if !self.had_output && !s.is_empty() { 98 | self.had_output = true; 99 | } 100 | 101 | self.inner.write_str(s) 102 | } 103 | } 104 | 105 | #[cfg(feature = "issue-url")] 106 | #[allow(explicit_outlives_requirements)] 107 | pub(crate) struct Footer 108 | where 109 | B: Display, 110 | H: Display, 111 | { 112 | body: B, 113 | footer: H, 114 | } 115 | 116 | #[cfg(feature = "issue-url")] 117 | impl fmt::Display for Footer 118 | where 119 | B: Display, 120 | H: Display, 121 | { 122 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 123 | let mut inner_f = FooterWriter { 124 | inner: &mut *f, 125 | had_output: false, 126 | }; 127 | 128 | write!(&mut inner_f, "{}", self.body)?; 129 | 130 | if inner_f.had_output { 131 | self.footer.fmt(f)?; 132 | } 133 | 134 | Ok(()) 135 | } 136 | } 137 | 138 | #[cfg(feature = "issue-url")] 139 | #[allow(explicit_outlives_requirements)] 140 | pub(crate) struct Header 141 | where 142 | B: Display, 143 | H: Display, 144 | { 145 | body: B, 146 | h: H, 147 | } 148 | 149 | #[cfg(feature = "issue-url")] 150 | impl fmt::Display for Header 151 | where 152 | B: Display, 153 | H: Display, 154 | { 155 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 156 | write!(f.header(&self.h).ready(), "{}", self.body)?; 157 | 158 | Ok(()) 159 | } 160 | } 161 | 162 | #[cfg(feature = "capture-spantrace")] 163 | pub(crate) struct FormattedSpanTrace<'a>(pub(crate) &'a SpanTrace); 164 | 165 | #[cfg(feature = "capture-spantrace")] 166 | impl fmt::Display for FormattedSpanTrace<'_> { 167 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 168 | use indenter::indented; 169 | use indenter::Format; 170 | 171 | if self.0.status() == SpanTraceStatus::CAPTURED { 172 | write!( 173 | indented(f).with_format(Format::Uniform { indentation: " " }), 174 | "{}", 175 | color_spantrace::colorize(self.0) 176 | )?; 177 | } 178 | 179 | Ok(()) 180 | } 181 | } 182 | 183 | pub(crate) struct EnvSection<'a> { 184 | pub(crate) bt_captured: &'a bool, 185 | #[cfg(feature = "capture-spantrace")] 186 | pub(crate) span_trace: Option<&'a SpanTrace>, 187 | } 188 | 189 | impl fmt::Display for EnvSection<'_> { 190 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 191 | let v = if std::thread::panicking() { 192 | panic_verbosity() 193 | } else { 194 | lib_verbosity() 195 | }; 196 | write!(f, "{}", BacktraceOmited(!self.bt_captured))?; 197 | 198 | let mut separated = HeaderWriter { 199 | inner: &mut *f, 200 | header: &"\n", 201 | started: false, 202 | }; 203 | write!(&mut separated.ready(), "{}", SourceSnippets(v))?; 204 | #[cfg(feature = "capture-spantrace")] 205 | write!( 206 | &mut separated.ready(), 207 | "{}", 208 | SpanTraceOmited(self.span_trace) 209 | )?; 210 | Ok(()) 211 | } 212 | } 213 | 214 | #[cfg(feature = "capture-spantrace")] 215 | struct SpanTraceOmited<'a>(Option<&'a SpanTrace>); 216 | 217 | #[cfg(feature = "capture-spantrace")] 218 | impl fmt::Display for SpanTraceOmited<'_> { 219 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 220 | if let Some(span_trace) = self.0 { 221 | if span_trace.status() == SpanTraceStatus::UNSUPPORTED { 222 | writeln!(f, "Warning: SpanTrace capture is Unsupported.")?; 223 | write!( 224 | f, 225 | "Ensure that you've setup a tracing-error ErrorLayer and the semver versions are compatible" 226 | )?; 227 | } 228 | } 229 | 230 | Ok(()) 231 | } 232 | } 233 | 234 | struct BacktraceOmited(bool); 235 | 236 | impl fmt::Display for BacktraceOmited { 237 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 238 | // Print some info on how to increase verbosity. 239 | if self.0 { 240 | write!( 241 | f, 242 | "Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it." 243 | )?; 244 | } else { 245 | // This text only makes sense if frames are displayed. 246 | write!( 247 | f, 248 | "Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering." 249 | )?; 250 | } 251 | 252 | Ok(()) 253 | } 254 | } 255 | 256 | struct SourceSnippets(Verbosity); 257 | 258 | impl fmt::Display for SourceSnippets { 259 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 260 | if self.0 <= Verbosity::Medium { 261 | write!( 262 | f, 263 | "Run with RUST_BACKTRACE=full to include source snippets." 264 | )?; 265 | } 266 | 267 | Ok(()) 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /color-eyre/tests/bt_disabled.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::eyre; 2 | use eyre::eyre; 3 | 4 | #[test] 5 | fn disabled() { 6 | color_eyre::config::HookBuilder::default() 7 | .display_env_section(false) 8 | .install() 9 | .unwrap(); 10 | 11 | let report = eyre!("error occured"); 12 | 13 | let report = format!("{:?}", report); 14 | assert!(!report.contains("RUST_BACKTRACE")); 15 | } 16 | -------------------------------------------------------------------------------- /color-eyre/tests/bt_enabled.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::eyre; 2 | use eyre::eyre; 3 | 4 | #[test] 5 | fn enabled() { 6 | color_eyre::config::HookBuilder::default() 7 | .display_env_section(true) 8 | .install() 9 | .unwrap(); 10 | 11 | let report = eyre!("error occured"); 12 | 13 | let report = format!("{:?}", report); 14 | assert!(report.contains("RUST_BACKTRACE")); 15 | } 16 | -------------------------------------------------------------------------------- /color-eyre/tests/data/theme_error_control.txt: -------------------------------------------------------------------------------- 1 | 2 | 0: test 3 | 4 | Location: 5 | tests/theme.rs:17 6 | 7 | Error: 8 | 0: error 9 | 10 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11 | 12 | 0: theme::get_error with msg="test" 13 | at tests/theme.rs:11 14 | 15 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 |  ⋮ 5 frames hidden ⋮  17 | 6: theme::get_error::create_report::hdb41452bef3fc05d 18 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:17 19 | 7: theme::get_error::{{closure}}::h739c7fe800e2d03f 20 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25 21 | 8: core::option::Option::ok_or_else::hd8e670bbca63e94a 22 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs:954 23 | 9: theme::get_error::h2f751f4927c6fecb 24 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25 25 | 10: theme::test_error_backwards_compatibility::hfc4be9f22c32535c 26 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:43 27 | 11: theme::test_error_backwards_compatibility::{{closure}}::hb001a9a908f0f5a4 28 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:41 29 | 12: core::ops::function::FnOnce::call_once::he26938a69d361bf6 30 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227 31 | 13: core::ops::function::FnOnce::call_once::h83cc023b85256d97 32 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227 33 | 14: test::__rust_begin_short_backtrace::h7330e4e8b0549e26 34 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:585 35 | 15:  as core::ops::function::FnOnce>::call_once::h6b77566b8f386abb 36 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs:1691 37 | 16:  as core::ops::function::FnOnce<()>>::call_once::h2ad5de64df41b71c 38 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs:271 39 | 17: std::panicking::try::do_call::he67b1e56b423a618 40 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:403 41 | 18: std::panicking::try::ha9224adcdd41a723 42 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:367 43 | 19: std::panic::catch_unwind::h9111b58ae0b27828 44 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs:133 45 | 20: test::run_test_in_process::h15b6b7d5919893aa 46 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:608 47 | 21: test::run_test::{{closure}}::h8ef02d13d4506b7f 48 | at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:572 49 | 22: test::run_test::{{closure}}::hcd7b423365d0ff7e 50 | at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:600 51 |  ⋮ 13 frames hidden ⋮  52 | 53 | Note: note 54 | Warning: warning 55 | Suggestion: suggestion 56 | 57 | Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering. 58 | Run with RUST_BACKTRACE=full to include source snippets. 59 | -------------------------------------------------------------------------------- /color-eyre/tests/data/theme_error_control_location.txt: -------------------------------------------------------------------------------- 1 | 2 | 0: test 3 | 4 | Location: 5 | tests/theme.rs:17 6 | 7 | Error: 8 | 0: error 9 | 10 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11 |  ⋮ 5 frames hidden ⋮  12 | 6: theme::get_error::create_report::hf800a973f2100b44 13 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:17 14 | 7: theme::get_error::{{closure}}::ha65156cf9648d3e0 15 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25 16 | 8: core::option::Option::ok_or_else::h08df66cff4c7bff2 17 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs:954 18 | 9: theme::get_error::h7c1fce8fa3550ff9 19 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25 20 | 10: theme::test_error_backwards_compatibility::h732311d7da5d7160 21 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:43 22 | 11: theme::test_error_backwards_compatibility::{{closure}}::h144cea82038adfc7 23 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:41 24 | 12: core::ops::function::FnOnce::call_once::h8d0ee3b0b70ed418 25 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227 26 | 13: core::ops::function::FnOnce::call_once::h83cc023b85256d97 27 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227 28 | 14: test::__rust_begin_short_backtrace::h7330e4e8b0549e26 29 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:585 30 | 15:  as core::ops::function::FnOnce>::call_once::h6b77566b8f386abb 31 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs:1691 32 | 16:  as core::ops::function::FnOnce<()>>::call_once::h2ad5de64df41b71c 33 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs:271 34 | 17: std::panicking::try::do_call::he67b1e56b423a618 35 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:403 36 | 18: std::panicking::try::ha9224adcdd41a723 37 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:367 38 | 19: std::panic::catch_unwind::h9111b58ae0b27828 39 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs:133 40 | 20: test::run_test_in_process::h15b6b7d5919893aa 41 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:608 42 | 21: test::run_test::{{closure}}::h8ef02d13d4506b7f 43 | at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:572 44 | 22: test::run_test::{{closure}}::hcd7b423365d0ff7e 45 | at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:600 46 |  ⋮ 13 frames hidden ⋮  47 | 48 | Note: note 49 | Warning: warning 50 | Suggestion: suggestion 51 | 52 | Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering. 53 | Run with RUST_BACKTRACE=full to include source snippets. 54 | -------------------------------------------------------------------------------- /color-eyre/tests/data/theme_error_control_minimal.txt: -------------------------------------------------------------------------------- 1 | 2 | 0: test 3 | 4 | Error: 5 | 0: error 6 | 7 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8 |  ⋮ 5 frames hidden ⋮  9 | 6: theme::get_error::create_report::h43540daddae98383 10 | at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:17 11 | 7: theme::get_error::{{closure}}::h40bbef2f4cd93fab 12 | at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:26 13 | 8: core::option::Option::ok_or_else::h8aa47839ff49cfbe 14 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/option.rs:1087 15 | 9: theme::get_error::h78b5b4d52bfbbad0 16 | at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:26 17 | 10: theme::test_error_backwards_compatibility::h9de398ce80defffa 18 | at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:45 19 | 11: theme::test_error_backwards_compatibility::{{closure}}::hbe7b8ad2562c4dc4 20 | at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:43 21 | 12: core::ops::function::FnOnce::call_once::hfc715417a1b707c5 22 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248 23 | 13: core::ops::function::FnOnce::call_once::h9ee1367930602049 24 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248 25 | 14: test::__rust_begin_short_backtrace::h35061c5e0f5ad5d6 26 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/test/src/lib.rs:572 27 | 15:  as core::ops::function::FnOnce>::call_once::h98fe3dd14bfe63ea 28 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/alloc/src/boxed.rs:1940 29 | 16:  as core::ops::function::FnOnce<()>>::call_once::h3ab012fb764e8d57 30 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panic/unwind_safe.rs:271 31 | 17: std::panicking::try::do_call::h810a5ea64fd04126 32 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:492 33 | 18: std::panicking::try::h0b213f9a8c1fe629 34 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:456 35 | 19: std::panic::catch_unwind::h00f746771ade371f 36 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs:137 37 | 20: test::run_test_in_process::h5645647f0d0a3da3 38 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/test/src/lib.rs:595 39 |  ⋮ 15 frames hidden ⋮  40 | 41 | Note: note 42 | Warning: warning 43 | Suggestion: suggestion 44 | 45 | Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering. 46 | Run with RUST_BACKTRACE=full to include source snippets. 47 | -------------------------------------------------------------------------------- /color-eyre/tests/data/theme_error_control_spantrace.txt: -------------------------------------------------------------------------------- 1 | 2 | 0: test 3 | 4 | Error: 5 | 0: error 6 | 7 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8 | 9 | 0: theme::get_error with msg="test" 10 | at tests/theme.rs:11 11 | 12 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13 |  ⋮ 5 frames hidden ⋮  14 | 6: theme::get_error::create_report::h4bc625c000e4636e 15 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:17 16 | 7: theme::get_error::{{closure}}::h3dee499015f52230 17 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25 18 | 8: core::option::Option::ok_or_else::h32a80642d5f9cd65 19 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs:954 20 | 9: theme::get_error::hb3756d9f0d65527f 21 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25 22 | 10: theme::test_error_backwards_compatibility::h69192dd92f3a8a2e 23 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:43 24 | 11: theme::test_error_backwards_compatibility::{{closure}}::hd9459c2e516ade18 25 | at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:41 26 | 12: core::ops::function::FnOnce::call_once::h540507413fe72275 27 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227 28 | 13: core::ops::function::FnOnce::call_once::h83cc023b85256d97 29 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227 30 | 14: test::__rust_begin_short_backtrace::h7330e4e8b0549e26 31 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:585 32 | 15:  as core::ops::function::FnOnce>::call_once::h6b77566b8f386abb 33 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs:1691 34 | 16:  as core::ops::function::FnOnce<()>>::call_once::h2ad5de64df41b71c 35 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs:271 36 | 17: std::panicking::try::do_call::he67b1e56b423a618 37 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:403 38 | 18: std::panicking::try::ha9224adcdd41a723 39 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:367 40 | 19: std::panic::catch_unwind::h9111b58ae0b27828 41 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs:133 42 | 20: test::run_test_in_process::h15b6b7d5919893aa 43 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:608 44 | 21: test::run_test::{{closure}}::h8ef02d13d4506b7f 45 | at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:572 46 | 22: test::run_test::{{closure}}::hcd7b423365d0ff7e 47 | at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:600 48 |  ⋮ 13 frames hidden ⋮  49 | 50 | Note: note 51 | Warning: warning 52 | Suggestion: suggestion 53 | 54 | Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering. 55 | Run with RUST_BACKTRACE=full to include source snippets. 56 | -------------------------------------------------------------------------------- /color-eyre/tests/data/theme_panic_control.txt: -------------------------------------------------------------------------------- 1 | Finished dev [unoptimized + debuginfo] target(s) in 0.03s 2 | Running `target/debug/examples/theme_test_helper` 3 | The application panicked (crashed). 4 | Message:  5 | Location: examples/theme_test_helper.rs:37 6 | 7 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8 | 9 | 0: theme_test_helper::get_error with msg="test" 10 | at examples/theme_test_helper.rs:34 11 | 12 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13 |  ⋮ 6 frames hidden ⋮  14 | 7: std::panic::panic_any::hd76a7f826307234c 15 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs:57 16 | 8: theme_test_helper::main::h767d3fd6c45048c8 17 | at /home/jlusby/git/yaahc/color-eyre/examples/theme_test_helper.rs:37 18 | 9: core::ops::function::FnOnce::call_once::hc5a1cd4127189dad 19 | at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227 20 |  ⋮ 15 frames hidden ⋮  21 | 22 | Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering. 23 | Run with RUST_BACKTRACE=full to include source snippets. 24 | -------------------------------------------------------------------------------- /color-eyre/tests/data/theme_panic_control_no_spantrace.txt: -------------------------------------------------------------------------------- 1 | Finished dev [unoptimized + debuginfo] target(s) in 0.07s 2 | Running `/home/username/dev/rust/eyre/target/debug/examples/theme_test_helper` 3 | The application panicked (crashed). 4 | Message:  5 | Location: color-eyre/examples/theme_test_helper.rs:38 6 | 7 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8 |  ⋮ 6 frames hidden ⋮  9 | 7: std::panic::panic_any::h4a05c03c4d0c389c 10 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs:61 11 | 8: theme_test_helper::main::hfc653b28cad3659d 12 | at /home/username/dev/rust/eyre/color-eyre/examples/theme_test_helper.rs:38 13 | 9: core::ops::function::FnOnce::call_once::hb0110cdf4417a5ed 14 | at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248 15 |  ⋮ 16 frames hidden ⋮  16 | 17 | Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering. 18 | Run with RUST_BACKTRACE=full to include source snippets. 19 | -------------------------------------------------------------------------------- /color-eyre/tests/install.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::install; 2 | 3 | #[test] 4 | fn double_install_should_not_panic() { 5 | install().unwrap(); 6 | assert!(install().is_err()); 7 | } 8 | -------------------------------------------------------------------------------- /color-eyre/tests/location_disabled.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "track-caller")] 2 | #[test] 3 | fn disabled() { 4 | use color_eyre::eyre; 5 | use eyre::eyre; 6 | 7 | color_eyre::config::HookBuilder::default() 8 | .display_location_section(false) 9 | .install() 10 | .unwrap(); 11 | 12 | let report = eyre!("error occured"); 13 | 14 | let report = format!("{:?}", report); 15 | assert!(!report.contains("Location:")); 16 | } 17 | -------------------------------------------------------------------------------- /color-eyre/tests/wasm.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "wasm32")] 2 | #[wasm_bindgen_test::wasm_bindgen_test] 3 | pub fn color_eyre_simple() { 4 | use color_eyre::eyre::WrapErr; 5 | use color_eyre::*; 6 | 7 | install().expect("Failed to install color_eyre"); 8 | let err_str = format!( 9 | "{:?}", 10 | Err::<(), Report>(eyre::eyre!("Base Error")) 11 | .note("A note") 12 | .suggestion("A suggestion") 13 | .wrap_err("A wrapped error") 14 | .unwrap_err() 15 | ); 16 | // Print it out so if people run with `-- --nocapture`, they 17 | // can see the full message. 18 | println!("Error String is:\n\n{}", err_str); 19 | assert!(err_str.contains("A wrapped error")); 20 | assert!(err_str.contains("A suggestion")); 21 | assert!(err_str.contains("A note")); 22 | assert!(err_str.contains("Base Error")); 23 | } 24 | -------------------------------------------------------------------------------- /color-spantrace/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /color-spantrace/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | 9 | ## [Unreleased] - ReleaseDate 10 | 11 | ## [0.2.1] - 2023-11-17 12 | ### Fixed 13 | - Add license files [by erickt](https://github.com/eyre-rs/color-spantrace/pull/19) 14 | 15 | ## [0.2.0] - 2022-01-12 16 | ### Changed 17 | - Updated dependency versions to match latest tracing versions 18 | 19 | ## [0.1.6] - 2020-12-02 20 | ### Fixed 21 | - Ignore all io errors when resolving source files instead of only file not 22 | found errors 23 | 24 | ## [v0.1.5] - 2020-12-01 25 | ### Added 26 | - Support custom color themes for spantrace format 27 | 28 | 29 | [Unreleased]: https://github.com/eyre-rs/color-spantrace/compare/v0.2.1...HEAD 30 | [0.2.1]: https://github.com/eyre-rs/color-spantrace/compare/v0.2.0...v0.2.1 31 | [0.2.0]: https://github.com/eyre-rs/color-spantrace/compare/v0.1.6...v0.2.0 32 | [0.1.6]: https://github.com/eyre-rs/color-spantrace/compare/v0.1.5...v0.1.6 33 | [v0.1.5]: https://github.com/eyre-rs/color-spantrace/releases/tag/v0.1.5 34 | -------------------------------------------------------------------------------- /color-spantrace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "color-spantrace" 3 | version = "0.3.0" 4 | description = "A pretty printer for tracing_error::SpanTrace based on color-backtrace" 5 | documentation = "https://docs.rs/color-spantrace" 6 | include = ["README.md", "src/**/*.rs", "build.rs", "LICENSE-APACHE", "LICENSE-MIT"] 7 | 8 | authors = { workspace = true } 9 | edition = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | readme = { workspace = true } 13 | rust-version = { workspace = true } 14 | 15 | [dependencies] 16 | tracing-error = "0.2.0" 17 | tracing-core = "0.1.21" 18 | owo-colors = { workspace = true } 19 | once_cell = { workspace = true } 20 | 21 | [dev-dependencies] 22 | tracing-subscriber = "0.3.4" 23 | tracing = "0.1.29" 24 | ansi-parser = "0.8" # used for testing color schemes 25 | 26 | [package.metadata.docs.rs] 27 | all-features = true 28 | rustdoc-args = ["--cfg", "docsrs"] 29 | 30 | [package.metadata.workspaces] 31 | independent = true 32 | -------------------------------------------------------------------------------- /color-spantrace/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /color-spantrace/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /color-spantrace/README.md: -------------------------------------------------------------------------------- 1 | # color-spantrace 2 | 3 | [![Build Status][actions-badge]][actions-url] 4 | [![Latest Version][version-badge]][version-url] 5 | [![Rust Documentation][docs-badge]][docs-url] 6 | 7 | [actions-badge]: https://github.com/eyre-rs/eyre/workflows/Continuous%20integration/badge.svg 8 | [actions-url]: https://github.com/eyre-rs/eyre/actions?query=workflow%3A%22Continuous+integration%22 9 | [version-badge]: https://img.shields.io/crates/v/color-spantrace.svg 10 | [version-url]: https://crates.io/crates/color-spantrace 11 | [docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg 12 | [docs-url]: https://docs.rs/color-spantrace 13 | 14 | A rust library for colorizing [`tracing_error::SpanTrace`] objects in the style 15 | of [`color-backtrace`]. 16 | 17 | ## Setup 18 | 19 | Add the following to your `Cargo.toml`: 20 | 21 | ```toml 22 | [dependencies] 23 | color-spantrace = "0.2" 24 | tracing = "0.1" 25 | tracing-error = "0.2" 26 | tracing-subscriber = "0.3" 27 | ``` 28 | 29 | Setup a tracing subscriber with an `ErrorLayer`: 30 | 31 | ```rust 32 | use tracing_error::ErrorLayer; 33 | use tracing_subscriber::{prelude::*, registry::Registry}; 34 | 35 | Registry::default().with(ErrorLayer::default()).init(); 36 | ``` 37 | 38 | Create spans and enter them: 39 | 40 | ```rust 41 | use tracing::instrument; 42 | use tracing_error::SpanTrace; 43 | 44 | #[instrument] 45 | fn foo() -> SpanTrace { 46 | SpanTrace::capture() 47 | } 48 | ``` 49 | 50 | And finally colorize the `SpanTrace`: 51 | 52 | ```rust 53 | use tracing_error::SpanTrace; 54 | 55 | let span_trace = SpanTrace::capture(); 56 | println!("{}", color_spantrace::colorize(&span_trace)); 57 | ``` 58 | 59 | ## Example 60 | 61 | This example is taken from `examples/color-spantrace-usage.rs`: 62 | 63 | ```rust 64 | use tracing::instrument; 65 | use tracing_error::{ErrorLayer, SpanTrace}; 66 | use tracing_subscriber::{prelude::*, registry::Registry}; 67 | 68 | #[instrument] 69 | fn main() { 70 | Registry::default().with(ErrorLayer::default()).init(); 71 | 72 | let span_trace = one(42); 73 | println!("{}", color_spantrace::colorize(&span_trace)); 74 | } 75 | 76 | #[instrument] 77 | fn one(i: u32) -> SpanTrace { 78 | two() 79 | } 80 | 81 | #[instrument] 82 | fn two() -> SpanTrace { 83 | SpanTrace::capture() 84 | } 85 | ``` 86 | 87 | This creates the following output 88 | 89 | ### Minimal Format 90 | 91 | ![minimal format](https://raw.githubusercontent.com/eyre-rs/eyre/refs/heads/master/color-spantrace/pictures/minimal.png) 92 | 93 | ### Full Format 94 | 95 | ![Full format](https://raw.githubusercontent.com/eyre-rs/eyre/refs/heads/master/color-spantrace/pictures/full.png) 96 | 97 | #### License 98 | 99 | 100 | Licensed under either of Apache License, Version 101 | 2.0 or MIT license at your option. 102 | 103 | 104 |
105 | 106 | 107 | Unless you explicitly state otherwise, any contribution intentionally submitted 108 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 109 | be dual licensed as above, without any additional terms or conditions. 110 | 111 | 112 | [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html 113 | [`color-backtrace`]: https://github.com/athre0z/color-backtrace 114 | -------------------------------------------------------------------------------- /color-spantrace/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsString; 3 | use std::process::Command; 4 | use std::str; 5 | 6 | fn main() { 7 | let version = match rustc_version_info() { 8 | Some(version) => version, 9 | None => return, 10 | }; 11 | version.toolchain.set_feature(); 12 | } 13 | 14 | #[derive(PartialEq)] 15 | enum Toolchain { 16 | Stable, 17 | Beta, 18 | Nightly, 19 | } 20 | 21 | impl Toolchain { 22 | fn set_feature(self) { 23 | println!("cargo:rustc-check-cfg=cfg(nightly)"); 24 | println!("cargo:rustc-check-cfg=cfg(beta)"); 25 | println!("cargo:rustc-check-cfg=cfg(stable)"); 26 | match self { 27 | Toolchain::Nightly => println!("cargo:rustc-cfg=nightly"), 28 | Toolchain::Beta => println!("cargo:rustc-cfg=beta"), 29 | Toolchain::Stable => println!("cargo:rustc-cfg=stable"), 30 | } 31 | } 32 | } 33 | 34 | struct VersionInfo { 35 | toolchain: Toolchain, 36 | } 37 | 38 | fn rustc_version_info() -> Option { 39 | let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); 40 | let output = Command::new(rustc).arg("--version").output().ok()?; 41 | let version = str::from_utf8(&output.stdout).ok()?; 42 | let mut pieces = version.split(['.', ' ', '-']); 43 | if pieces.next() != Some("rustc") { 44 | return None; 45 | } 46 | let _major: u32 = pieces.next()?.parse().ok()?; 47 | let _minor: u32 = pieces.next()?.parse().ok()?; 48 | let _patch: u32 = pieces.next()?.parse().ok()?; 49 | let toolchain = match pieces.next() { 50 | Some("beta") => Toolchain::Beta, 51 | Some("nightly") => Toolchain::Nightly, 52 | _ => Toolchain::Stable, 53 | }; 54 | let version = VersionInfo { toolchain }; 55 | Some(version) 56 | } 57 | -------------------------------------------------------------------------------- /color-spantrace/examples/color-spantrace-usage.rs: -------------------------------------------------------------------------------- 1 | use tracing::instrument; 2 | use tracing_error::{ErrorLayer, SpanTrace}; 3 | use tracing_subscriber::{prelude::*, registry::Registry}; 4 | 5 | #[instrument] 6 | fn main() { 7 | Registry::default().with(ErrorLayer::default()).init(); 8 | 9 | let span_trace = one(42); 10 | println!("{}", color_spantrace::colorize(&span_trace)); 11 | } 12 | 13 | #[instrument] 14 | fn one(i: u32) -> SpanTrace { 15 | two() 16 | } 17 | 18 | #[instrument] 19 | fn two() -> SpanTrace { 20 | SpanTrace::capture() 21 | } 22 | -------------------------------------------------------------------------------- /color-spantrace/pictures/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyre-rs/eyre/dbaf9ed0eba704ab9b87a1ec5ff247a1c136f67a/color-spantrace/pictures/full.png -------------------------------------------------------------------------------- /color-spantrace/pictures/minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyre-rs/eyre/dbaf9ed0eba704ab9b87a1ec5ff247a1c136f67a/color-spantrace/pictures/minimal.png -------------------------------------------------------------------------------- /color-spantrace/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A rust library for colorizing [`tracing_error::SpanTrace`] objects in the style 2 | //! of [`color-backtrace`]. 3 | //! 4 | //! ## Setup 5 | //! 6 | //! Add the following to your `Cargo.toml`: 7 | //! 8 | //! ```toml 9 | //! [dependencies] 10 | //! color-spantrace = "0.2" 11 | //! tracing = "0.1" 12 | //! tracing-error = "0.2" 13 | //! tracing-subscriber = "0.3" 14 | //! ``` 15 | //! 16 | //! Setup a tracing subscriber with an `ErrorLayer`: 17 | //! 18 | //! ```rust 19 | //! use tracing_error::ErrorLayer; 20 | //! use tracing_subscriber::{prelude::*, registry::Registry}; 21 | //! 22 | //! Registry::default().with(ErrorLayer::default()).init(); 23 | //! ``` 24 | //! 25 | //! Create spans and enter them: 26 | //! 27 | //! ```rust 28 | //! use tracing::instrument; 29 | //! use tracing_error::SpanTrace; 30 | //! 31 | //! #[instrument] 32 | //! fn foo() -> SpanTrace { 33 | //! SpanTrace::capture() 34 | //! } 35 | //! ``` 36 | //! 37 | //! And finally colorize the `SpanTrace`: 38 | //! 39 | //! ```rust 40 | //! use tracing_error::SpanTrace; 41 | //! 42 | //! let span_trace = SpanTrace::capture(); 43 | //! println!("{}", color_spantrace::colorize(&span_trace)); 44 | //! ``` 45 | //! 46 | //! ## Output Format 47 | //! 48 | //! Running `examples/color-spantrace-usage.rs` from the `color-spantrace` repo produces the following output: 49 | //! 50 | //!
 cargo run --example color-spantrace-usage
 51 | //!     Finished dev [unoptimized + debuginfo] target(s) in 0.04s
 52 | //!      Running `target/debug/examples/color-spantrace-usage`
 53 | //! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 54 | //!
 55 | //!  0: color-spantrace-usage::two
 56 | //!     at examples/color-spantrace-usage.rs:18
 57 | //!  1: color-spantrace-usage::one with i=42
 58 | //!     at examples/color-spantrace-usage.rs:13
59 | //! 60 | //! [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html 61 | //! [`color-backtrace`]: https://github.com/athre0z/color-backtrace 62 | #![doc(html_root_url = "https://docs.rs/color-spantrace/0.2.1")] 63 | #![cfg_attr( 64 | nightly, 65 | feature(rustdoc_missing_doc_code_examples), 66 | warn(rustdoc::missing_doc_code_examples) 67 | )] 68 | #![warn( 69 | missing_debug_implementations, 70 | missing_docs, 71 | rust_2018_idioms, 72 | unreachable_pub, 73 | bad_style, 74 | dead_code, 75 | improper_ctypes, 76 | non_shorthand_field_patterns, 77 | no_mangle_generic_items, 78 | overflowing_literals, 79 | path_statements, 80 | patterns_in_fns_without_body, 81 | unconditional_recursion, 82 | unused, 83 | unused_allocation, 84 | unused_comparisons, 85 | unused_parens, 86 | while_true 87 | )] 88 | use once_cell::sync::OnceCell; 89 | use owo_colors::{style, Style}; 90 | use std::env; 91 | use std::fmt; 92 | use std::fs::File; 93 | use std::io::{BufRead, BufReader}; 94 | use tracing_error::SpanTrace; 95 | 96 | static THEME: OnceCell = OnceCell::new(); 97 | 98 | /// A struct that represents theme that is used by `color_spantrace` 99 | #[derive(Debug, Copy, Clone, Default)] 100 | pub struct Theme { 101 | file: Style, 102 | line_number: Style, 103 | target: Style, 104 | fields: Style, 105 | active_line: Style, 106 | } 107 | 108 | impl Theme { 109 | /// Create blank theme 110 | pub fn new() -> Self { 111 | Self::default() 112 | } 113 | 114 | /// A theme for a dark background. This is the default 115 | pub fn dark() -> Self { 116 | Self { 117 | file: style().purple(), 118 | line_number: style().purple(), 119 | active_line: style().white().bold(), 120 | target: style().bright_red(), 121 | fields: style().bright_cyan(), 122 | } 123 | } 124 | 125 | // XXX same as with `light` in `color_eyre` 126 | /// A theme for a light background 127 | pub fn light() -> Self { 128 | Self { 129 | file: style().purple(), 130 | line_number: style().purple(), 131 | target: style().red(), 132 | fields: style().blue(), 133 | active_line: style().bold(), 134 | } 135 | } 136 | 137 | /// Styles printed paths 138 | pub fn file(mut self, style: Style) -> Self { 139 | self.file = style; 140 | self 141 | } 142 | 143 | /// Styles the line number of a file 144 | pub fn line_number(mut self, style: Style) -> Self { 145 | self.line_number = style; 146 | self 147 | } 148 | 149 | /// Styles the target (i.e. the module and function name, and so on) 150 | pub fn target(mut self, style: Style) -> Self { 151 | self.target = style; 152 | self 153 | } 154 | 155 | /// Styles fields associated with a the `tracing::Span`. 156 | pub fn fields(mut self, style: Style) -> Self { 157 | self.fields = style; 158 | self 159 | } 160 | 161 | /// Styles the selected line of displayed code 162 | pub fn active_line(mut self, style: Style) -> Self { 163 | self.active_line = style; 164 | self 165 | } 166 | } 167 | 168 | /// An error returned by `set_theme` if a global theme was already set 169 | #[derive(Debug)] 170 | pub struct InstallThemeError; 171 | 172 | impl fmt::Display for InstallThemeError { 173 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 174 | f.write_str("could not set the provided `Theme` globally as another was already set") 175 | } 176 | } 177 | 178 | impl std::error::Error for InstallThemeError {} 179 | 180 | /// Sets the global theme. 181 | /// 182 | /// # Details 183 | /// 184 | /// This can only be set once and otherwise fails. 185 | /// 186 | /// **Note:** `colorize` sets the global theme implicitly, if it was not set already. So calling `colorize` and then `set_theme` fails 187 | pub fn set_theme(theme: Theme) -> Result<(), InstallThemeError> { 188 | THEME.set(theme).map_err(|_| InstallThemeError) 189 | } 190 | 191 | /// Display a [`SpanTrace`] with colors and source 192 | /// 193 | /// This function returns an `impl Display` type which can be then used in place of the original 194 | /// SpanTrace when writing it too the screen or buffer. 195 | /// 196 | /// # Example 197 | /// 198 | /// ```rust 199 | /// use tracing_error::SpanTrace; 200 | /// 201 | /// let span_trace = SpanTrace::capture(); 202 | /// println!("{}", color_spantrace::colorize(&span_trace)); 203 | /// ``` 204 | /// 205 | /// **Note:** `colorize` sets the global theme implicitly, if it was not set already. So calling `colorize` and then `set_theme` fails 206 | /// 207 | /// [`SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html 208 | pub fn colorize(span_trace: &SpanTrace) -> impl fmt::Display + '_ { 209 | let theme = *THEME.get_or_init(Theme::dark); 210 | ColorSpanTrace { span_trace, theme } 211 | } 212 | 213 | struct ColorSpanTrace<'a> { 214 | span_trace: &'a SpanTrace, 215 | theme: Theme, 216 | } 217 | 218 | macro_rules! try_bool { 219 | ($e:expr, $dest:ident) => {{ 220 | let ret = $e.unwrap_or_else(|e| $dest = Err(e)); 221 | 222 | if $dest.is_err() { 223 | return false; 224 | } 225 | 226 | ret 227 | }}; 228 | } 229 | 230 | struct Frame<'a> { 231 | metadata: &'a tracing_core::Metadata<'static>, 232 | fields: &'a str, 233 | theme: Theme, 234 | } 235 | 236 | /// Defines how verbose the backtrace is supposed to be. 237 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 238 | enum Verbosity { 239 | /// Print a small message including the panic payload and the panic location. 240 | Minimal, 241 | /// Everything in `Minimal` and additionally print a backtrace. 242 | Medium, 243 | /// Everything in `Medium` plus source snippets for all backtrace locations. 244 | Full, 245 | } 246 | 247 | impl Verbosity { 248 | fn lib_from_env() -> Self { 249 | Self::convert_env( 250 | env::var("RUST_LIB_BACKTRACE") 251 | .or_else(|_| env::var("RUST_BACKTRACE")) 252 | .ok(), 253 | ) 254 | } 255 | 256 | fn convert_env(env: Option) -> Self { 257 | match env { 258 | Some(ref x) if x == "full" => Verbosity::Full, 259 | Some(_) => Verbosity::Medium, 260 | None => Verbosity::Minimal, 261 | } 262 | } 263 | } 264 | 265 | impl Frame<'_> { 266 | fn print(&self, i: u32, f: &mut fmt::Formatter<'_>) -> fmt::Result { 267 | self.print_header(i, f)?; 268 | self.print_fields(f)?; 269 | self.print_source_location(f)?; 270 | Ok(()) 271 | } 272 | 273 | fn print_header(&self, i: u32, f: &mut fmt::Formatter<'_>) -> fmt::Result { 274 | write!( 275 | f, 276 | "{:>2}: {}{}{}", 277 | i, 278 | self.theme.target.style(self.metadata.target()), 279 | self.theme.target.style("::"), 280 | self.theme.target.style(self.metadata.name()), 281 | ) 282 | } 283 | 284 | fn print_fields(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 285 | if !self.fields.is_empty() { 286 | write!(f, " with {}", self.theme.fields.style(self.fields))?; 287 | } 288 | 289 | Ok(()) 290 | } 291 | 292 | fn print_source_location(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 293 | if let Some(file) = self.metadata.file() { 294 | let lineno = self 295 | .metadata 296 | .line() 297 | .map_or("".to_owned(), |x| x.to_string()); 298 | write!( 299 | f, 300 | "\n at {}:{}", 301 | self.theme.file.style(file), 302 | self.theme.line_number.style(lineno), 303 | )?; 304 | } else { 305 | write!(f, "\n at ")?; 306 | } 307 | 308 | Ok(()) 309 | } 310 | 311 | fn print_source_if_avail(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 312 | let (lineno, filename) = match (self.metadata.line(), self.metadata.file()) { 313 | (Some(a), Some(b)) => (a, b), 314 | // Without a line number and file name, we can't sensibly proceed. 315 | _ => return Ok(()), 316 | }; 317 | 318 | let file = match File::open(filename) { 319 | Ok(file) => file, 320 | // ignore io errors and just don't print the source 321 | Err(_) => return Ok(()), 322 | }; 323 | 324 | use std::fmt::Write; 325 | 326 | // Extract relevant lines. 327 | let reader = BufReader::new(file); 328 | let start_line = lineno - 2.min(lineno - 1); 329 | let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5); 330 | let mut buf = String::new(); 331 | for (line, cur_line_no) in surrounding_src.zip(start_line..) { 332 | if cur_line_no == lineno { 333 | write!( 334 | &mut buf, 335 | "{:>8} > {}", 336 | cur_line_no.to_string(), 337 | line.unwrap() 338 | )?; 339 | write!(f, "\n{}", self.theme.active_line.style(&buf))?; 340 | buf.clear(); 341 | } else { 342 | write!(f, "\n{:>8} │ {}", cur_line_no, line.unwrap())?; 343 | } 344 | } 345 | 346 | Ok(()) 347 | } 348 | } 349 | 350 | impl fmt::Display for ColorSpanTrace<'_> { 351 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 352 | let mut err = Ok(()); 353 | let mut span = 0; 354 | 355 | writeln!(f, "{:━^80}\n", " SPANTRACE ")?; 356 | self.span_trace.with_spans(|metadata, fields| { 357 | let frame = Frame { 358 | metadata, 359 | fields, 360 | theme: self.theme, 361 | }; 362 | 363 | if span > 0 { 364 | try_bool!(write!(f, "\n",), err); 365 | } 366 | 367 | try_bool!(frame.print(span, f), err); 368 | 369 | if Verbosity::lib_from_env() == Verbosity::Full { 370 | try_bool!(frame.print_source_if_avail(f), err); 371 | } 372 | 373 | span += 1; 374 | true 375 | }); 376 | 377 | err 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /color-spantrace/tests/data/theme_control.txt: -------------------------------------------------------------------------------- 1 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2 | 3 | 0: themes::test_capture with x=42 4 | at tests/themes.rs:42 5 | 40 │ use tracing_subscriber::{prelude::*, registry::Registry}; 6 | 41 │ 7 |  42 > #[instrument] 8 | 43 │ fn test_capture(x: u8) -> SpanTrace { 9 | 44 │ #[allow(clippy::if_same_then_else)] -------------------------------------------------------------------------------- /color-spantrace/tests/themes.rs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | # How this test works: 4 | 5 | 1) generate a spantrace with `test_capture` 6 | 7 | 2) convert the spantrace to a string 8 | 9 | 3) load stored spantrace control to compare to spantrace string (stored in the path of `control_file_path` below) 10 | 11 | 4) if `control_file_path` doesn't exist, generate corresponding file in the current working directory and request the user to fix the issue (see below) 12 | 13 | 5) extract ANSI escaping sequences (of control and current spantrace) 14 | 15 | 6) compare if the current spantrace and the control contains the same ANSI escape sequences 16 | 17 | 7) If not, fail and show the full strings of the control and the current spantrace 18 | 19 | # Re-generating the control 20 | 21 | If the control spantrace is lost and/or it needs to be re-generated, do the following: 22 | 23 | 1) Checkout the `color_spantrace` version from Git that you want to test against 24 | 25 | 3) Add this test file to '/tests' 26 | 27 | 4) If `control_file_path` exist, delete it 28 | 29 | 5) If you now run this test, it will generate a test control file in the current working directory 30 | 31 | 6) copy this file to `control_file_path` (see instructions that are shown) 32 | 33 | */ 34 | 35 | use tracing::instrument; 36 | use tracing_error::SpanTrace; 37 | 38 | #[instrument] 39 | fn test_capture(x: u8) -> SpanTrace { 40 | #[allow(clippy::if_same_then_else)] 41 | if x == 42 { 42 | SpanTrace::capture() 43 | } else { 44 | SpanTrace::capture() 45 | } 46 | } 47 | 48 | #[cfg(not(miri))] 49 | #[test] 50 | fn test_backwards_compatibility() { 51 | use ansi_parser::{AnsiParser, AnsiSequence, Output}; 52 | use std::{fs, path::Path}; 53 | use tracing_error::ErrorLayer; 54 | use tracing_subscriber::{prelude::*, registry::Registry}; 55 | std::env::set_var("RUST_LIB_BACKTRACE", "full"); 56 | 57 | // This integration is ran by cargo with cwd="color-spantrace", but the string literals for 58 | // `file!` are relative to the workspace root. This changes the cwd to the workspace root. 59 | // 60 | // The behavior of file! when invoked from a workspace is not documented. See: . 61 | // 62 | // Noteworthy: non-member path dependencies will get an absolute path, as will registry and git 63 | // dependencies. 64 | std::env::set_current_dir("..").unwrap(); 65 | 66 | Registry::default().with(ErrorLayer::default()).init(); 67 | 68 | let spantrace = test_capture(42); 69 | let colored_spantrace = format!("{}", color_spantrace::colorize(&spantrace)); 70 | 71 | let control_file_name = "theme_control.txt"; 72 | let control_file_path = ["color-spantrace/tests/data/", control_file_name].concat(); 73 | 74 | // If `control_file_path` is missing, save corresponding file to current working directory, and panic with the request to move these files to `control_file_path`, and to commit them to Git. Being explicit (instead of saving directly to `control_file_path` to make sure `control_file_path` is committed to Git. These files anyway should never be missing. 75 | 76 | if !Path::new(&control_file_path).is_file() { 77 | std::fs::write(control_file_name, &colored_spantrace) 78 | .expect("\n\nError saving `colored_spantrace` to a file"); 79 | panic!("Required test data missing! Fix this, by moving '{}' to '{}', and commit it to Git.\n\nNote: '{0}' was just generated in the current working directory.\n\n", control_file_name, control_file_path); 80 | } 81 | 82 | // `unwrap` should never fail with files generated by this test 83 | let colored_spantrace_control = 84 | String::from_utf8(fs::read(control_file_path).unwrap()).unwrap(); 85 | 86 | fn get_ansi(s: &str) -> impl Iterator + '_ { 87 | s.ansi_parse().filter_map(|x| { 88 | if let Output::Escape(ansi) = x { 89 | Some(ansi) 90 | } else { 91 | None 92 | } 93 | }) 94 | } 95 | 96 | let colored_spantrace_ansi = get_ansi(&colored_spantrace); 97 | let colored_spantrace_control_ansi = get_ansi(&colored_spantrace_control); 98 | 99 | assert!( 100 | colored_spantrace_ansi.eq(colored_spantrace_control_ansi), 101 | "\x1b[0mANSI escape sequences are not identical to control!\n\nCONTROL:\n\n{}\n\n\n\n{:?}\n\nCURRENT:\n\n{}\n\n\n\n{:?}\n\n", &colored_spantrace_control, &colored_spantrace_control, &colored_spantrace, &colored_spantrace 102 | // `\x1b[0m` clears previous ANSI escape sequences 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /eyre/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | 9 | ## [Unreleased] - ReleaseDate 10 | ### Added 11 | - feature flag for `anyhow` compatibility traits [by LeoniePhiline](https://github.com/eyre-rs/eyre/pull/138) 12 | 13 | ## [0.6.11] - 2023-12-13 14 | ### Fixed 15 | - stale references to `Error` in docstrings [by birkenfeld](https://github.com/eyre-rs/eyre/pull/87) 16 | 17 | ### Added 18 | - one-argument ensure!($expr) [by sharnoff](https://github.com/eyre-rs/eyre/pull/86) 19 | - documentation on the performance characteristics of `wrap_err` vs `wrap_err_with` [by akshayknarayan](https://github.com/eyre-rs/eyre/pull/93) 20 | - tl;dr: `wrap_err_with` is faster unless the constructed error object already exists 21 | - ~~automated conversion to external errors for ensure! and bail! [by j-baker](https://github.com/eyre-rs/eyre/pull/95)~~ breaking change: shelved for next major release 22 | - eyre::Ok for generating eyre::Ok() without fully specifying the type [by kylewlacy](https://github.com/eyre-rs/eyre/pull/91) 23 | - `OptionExt::ok_or_eyre` for yielding static `Report`s from `None` [by LeoniePhiline](https://github.com/eyre-rs/eyre/pull/125) 24 | 25 | ### New Contributors 26 | - @sharnoff made their first contribution in https://github.com/eyre-rs/eyre/pull/86 27 | - @akshayknarayan made their first contribution in https://github.com/eyre-rs/eyre/pull/93 28 | - @j-baker made their first contribution in https://github.com/eyre-rs/eyre/pull/95 29 | - @kylewlacy made their first contribution in https://github.com/eyre-rs/eyre/pull/91 30 | - @LeoniePhiline made their first contribution in https://github.com/eyre-rs/eyre/pull/129 31 | 32 | ~~## [0.6.10] - 2023-12-07~~ Yanked 33 | 34 | ## [0.6.9] - 2023-11-17 35 | ### Fixed 36 | - stacked borrows when dropping [by TimDiekmann](https://github.com/eyre-rs/eyre/pull/81) 37 | - miri validation errors through now stricter provenance [by ten3roberts](https://github.com/eyre-rs/eyre/pull/103) 38 | - documentation on no_std support [by thenorili](https://github.com/eyre-rs/eyre/pull/111) 39 | 40 | ### Added 41 | - monorepo for eyre-related crates [by pksunkara](https://github.com/eyre-rs/eyre/pull/104), [[2]](https://github.com/eyre-rs/eyre/pull/105)[[3]](https://github.com/eyre-rs/eyre/pull/107) 42 | - CONTRIBUTING.md [by yaahc](https://github.com/eyre-rs/eyre/pull/99) 43 | 44 | ## [0.6.8] - 2022-04-04 45 | ### Added 46 | - `#[must_use]` to `Report` 47 | - `must-install` feature to help reduce binary sizes when using a custom `EyreHandler` 48 | 49 | ## [0.6.7] - 2022-02-24 50 | ### Fixed 51 | - missing track_caller annotation to new format arg capture constructor 52 | 53 | ## [0.6.6] - 2022-01-19 54 | ### Added 55 | - support for format arguments capture on 1.58 and later 56 | 57 | ## [0.6.5] - 2021-01-05 58 | ### Added 59 | - optional support for converting into `pyo3` exceptions 60 | 61 | ## [0.6.4] - 2021-01-04 62 | ### Fixed 63 | - missing track_caller annotations to `wrap_err` related trait methods 64 | 65 | ## [0.6.3] - 2020-11-10 66 | ### Fixed 67 | - missing track_caller annotation to autoref specialization functions 68 | 69 | ## [0.6.2] - 2020-10-27 70 | ### Fixed 71 | - missing track_caller annotation to new_adhoc function 72 | 73 | ## [0.6.1] - 2020-09-28 74 | ### Added 75 | - support for track_caller on rust versions where it is available 76 | 77 | 78 | 79 | [Unreleased]: https://github.com/eyre-rs/eyre/compare/v0.6.11...HEAD 80 | [0.6.11]: https://github.com/eyre-rs/eyre/compare/v0.6.9...v0.6.11 81 | [0.6.9]: https://github.com/eyre-rs/eyre/compare/v0.6.8...v0.6.9 82 | [0.6.8]: https://github.com/eyre-rs/eyre/compare/v0.6.7...v0.6.8 83 | [0.6.7]: https://github.com/eyre-rs/eyre/compare/v0.6.6...v0.6.7 84 | [0.6.6]: https://github.com/eyre-rs/eyre/compare/v0.6.5...v0.6.6 85 | [0.6.5]: https://github.com/eyre-rs/eyre/compare/v0.6.4...v0.6.5 86 | [0.6.4]: https://github.com/eyre-rs/eyre/compare/v0.6.3...v0.6.4 87 | [0.6.3]: https://github.com/eyre-rs/eyre/compare/v0.6.2...v0.6.3 88 | [0.6.2]: https://github.com/eyre-rs/eyre/compare/v0.6.1...v0.6.2 89 | [0.6.1]: https://github.com/eyre-rs/eyre/releases/tag/v0.6.1 90 | -------------------------------------------------------------------------------- /eyre/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eyre" 3 | version = "1.0.0" 4 | authors = ["David Tolnay ", "Jane Lusby "] 5 | description = "Flexible concrete Error Reporting type built on std::error::Error with customizable Reports" 6 | documentation = "https://docs.rs/eyre" 7 | categories = ["rust-patterns"] 8 | 9 | edition = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | readme = { workspace = true } 13 | rust-version = { workspace = true } 14 | 15 | [features] 16 | default = ["anyhow", "auto-install", "track-caller"] 17 | anyhow = [] 18 | auto-install = [] 19 | track-caller = [] 20 | 21 | [dependencies] 22 | indenter = { workspace = true } 23 | once_cell = { workspace = true } 24 | pyo3 = { version = "0.24.0", optional = true, default-features = false } 25 | 26 | [build-dependencies] 27 | autocfg = { workspace = true } 28 | 29 | [dev-dependencies] 30 | futures = { version = "0.3", default-features = false } 31 | rustversion = "1.0" 32 | thiserror = "1.0" 33 | trybuild = { version = "=1.0.89", features = ["diff"] } # pinned due to MSRV 34 | backtrace = "0.3.46" 35 | anyhow = "1.0.28" 36 | syn = { version = "2.0", features = ["full"] } 37 | pyo3 = { version = "0.24", default-features = false, features = ["auto-initialize"] } 38 | 39 | [package.metadata.docs.rs] 40 | targets = ["x86_64-unknown-linux-gnu"] 41 | rustdoc-args = ["--cfg", "doc_cfg"] 42 | 43 | [package.metadata.workspaces] 44 | independent = true 45 | 46 | -------------------------------------------------------------------------------- /eyre/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /eyre/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /eyre/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /eyre/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, fs, 3 | path::Path, 4 | process::{Command, ExitStatus}, 5 | }; 6 | 7 | fn main() { 8 | println!("cargo:rustc-check-cfg=cfg(nightly)"); 9 | println!("cargo:rustc-check-cfg=cfg(track_caller)"); 10 | println!("cargo:rustc-check-cfg=cfg(generic_member_access)"); 11 | println!("cargo:rustc-check-cfg=cfg(eyre_no_fmt_args_capture)"); 12 | println!("cargo:rustc-check-cfg=cfg(backtrace)"); 13 | println!("cargo:rustc-check-cfg=cfg(eyre_no_fmt_arguments_as_str)"); 14 | println!("cargo:rustc-check-cfg=cfg(doc_cfg)"); 15 | let ac = autocfg::new(); 16 | 17 | // https://github.com/rust-lang/rust/issues/99301 [nightly] 18 | // 19 | // Autocfg does currently not support custom probes, or `nightly` only features 20 | match compile_probe(GENERIC_MEMBER_ACCESS_PROBE) { 21 | Some(status) if status.success() => autocfg::emit("generic_member_access"), 22 | _ => {} 23 | } 24 | 25 | // https://github.com/rust-lang/rust/issues/47809 [rustc-1.46] 26 | ac.emit_expression_cfg("std::panic::Location::caller", "track_caller"); 27 | 28 | if ac.probe_rustc_version(1, 52) { 29 | autocfg::emit("eyre_no_fmt_arguments_as_str"); 30 | } 31 | 32 | if ac.probe_rustc_version(1, 58) { 33 | autocfg::emit("eyre_no_fmt_args_capture"); 34 | } 35 | 36 | if ac.probe_rustc_version(1, 65) { 37 | autocfg::emit("backtrace") 38 | } 39 | } 40 | 41 | // This code exercises the surface area or the generic member access feature for the `std::error::Error` trait. 42 | // 43 | // This is use to detect and supply backtrace information through different errors types. 44 | const GENERIC_MEMBER_ACCESS_PROBE: &str = r#" 45 | #![feature(error_generic_member_access)] 46 | #![allow(dead_code)] 47 | 48 | use std::error::{Error, Request}; 49 | use std::fmt::{self, Display}; 50 | 51 | #[derive(Debug)] 52 | struct E { 53 | backtrace: MyBacktrace, 54 | } 55 | 56 | #[derive(Debug)] 57 | struct MyBacktrace; 58 | 59 | impl Display for E { 60 | fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { 61 | unimplemented!() 62 | } 63 | } 64 | 65 | impl Error for E { 66 | fn provide<'a>(&'a self, request: &mut Request<'a>) { 67 | request 68 | .provide_ref::(&self.backtrace); 69 | } 70 | } 71 | "#; 72 | 73 | fn compile_probe(probe: &str) -> Option { 74 | let rustc = env::var_os("RUSTC")?; 75 | let out_dir = env::var_os("OUT_DIR")?; 76 | let probefile = Path::new(&out_dir).join("probe.rs"); 77 | fs::write(&probefile, probe).ok()?; 78 | 79 | let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty()); 80 | let rustc_workspace_wrapper = 81 | env::var_os("RUSTC_WORKSPACE_WRAPPER").filter(|wrapper| !wrapper.is_empty()); 82 | let mut rustc = rustc_wrapper 83 | .into_iter() 84 | .chain(rustc_workspace_wrapper) 85 | .chain(std::iter::once(rustc)); 86 | 87 | let mut cmd = Command::new(rustc.next().unwrap()); 88 | cmd.args(rustc); 89 | 90 | if let Some(target) = env::var_os("TARGET") { 91 | cmd.arg("--target").arg(target); 92 | } 93 | 94 | // If Cargo wants to set RUSTFLAGS, use that. 95 | if let Ok(rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") { 96 | if !rustflags.is_empty() { 97 | for arg in rustflags.split('\x1f') { 98 | cmd.arg(arg); 99 | } 100 | } 101 | } 102 | 103 | cmd.arg("--edition=2018") 104 | .arg("--crate-name=eyre_build") 105 | .arg("--crate-type=lib") 106 | .arg("--emit=metadata") 107 | .arg("--out-dir") 108 | .arg(out_dir) 109 | .arg(probefile) 110 | .status() 111 | .ok() 112 | } 113 | -------------------------------------------------------------------------------- /eyre/examples/custom_handler.rs: -------------------------------------------------------------------------------- 1 | use backtrace::Backtrace; 2 | use eyre::EyreHandler; 3 | use std::error::Error; 4 | use std::{fmt, iter}; 5 | 6 | fn main() -> eyre::Result<()> { 7 | // Install our custom eyre report hook for constructing our custom Handlers 8 | install().unwrap(); 9 | 10 | // construct a report with, hopefully, our custom handler! 11 | let mut report = eyre::eyre!("hello from custom error town!"); 12 | 13 | // manually set the custom msg for this report after it has been constructed 14 | if let Some(handler) = report.handler_mut().downcast_mut::() { 15 | handler.custom_msg = Some("you're the best users, you know that right???"); 16 | } 17 | 18 | // print that shit!! 19 | Err(report) 20 | } 21 | 22 | // define a handler that captures backtraces unless told not to 23 | fn install() -> Result<(), impl Error> { 24 | let capture_backtrace = std::env::var("RUST_BACKWARDS_TRACE") 25 | .map(|val| val != "0") 26 | .unwrap_or(true); 27 | 28 | let hook = Hook { capture_backtrace }; 29 | 30 | eyre::set_hook(Box::new(move |e| Box::new(hook.make_handler(e)))) 31 | } 32 | 33 | struct Hook { 34 | capture_backtrace: bool, 35 | } 36 | 37 | impl Hook { 38 | fn make_handler(&self, _error: &(dyn Error + 'static)) -> Handler { 39 | let backtrace = if self.capture_backtrace { 40 | Some(Backtrace::new()) 41 | } else { 42 | None 43 | }; 44 | 45 | Handler { 46 | backtrace, 47 | custom_msg: None, 48 | } 49 | } 50 | } 51 | 52 | struct Handler { 53 | // custom configured backtrace capture 54 | backtrace: Option, 55 | // customizable message payload associated with reports 56 | custom_msg: Option<&'static str>, 57 | } 58 | 59 | impl EyreHandler for Handler { 60 | fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { 61 | if f.alternate() { 62 | return fmt::Debug::fmt(error, f); 63 | } 64 | 65 | let errors = iter::successors(Some(error), |error| (*error).source()); 66 | 67 | for (ind, error) in errors.enumerate() { 68 | write!(f, "\n{:>4}: {}", ind, error)?; 69 | } 70 | 71 | if let Some(backtrace) = self.backtrace.as_ref() { 72 | writeln!(f, "\n\nBacktrace:\n{:?}", backtrace)?; 73 | } 74 | 75 | if let Some(msg) = self.custom_msg.as_ref() { 76 | writeln!(f, "\n\n{}", msg)?; 77 | } 78 | 79 | Ok(()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /eyre/examples/eyre-usage.rs: -------------------------------------------------------------------------------- 1 | use eyre::{eyre, Report, WrapErr}; 2 | 3 | fn main() -> Result<(), Report> { 4 | let e: Report = eyre!("oh no this program is just bad!"); 5 | 6 | Err(e).wrap_err("usage example successfully experienced a failure") 7 | } 8 | -------------------------------------------------------------------------------- /eyre/src/backtrace.rs: -------------------------------------------------------------------------------- 1 | #[cfg(backtrace)] 2 | pub(crate) use std::backtrace::Backtrace; 3 | 4 | #[cfg(not(backtrace))] 5 | pub(crate) enum Backtrace {} 6 | 7 | #[cfg(backtrace)] 8 | macro_rules! capture_backtrace { 9 | () => { 10 | Some(Backtrace::capture()) 11 | }; 12 | } 13 | 14 | #[cfg(not(backtrace))] 15 | macro_rules! capture_backtrace { 16 | () => { 17 | None 18 | }; 19 | } 20 | /// Capture a backtrace iff there is not already a backtrace in the error chain 21 | #[cfg(generic_member_access)] 22 | macro_rules! backtrace_if_absent { 23 | ($err:expr) => { 24 | match std::error::request_ref::($err as &dyn std::error::Error) { 25 | Some(_) => None, 26 | None => capture_backtrace!(), 27 | } 28 | }; 29 | } 30 | 31 | #[cfg(not(generic_member_access))] 32 | macro_rules! backtrace_if_absent { 33 | ($err:expr) => { 34 | capture_backtrace!() 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /eyre/src/chain.rs: -------------------------------------------------------------------------------- 1 | use self::ChainState::*; 2 | use crate::StdError; 3 | 4 | use std::vec; 5 | 6 | pub(crate) use crate::Chain; 7 | 8 | #[derive(Clone)] 9 | pub(crate) enum ChainState<'a> { 10 | Linked { 11 | next: Option<&'a (dyn StdError + 'static)>, 12 | }, 13 | Buffered { 14 | rest: vec::IntoIter<&'a (dyn StdError + 'static)>, 15 | }, 16 | } 17 | 18 | impl<'a> Chain<'a> { 19 | /// Construct an iterator over a chain of errors via the `source` method 20 | /// 21 | /// # Example 22 | /// 23 | /// ```rust 24 | /// use std::error::Error; 25 | /// use std::fmt::{self, Write}; 26 | /// use eyre::Chain; 27 | /// use indenter::indented; 28 | /// 29 | /// fn report(error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | /// let mut errors = Chain::new(error).enumerate(); 31 | /// for (i, error) in errors { 32 | /// writeln!(f)?; 33 | /// write!(indented(f).ind(i), "{}", error)?; 34 | /// } 35 | /// 36 | /// Ok(()) 37 | /// } 38 | /// ``` 39 | pub fn new(head: &'a (dyn StdError + 'static)) -> Self { 40 | Chain { 41 | state: ChainState::Linked { next: Some(head) }, 42 | } 43 | } 44 | } 45 | 46 | impl<'a> Iterator for Chain<'a> { 47 | type Item = &'a (dyn StdError + 'static); 48 | 49 | fn next(&mut self) -> Option { 50 | match &mut self.state { 51 | Linked { next } => { 52 | let error = (*next)?; 53 | *next = error.source(); 54 | Some(error) 55 | } 56 | Buffered { rest } => rest.next(), 57 | } 58 | } 59 | 60 | fn size_hint(&self) -> (usize, Option) { 61 | let len = self.len(); 62 | (len, Some(len)) 63 | } 64 | } 65 | 66 | impl DoubleEndedIterator for Chain<'_> { 67 | fn next_back(&mut self) -> Option { 68 | match &mut self.state { 69 | Linked { mut next } => { 70 | let mut rest = Vec::new(); 71 | while let Some(cause) = next { 72 | next = cause.source(); 73 | rest.push(cause); 74 | } 75 | let mut rest = rest.into_iter(); 76 | let last = rest.next_back(); 77 | self.state = Buffered { rest }; 78 | last 79 | } 80 | Buffered { rest } => rest.next_back(), 81 | } 82 | } 83 | } 84 | 85 | impl ExactSizeIterator for Chain<'_> { 86 | fn len(&self) -> usize { 87 | match &self.state { 88 | Linked { mut next } => { 89 | let mut len = 0; 90 | while let Some(cause) = next { 91 | next = cause.source(); 92 | len += 1; 93 | } 94 | len 95 | } 96 | Buffered { rest } => rest.len(), 97 | } 98 | } 99 | } 100 | 101 | impl Default for Chain<'_> { 102 | fn default() -> Self { 103 | Chain { 104 | state: ChainState::Buffered { 105 | rest: Vec::new().into_iter(), 106 | }, 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /eyre/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{ContextError, ErrorImpl}; 2 | use crate::{Report, StdError, WrapErr}; 3 | use core::fmt::{self, Debug, Display, Write}; 4 | 5 | mod ext { 6 | use super::*; 7 | 8 | pub trait StdError { 9 | #[cfg_attr(track_caller, track_caller)] 10 | fn ext_report(self, msg: D) -> Report 11 | where 12 | D: Display + Send + Sync + 'static; 13 | } 14 | 15 | impl StdError for E 16 | where 17 | E: std::error::Error + Send + Sync + 'static, 18 | { 19 | fn ext_report(self, msg: D) -> Report 20 | where 21 | D: Display + Send + Sync + 'static, 22 | { 23 | Report::from_msg(msg, self) 24 | } 25 | } 26 | 27 | impl StdError for Report { 28 | fn ext_report(self, msg: D) -> Report 29 | where 30 | D: Display + Send + Sync + 'static, 31 | { 32 | self.wrap_err(msg) 33 | } 34 | } 35 | } 36 | 37 | impl WrapErr for Result 38 | where 39 | E: ext::StdError + Send + Sync + 'static, 40 | { 41 | fn wrap_err(self, msg: D) -> Result 42 | where 43 | D: Display + Send + Sync + 'static, 44 | { 45 | match self { 46 | Ok(t) => Ok(t), 47 | Err(e) => Err(e.ext_report(msg)), 48 | } 49 | } 50 | 51 | fn wrap_err_with(self, msg: F) -> Result 52 | where 53 | D: Display + Send + Sync + 'static, 54 | F: FnOnce() -> D, 55 | { 56 | match self { 57 | Ok(t) => Ok(t), 58 | Err(e) => Err(e.ext_report(msg())), 59 | } 60 | } 61 | } 62 | 63 | #[cfg(feature = "anyhow")] 64 | impl crate::ContextCompat for Result 65 | where 66 | Self: WrapErr, 67 | { 68 | #[track_caller] 69 | fn context(self, msg: D) -> crate::Result 70 | where 71 | D: Display + Send + Sync + 'static, 72 | { 73 | self.wrap_err(msg) 74 | } 75 | 76 | #[track_caller] 77 | fn with_context(self, f: F) -> crate::Result 78 | where 79 | D: Display + Send + Sync + 'static, 80 | F: FnOnce() -> D, 81 | { 82 | self.wrap_err_with(f) 83 | } 84 | } 85 | 86 | #[cfg(feature = "anyhow")] 87 | impl crate::ContextCompat for Option { 88 | #[track_caller] 89 | fn context(self, msg: D) -> Result 90 | where 91 | D: Display + Send + Sync + 'static, 92 | { 93 | match self { 94 | Some(t) => Ok(t), 95 | None => Err(Report::from_display(msg)), 96 | } 97 | } 98 | 99 | #[track_caller] 100 | fn with_context(self, msg: F) -> Result 101 | where 102 | D: Display + Send + Sync + 'static, 103 | F: FnOnce() -> D, 104 | { 105 | match self { 106 | Some(t) => Ok(t), 107 | None => Err(Report::from_display(msg())), 108 | } 109 | } 110 | } 111 | 112 | impl Debug for ContextError 113 | where 114 | D: Display, 115 | E: Debug, 116 | { 117 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 118 | f.debug_struct("Error") 119 | .field("msg", &Quoted(&self.msg)) 120 | .field("source", &self.error) 121 | .finish() 122 | } 123 | } 124 | 125 | impl Display for ContextError 126 | where 127 | D: Display, 128 | { 129 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 130 | Display::fmt(&self.msg, f) 131 | } 132 | } 133 | 134 | impl StdError for ContextError 135 | where 136 | D: Display, 137 | E: StdError + 'static, 138 | { 139 | #[cfg(generic_member_access)] 140 | fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { 141 | self.error.provide(request); 142 | } 143 | 144 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 145 | Some(&self.error) 146 | } 147 | } 148 | 149 | impl StdError for ContextError 150 | where 151 | D: Display, 152 | { 153 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 154 | Some(ErrorImpl::error(self.error.inner.as_ref())) 155 | } 156 | } 157 | 158 | struct Quoted(D); 159 | 160 | impl Debug for Quoted 161 | where 162 | D: Display, 163 | { 164 | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 165 | formatter.write_char('"')?; 166 | Quoted(&mut *formatter).write_fmt(format_args!("{}", self.0))?; 167 | formatter.write_char('"')?; 168 | Ok(()) 169 | } 170 | } 171 | 172 | impl Write for Quoted<&mut fmt::Formatter<'_>> { 173 | fn write_str(&mut self, s: &str) -> fmt::Result { 174 | Display::fmt(&s.escape_debug(), self.0) 175 | } 176 | } 177 | 178 | pub(crate) mod private { 179 | use super::*; 180 | 181 | pub trait Sealed {} 182 | 183 | impl Sealed for Result where E: ext::StdError {} 184 | impl Sealed for Option {} 185 | } 186 | -------------------------------------------------------------------------------- /eyre/src/error/pyo3_compat.rs: -------------------------------------------------------------------------------- 1 | use crate::Report; 2 | 3 | impl From for pyo3::PyErr { 4 | fn from(error: Report) -> Self { 5 | pyo3::exceptions::PyRuntimeError::new_err(format!("{:?}", error)) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /eyre/src/fmt.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::ErrorImpl, ptr::RefPtr}; 2 | use core::fmt; 3 | 4 | impl ErrorImpl<()> { 5 | pub(crate) fn display(this: RefPtr<'_, Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result { 6 | ErrorImpl::header(this) 7 | .handler 8 | .as_ref() 9 | .map(|handler| handler.display(Self::error(this), f)) 10 | .unwrap_or_else(|| core::fmt::Display::fmt(Self::error(this), f)) 11 | } 12 | 13 | /// Debug formats the error using the captured handler 14 | pub(crate) fn debug(this: RefPtr<'_, Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | ErrorImpl::header(this) 16 | .handler 17 | .as_ref() 18 | .map(|handler| handler.debug(Self::error(this), f)) 19 | .unwrap_or_else(|| core::fmt::Debug::fmt(Self::error(this), f)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /eyre/src/kind.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_debug_implementations, missing_docs)] 2 | // Tagged dispatch mechanism for resolving the behavior of `eyre!($expr)`. 3 | // 4 | // When eyre! is given a single expr argument to turn into eyre::Report, we 5 | // want the resulting Report to pick up the input's implementation of source() 6 | // and backtrace() if it has a std::error::Error impl, otherwise require nothing 7 | // more than Display and Debug. 8 | // 9 | // Expressed in terms of specialization, we want something like: 10 | // 11 | // trait EyreNew { 12 | // fn new(self) -> Report; 13 | // } 14 | // 15 | // impl EyreNew for T 16 | // where 17 | // T: Display + Debug + Send + Sync + 'static, 18 | // { 19 | // default fn new(self) -> Report { 20 | // /* no std error impl */ 21 | // } 22 | // } 23 | // 24 | // impl EyreNew for T 25 | // where 26 | // T: std::error::Error + Send + Sync + 'static, 27 | // { 28 | // fn new(self) -> Report { 29 | // /* use std error's source() and backtrace() */ 30 | // } 31 | // } 32 | // 33 | // Since specialization is not stable yet, instead we rely on autoref behavior 34 | // of method resolution to perform tagged dispatch. Here we have two traits 35 | // AdhocKind and TraitKind that both have an eyre_kind() method. AdhocKind is 36 | // implemented whether or not the caller's type has a std error impl, while 37 | // TraitKind is implemented only when a std error impl does exist. The ambiguity 38 | // is resolved by AdhocKind requiring an extra autoref so that it has lower 39 | // precedence. 40 | // 41 | // The eyre! macro will set up the call in this form: 42 | // 43 | // #[allow(unused_imports)] 44 | // use $crate::private::{AdhocKind, TraitKind}; 45 | // let error = $msg; 46 | // (&error).eyre_kind().new(error) 47 | 48 | use crate::Report; 49 | use core::fmt::{Debug, Display}; 50 | 51 | use crate::StdError; 52 | 53 | pub struct Adhoc; 54 | 55 | pub trait AdhocKind: Sized { 56 | #[inline] 57 | fn eyre_kind(&self) -> Adhoc { 58 | Adhoc 59 | } 60 | } 61 | 62 | impl AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'static {} 63 | 64 | impl Adhoc { 65 | #[cfg_attr(track_caller, track_caller)] 66 | pub fn new(self, message: M) -> Report 67 | where 68 | M: Display + Debug + Send + Sync + 'static, 69 | { 70 | Report::from_adhoc(message) 71 | } 72 | } 73 | 74 | pub struct Trait; 75 | 76 | pub trait TraitKind: Sized { 77 | #[inline] 78 | fn eyre_kind(&self) -> Trait { 79 | Trait 80 | } 81 | } 82 | 83 | impl TraitKind for E where E: Into {} 84 | 85 | impl Trait { 86 | #[cfg_attr(track_caller, track_caller)] 87 | pub fn new(self, error: E) -> Report 88 | where 89 | E: Into, 90 | { 91 | error.into() 92 | } 93 | } 94 | 95 | pub struct Boxed; 96 | 97 | pub trait BoxedKind: Sized { 98 | #[inline] 99 | fn eyre_kind(&self) -> Boxed { 100 | Boxed 101 | } 102 | } 103 | 104 | impl BoxedKind for Box {} 105 | 106 | impl Boxed { 107 | #[cfg_attr(track_caller, track_caller)] 108 | pub fn new(self, error: Box) -> Report { 109 | Report::from_boxed(error) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /eyre/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Return early with an error. 2 | /// 3 | /// This macro is equivalent to `return Err(eyre!())`. 4 | /// 5 | /// # Example 6 | /// 7 | /// ``` 8 | /// # use eyre::{bail, Result}; 9 | /// # 10 | /// # fn has_permission(user: usize, resource: usize) -> bool { 11 | /// # true 12 | /// # } 13 | /// # 14 | /// # fn main() -> Result<()> { 15 | /// # let user = 0; 16 | /// # let resource = 0; 17 | /// # 18 | /// if !has_permission(user, resource) { 19 | /// bail!("permission denied for accessing {}", resource); 20 | /// } 21 | /// # Ok(()) 22 | /// # } 23 | /// ``` 24 | /// 25 | /// ``` 26 | /// # use eyre::{bail, Result}; 27 | /// # use thiserror::Error; 28 | /// # 29 | /// # const MAX_DEPTH: usize = 1; 30 | /// # 31 | /// #[derive(Error, Debug)] 32 | /// enum ScienceError { 33 | /// #[error("recursion limit exceeded")] 34 | /// RecursionLimitExceeded, 35 | /// # #[error("...")] 36 | /// # More = (stringify! { 37 | /// ... 38 | /// # }, 1).1, 39 | /// } 40 | /// 41 | /// # fn main() -> Result<()> { 42 | /// # let depth = 0; 43 | /// # let err: &'static dyn std::error::Error = &ScienceError::RecursionLimitExceeded; 44 | /// # 45 | /// if depth > MAX_DEPTH { 46 | /// bail!(ScienceError::RecursionLimitExceeded); 47 | /// } 48 | /// # Ok(()) 49 | /// # } 50 | /// ``` 51 | #[macro_export] 52 | macro_rules! bail { 53 | ($msg:literal $(,)?) => { 54 | return $crate::private::Err($crate::eyre!($msg)); 55 | }; 56 | ($err:expr $(,)?) => { 57 | return $crate::private::Err($crate::eyre!($err)); 58 | }; 59 | ($fmt:expr, $($arg:tt)*) => { 60 | return $crate::private::Err($crate::eyre!($fmt, $($arg)*)); 61 | }; 62 | } 63 | 64 | /// Return early with an error if a condition is not satisfied. 65 | /// 66 | /// This macro is equivalent to `if !$cond { return Err(eyre!()); }`. 67 | /// 68 | /// Analogously to `assert!`, `ensure!` takes a condition and exits the function 69 | /// if the condition fails. Unlike `assert!`, `ensure!` returns an `eyre::Result` 70 | /// rather than panicking. 71 | /// 72 | /// # Example 73 | /// 74 | /// ``` 75 | /// # use eyre::{ensure, Result}; 76 | /// # 77 | /// # fn main() -> Result<()> { 78 | /// # let user = 0; 79 | /// # 80 | /// ensure!(user == 0, "only user 0 is allowed"); 81 | /// # Ok(()) 82 | /// # } 83 | /// ``` 84 | /// 85 | /// ``` 86 | /// # use eyre::{ensure, Result}; 87 | /// # use thiserror::Error; 88 | /// # 89 | /// # const MAX_DEPTH: usize = 1; 90 | /// # 91 | /// #[derive(Error, Debug)] 92 | /// enum ScienceError { 93 | /// #[error("recursion limit exceeded")] 94 | /// RecursionLimitExceeded, 95 | /// # #[error("...")] 96 | /// # More = (stringify! { 97 | /// ... 98 | /// # }, 1).1, 99 | /// } 100 | /// 101 | /// # fn main() -> Result<()> { 102 | /// # let depth = 0; 103 | /// # 104 | /// ensure!(depth <= MAX_DEPTH, ScienceError::RecursionLimitExceeded); 105 | /// # Ok(()) 106 | /// # } 107 | /// ``` 108 | #[macro_export] 109 | macro_rules! ensure { 110 | ($cond:expr $(,)?) => { 111 | $crate::ensure!($cond, concat!("Condition failed: `", stringify!($cond), "`")) 112 | }; 113 | ($cond:expr, $msg:literal $(,)?) => { 114 | if !$cond { 115 | return $crate::private::Err($crate::eyre!($msg)); 116 | } 117 | }; 118 | ($cond:expr, $err:expr $(,)?) => { 119 | if !$cond { 120 | return $crate::private::Err($crate::eyre!($err)); 121 | } 122 | }; 123 | ($cond:expr, $fmt:expr, $($arg:tt)*) => { 124 | if !$cond { 125 | return $crate::private::Err($crate::eyre!($fmt, $($arg)*)); 126 | } 127 | }; 128 | } 129 | 130 | /// Construct an ad-hoc error from a string. 131 | /// 132 | /// This evaluates to a `Report`. It can take either just a string, or a format 133 | /// string with arguments. It also can take any custom type which implements 134 | /// `Debug` and `Display`. 135 | /// 136 | /// # Example 137 | /// 138 | /// ``` 139 | /// # type V = (); 140 | /// # 141 | /// use eyre::{eyre, Result}; 142 | /// 143 | /// fn lookup(key: &str) -> Result { 144 | /// if key.len() != 16 { 145 | /// return Err(eyre!("key length must be 16 characters, got {:?}", key)); 146 | /// } 147 | /// 148 | /// // ... 149 | /// # Ok(()) 150 | /// } 151 | /// ``` 152 | #[macro_export] 153 | macro_rules! eyre { 154 | ($msg:literal $(,)?) => ({ 155 | let error = $crate::private::format_err($crate::private::format_args!($msg)); 156 | error 157 | }); 158 | ($err:expr $(,)?) => ({ 159 | use $crate::private::kind::*; 160 | let error = match $err { 161 | error => (&error).eyre_kind().new(error), 162 | }; 163 | error 164 | }); 165 | ($fmt:expr, $($arg:tt)*) => { 166 | $crate::private::new_adhoc($crate::private::format!($fmt, $($arg)*)) 167 | }; 168 | } 169 | -------------------------------------------------------------------------------- /eyre/src/option.rs: -------------------------------------------------------------------------------- 1 | use crate::OptionExt; 2 | use core::fmt::{Debug, Display}; 3 | 4 | impl OptionExt for Option { 5 | #[track_caller] 6 | fn ok_or_eyre(self, message: M) -> crate::Result 7 | where 8 | M: Debug + Display + Send + Sync + 'static, 9 | { 10 | match self { 11 | Some(ok) => Ok(ok), 12 | None => Err(crate::Report::msg(message)), 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /eyre/src/ptr.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::PhantomData, ptr::NonNull}; 2 | 3 | /// An owned pointer 4 | /// 5 | /// **NOTE**: Does not deallocate when dropped 6 | pub(crate) struct OwnedPtr { 7 | ptr: NonNull, 8 | } 9 | 10 | impl Copy for OwnedPtr {} 11 | 12 | impl Clone for OwnedPtr { 13 | fn clone(&self) -> Self { 14 | *self 15 | } 16 | } 17 | 18 | unsafe impl Send for OwnedPtr where T: Send {} 19 | unsafe impl Sync for OwnedPtr where T: Send {} 20 | 21 | impl OwnedPtr { 22 | pub(crate) fn new(value: T) -> Self { 23 | Self::from_boxed(Box::new(value)) 24 | } 25 | 26 | pub(crate) fn from_boxed(boxed: Box) -> Self { 27 | // Safety: `Box::into_raw` is guaranteed to be non-null 28 | Self { 29 | ptr: unsafe { NonNull::new_unchecked(Box::into_raw(boxed)) }, 30 | } 31 | } 32 | 33 | /// Convert the pointer to another type 34 | pub(crate) fn cast(self) -> OwnedPtr { 35 | OwnedPtr { 36 | ptr: self.ptr.cast(), 37 | } 38 | } 39 | 40 | /// Context the pointer into a Box 41 | /// 42 | /// # Safety 43 | /// 44 | /// Dropping the Box will deallocate a layout of `T` and run the destructor of `T`. 45 | /// 46 | /// A cast pointer must therefore be cast back to the original type before calling this method. 47 | pub(crate) unsafe fn into_box(self) -> Box { 48 | unsafe { Box::from_raw(self.ptr.as_ptr()) } 49 | } 50 | 51 | pub(crate) const fn as_ref(&self) -> RefPtr<'_, T> { 52 | RefPtr { 53 | ptr: self.ptr, 54 | _marker: PhantomData, 55 | } 56 | } 57 | 58 | pub(crate) fn as_mut(&mut self) -> MutPtr<'_, T> { 59 | MutPtr { 60 | ptr: self.ptr, 61 | _marker: PhantomData, 62 | } 63 | } 64 | } 65 | 66 | /// Convenience lifetime annotated mutable pointer which facilitates returning an inferred lifetime 67 | /// in a `fn` pointer. 68 | pub(crate) struct RefPtr<'a, T: ?Sized> { 69 | pub(crate) ptr: NonNull, 70 | _marker: PhantomData<&'a T>, 71 | } 72 | 73 | /// Safety: RefPtr indicates a shared reference to a value and as such exhibits the same Send + 74 | /// Sync behavior of &'a T 75 | unsafe impl<'a, T: ?Sized> Send for RefPtr<'a, T> where &'a T: Send {} 76 | unsafe impl<'a, T: ?Sized> Sync for RefPtr<'a, T> where &'a T: Sync {} 77 | 78 | impl Copy for RefPtr<'_, T> {} 79 | impl Clone for RefPtr<'_, T> { 80 | fn clone(&self) -> Self { 81 | *self 82 | } 83 | } 84 | 85 | impl<'a, T: ?Sized> RefPtr<'a, T> { 86 | pub(crate) fn new(ptr: &'a T) -> Self { 87 | Self { 88 | ptr: NonNull::from(ptr), 89 | _marker: PhantomData, 90 | } 91 | } 92 | 93 | /// Convert the pointer to another type 94 | pub(crate) fn cast(self) -> RefPtr<'a, U> { 95 | RefPtr { 96 | ptr: self.ptr.cast(), 97 | _marker: PhantomData, 98 | } 99 | } 100 | 101 | /// Returns a shared reference to the owned value 102 | /// 103 | /// # Safety 104 | /// 105 | /// See: [`NonNull::as_ref`] 106 | #[inline] 107 | pub(crate) unsafe fn as_ref(&self) -> &'a T { 108 | unsafe { self.ptr.as_ref() } 109 | } 110 | } 111 | 112 | /// Convenience lifetime annotated mutable pointer which facilitates returning an inferred lifetime 113 | /// in a `fn` pointer. 114 | pub(crate) struct MutPtr<'a, T: ?Sized> { 115 | pub(crate) ptr: NonNull, 116 | _marker: PhantomData<&'a mut T>, 117 | } 118 | 119 | /// Safety: RefPtr indicates an exclusive reference to a value and as such exhibits the same Send + 120 | /// Sync behavior of &'a mut T 121 | unsafe impl<'a, T: ?Sized> Send for MutPtr<'a, T> where &'a mut T: Send {} 122 | unsafe impl<'a, T: ?Sized> Sync for MutPtr<'a, T> where &'a mut T: Sync {} 123 | 124 | impl Copy for MutPtr<'_, T> {} 125 | impl Clone for MutPtr<'_, T> { 126 | fn clone(&self) -> Self { 127 | *self 128 | } 129 | } 130 | 131 | impl<'a, T: ?Sized> MutPtr<'a, T> { 132 | /// Convert the pointer to another type 133 | pub(crate) fn cast(self) -> MutPtr<'a, U> { 134 | MutPtr { 135 | ptr: self.ptr.cast(), 136 | _marker: PhantomData, 137 | } 138 | } 139 | 140 | /// Returns a mutable reference to the owned value with the lifetime decoupled from self 141 | /// 142 | /// # Safety 143 | /// 144 | /// See: [`NonNull::as_mut`] 145 | #[inline] 146 | pub(crate) unsafe fn into_mut(mut self) -> &'a mut T { 147 | unsafe { self.ptr.as_mut() } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /eyre/src/wrapper.rs: -------------------------------------------------------------------------------- 1 | use crate::StdError; 2 | use core::fmt::{self, Debug, Display}; 3 | 4 | #[repr(transparent)] 5 | pub(crate) struct DisplayError(pub(crate) M); 6 | 7 | #[repr(transparent)] 8 | /// Wraps a Debug + Display type as an error. 9 | /// 10 | /// Its Debug and Display impls are the same as the wrapped type. 11 | pub(crate) struct MessageError(pub(crate) M); 12 | 13 | pub(crate) struct NoneError; 14 | 15 | impl Debug for DisplayError 16 | where 17 | M: Display, 18 | { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | Display::fmt(&self.0, f) 21 | } 22 | } 23 | 24 | impl Display for DisplayError 25 | where 26 | M: Display, 27 | { 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 | Display::fmt(&self.0, f) 30 | } 31 | } 32 | 33 | impl StdError for DisplayError where M: Display + 'static {} 34 | 35 | impl Debug for MessageError 36 | where 37 | M: Display + Debug, 38 | { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | Debug::fmt(&self.0, f) 41 | } 42 | } 43 | 44 | impl Display for MessageError 45 | where 46 | M: Display + Debug, 47 | { 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | Display::fmt(&self.0, f) 50 | } 51 | } 52 | 53 | impl StdError for MessageError where M: Display + Debug + 'static {} 54 | 55 | impl Debug for NoneError { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | Debug::fmt("Option was None", f) 58 | } 59 | } 60 | 61 | impl Display for NoneError { 62 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 63 | Display::fmt("Option was None", f) 64 | } 65 | } 66 | 67 | impl StdError for NoneError {} 68 | 69 | #[repr(transparent)] 70 | pub(crate) struct BoxedError(pub(crate) Box); 71 | 72 | impl Debug for BoxedError { 73 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 74 | Debug::fmt(&self.0, f) 75 | } 76 | } 77 | 78 | impl Display for BoxedError { 79 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 80 | Display::fmt(&self.0, f) 81 | } 82 | } 83 | 84 | impl StdError for BoxedError { 85 | #[cfg(generic_member_access)] 86 | fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { 87 | self.0.provide(request); 88 | } 89 | 90 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 91 | self.0.source() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /eyre/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use eyre::{bail, set_hook, DefaultHandler, InstallError, Result}; 4 | use once_cell::sync::OnceCell; 5 | use std::io; 6 | 7 | pub fn bail_literal() -> Result<()> { 8 | bail!("oh no!"); 9 | } 10 | 11 | pub fn bail_fmt() -> Result<()> { 12 | bail!("{} {}!", "oh", "no"); 13 | } 14 | 15 | pub fn bail_error() -> Result<()> { 16 | bail!(io::Error::new(io::ErrorKind::Other, "oh no!")); 17 | } 18 | 19 | // Tests are multithreaded- use OnceCell to install hook once if auto-install 20 | // feature is disabled. 21 | pub fn maybe_install_handler() -> Result<(), InstallError> { 22 | static INSTALLER: OnceCell> = OnceCell::new(); 23 | 24 | if cfg!(not(feature = "auto-install")) { 25 | *INSTALLER.get_or_init(|| set_hook(Box::new(DefaultHandler::default_with))) 26 | } else { 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /eyre/tests/compiletest.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::attr(not(nightly), ignore)] 2 | #[cfg_attr(miri, ignore)] 3 | #[test] 4 | fn ui() { 5 | let t = trybuild::TestCases::new(); 6 | t.compile_fail("tests/ui/*.rs"); 7 | } 8 | -------------------------------------------------------------------------------- /eyre/tests/drop/mod.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt::{self, Display}; 3 | use std::sync::atomic::AtomicBool; 4 | use std::sync::atomic::Ordering::SeqCst; 5 | use std::sync::Arc; 6 | 7 | #[derive(Debug)] 8 | pub struct Flag { 9 | atomic: Arc, 10 | } 11 | 12 | impl Flag { 13 | pub fn new() -> Self { 14 | Flag { 15 | atomic: Arc::new(AtomicBool::new(false)), 16 | } 17 | } 18 | 19 | pub fn get(&self) -> bool { 20 | self.atomic.load(SeqCst) 21 | } 22 | } 23 | 24 | #[derive(Debug)] 25 | pub struct DetectDrop { 26 | has_dropped: Flag, 27 | label: &'static str, 28 | } 29 | 30 | impl DetectDrop { 31 | pub fn new(label: &'static str, has_dropped: &Flag) -> Self { 32 | DetectDrop { 33 | label, 34 | has_dropped: Flag { 35 | atomic: Arc::clone(&has_dropped.atomic), 36 | }, 37 | } 38 | } 39 | } 40 | 41 | impl StdError for DetectDrop {} 42 | 43 | impl Display for DetectDrop { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | write!(f, "oh no!") 46 | } 47 | } 48 | 49 | impl Drop for DetectDrop { 50 | fn drop(&mut self) { 51 | eprintln!("Dropping {}", self.label); 52 | let already_dropped = self.has_dropped.atomic.swap(true, SeqCst); 53 | assert!(!already_dropped); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /eyre/tests/generic_member_access.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(generic_member_access, feature(error_generic_member_access))] 2 | 3 | mod common; 4 | 5 | #[cfg(all(generic_member_access, not(miri)))] 6 | #[test] 7 | /// Tests that generic member access works through an `eyre::Report` 8 | fn generic_member_access() { 9 | use crate::common::maybe_install_handler; 10 | 11 | use eyre::WrapErr; 12 | use std::backtrace::Backtrace; 13 | use std::fmt::Display; 14 | 15 | fn fail() -> Result<(), MyError> { 16 | Err(MyError { 17 | cupcake: MyCupcake("Blueberry".into()), 18 | backtrace: std::backtrace::Backtrace::capture(), 19 | }) 20 | } 21 | 22 | maybe_install_handler().unwrap(); 23 | 24 | std::env::set_var("RUST_BACKTRACE", "1"); 25 | 26 | #[derive(Debug, PartialEq)] 27 | struct MyCupcake(String); 28 | 29 | #[derive(Debug)] 30 | struct MyError { 31 | cupcake: MyCupcake, 32 | backtrace: std::backtrace::Backtrace, 33 | } 34 | 35 | impl Display for MyError { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | write!(f, "Error: {}", self.cupcake.0) 38 | } 39 | } 40 | 41 | impl std::error::Error for MyError { 42 | fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { 43 | request 44 | .provide_ref(&self.cupcake) 45 | .provide_ref(&self.backtrace); 46 | } 47 | } 48 | 49 | let err = fail() 50 | .wrap_err("Failed to bake my favorite cupcake") 51 | .unwrap_err(); 52 | 53 | let err: Box = err.into(); 54 | 55 | assert!( 56 | format!("{:?}", err).contains("generic_member_access::generic_member_access::fail"), 57 | "should contain the source error backtrace" 58 | ); 59 | 60 | assert_eq!( 61 | std::error::request_ref::(&*err), 62 | Some(&MyCupcake("Blueberry".into())) 63 | ); 64 | 65 | let bt = std::error::request_ref::(&*err).unwrap(); 66 | 67 | assert!( 68 | bt.to_string() 69 | .contains("generic_member_access::generic_member_access::fail"), 70 | "should contain the fail method as it was captured by the original error\n\n{}", 71 | bt 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /eyre/tests/test_autotrait.rs: -------------------------------------------------------------------------------- 1 | use eyre::Report; 2 | 3 | #[test] 4 | fn test_send() { 5 | fn assert_send() {} 6 | assert_send::(); 7 | } 8 | 9 | #[test] 10 | fn test_sync() { 11 | fn assert_sync() {} 12 | assert_sync::(); 13 | } 14 | -------------------------------------------------------------------------------- /eyre/tests/test_boxed.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use self::common::maybe_install_handler; 4 | use eyre::{eyre, Report}; 5 | use std::error::Error as StdError; 6 | use std::io; 7 | use thiserror::Error; 8 | 9 | #[derive(Error, Debug)] 10 | #[error("outer")] 11 | struct MyError { 12 | source: io::Error, 13 | } 14 | 15 | #[test] 16 | fn test_boxed_str() { 17 | maybe_install_handler().unwrap(); 18 | 19 | let error = Box::::from("oh no!"); 20 | let error: Report = eyre!(error); 21 | assert_eq!("oh no!", error.to_string()); 22 | assert_eq!( 23 | "oh no!", 24 | error 25 | .downcast_ref::>() 26 | .unwrap() 27 | .to_string() 28 | ); 29 | } 30 | 31 | #[test] 32 | fn test_boxed_thiserror() { 33 | maybe_install_handler().unwrap(); 34 | 35 | let error = MyError { 36 | source: io::Error::new(io::ErrorKind::Other, "oh no!"), 37 | }; 38 | let error: Report = eyre!(error); 39 | assert_eq!("oh no!", error.source().unwrap().to_string()); 40 | } 41 | 42 | #[test] 43 | fn test_boxed_eyre() { 44 | maybe_install_handler().unwrap(); 45 | 46 | let error: Report = eyre!("oh no!").wrap_err("it failed"); 47 | let error = eyre!(error); 48 | assert_eq!("oh no!", error.source().unwrap().to_string()); 49 | } 50 | 51 | #[test] 52 | fn test_boxed_sources() { 53 | maybe_install_handler().unwrap(); 54 | 55 | let error = MyError { 56 | source: io::Error::new(io::ErrorKind::Other, "oh no!"), 57 | }; 58 | let error = Box::::from(error); 59 | let error: Report = eyre!(error).wrap_err("it failed"); 60 | assert_eq!("it failed", error.to_string()); 61 | assert_eq!("outer", error.source().unwrap().to_string()); 62 | assert_eq!( 63 | "oh no!", 64 | error.source().unwrap().source().unwrap().to_string() 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /eyre/tests/test_chain.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use self::common::maybe_install_handler; 4 | use eyre::{eyre, Report}; 5 | 6 | fn error() -> Report { 7 | eyre!({ 0 }).wrap_err(1).wrap_err(2).wrap_err(3) 8 | } 9 | 10 | #[test] 11 | fn test_iter() { 12 | maybe_install_handler().unwrap(); 13 | 14 | let e = error(); 15 | let mut chain = e.chain(); 16 | assert_eq!("3", chain.next().unwrap().to_string()); 17 | assert_eq!("2", chain.next().unwrap().to_string()); 18 | assert_eq!("1", chain.next().unwrap().to_string()); 19 | assert_eq!("0", chain.next().unwrap().to_string()); 20 | assert!(chain.next().is_none()); 21 | assert!(chain.next_back().is_none()); 22 | } 23 | 24 | #[test] 25 | fn test_rev() { 26 | maybe_install_handler().unwrap(); 27 | 28 | let e = error(); 29 | let mut chain = e.chain().rev(); 30 | assert_eq!("0", chain.next().unwrap().to_string()); 31 | assert_eq!("1", chain.next().unwrap().to_string()); 32 | assert_eq!("2", chain.next().unwrap().to_string()); 33 | assert_eq!("3", chain.next().unwrap().to_string()); 34 | assert!(chain.next().is_none()); 35 | assert!(chain.next_back().is_none()); 36 | } 37 | 38 | #[test] 39 | fn test_len() { 40 | maybe_install_handler().unwrap(); 41 | 42 | let e = error(); 43 | let mut chain = e.chain(); 44 | assert_eq!(4, chain.len()); 45 | assert_eq!("3", chain.next().unwrap().to_string()); 46 | assert_eq!(3, chain.len()); 47 | assert_eq!("0", chain.next_back().unwrap().to_string()); 48 | assert_eq!(2, chain.len()); 49 | assert_eq!("2", chain.next().unwrap().to_string()); 50 | assert_eq!(1, chain.len()); 51 | assert_eq!("1", chain.next_back().unwrap().to_string()); 52 | assert_eq!(0, chain.len()); 53 | assert!(chain.next().is_none()); 54 | } 55 | -------------------------------------------------------------------------------- /eyre/tests/test_context.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod drop; 3 | 4 | use crate::common::maybe_install_handler; 5 | use crate::drop::{DetectDrop, Flag}; 6 | use eyre::{Report, Result, WrapErr}; 7 | use std::fmt::{self, Display}; 8 | use thiserror::Error; 9 | 10 | // https://github.com/dtolnay/eyre/issues/18 11 | #[test] 12 | fn test_inference() -> Result<()> { 13 | let x = "1"; 14 | let y: u32 = x.parse().wrap_err("...")?; 15 | assert_eq!(y, 1); 16 | Ok(()) 17 | } 18 | 19 | macro_rules! context_type { 20 | ($name:ident) => { 21 | #[derive(Debug)] 22 | #[repr(C)] 23 | struct $name { 24 | _drop: DetectDrop, 25 | message: &'static str, 26 | } 27 | 28 | impl Display for $name { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | f.write_str(self.message) 31 | } 32 | } 33 | }; 34 | } 35 | 36 | context_type!(HighLevel); 37 | context_type!(MidLevel); 38 | 39 | #[derive(Error, Debug)] 40 | #[error("{message}")] 41 | #[repr(C)] 42 | struct LowLevel { 43 | message: &'static str, 44 | drop: DetectDrop, 45 | } 46 | 47 | struct Dropped { 48 | low: Flag, 49 | mid: Flag, 50 | high: Flag, 51 | } 52 | 53 | impl Dropped { 54 | fn none(&self) -> bool { 55 | !self.low.get() && !self.mid.get() && !self.high.get() 56 | } 57 | 58 | fn all(&self) -> bool { 59 | self.low.get() && self.mid.get() && self.high.get() 60 | } 61 | } 62 | 63 | fn make_chain() -> (Report, Dropped) { 64 | let dropped = Dropped { 65 | low: Flag::new(), 66 | mid: Flag::new(), 67 | high: Flag::new(), 68 | }; 69 | 70 | let low = LowLevel { 71 | message: "no such file or directory", 72 | drop: DetectDrop::new("LowLevel", &dropped.low), 73 | }; 74 | 75 | // impl Report for Result 76 | let mid = Err::<(), LowLevel>(low) 77 | .wrap_err(MidLevel { 78 | message: "failed to load config", 79 | _drop: DetectDrop::new("MidLevel", &dropped.mid), 80 | }) 81 | .unwrap_err(); 82 | 83 | // impl Report for Result 84 | let high = Err::<(), Report>(mid) 85 | .wrap_err(HighLevel { 86 | message: "failed to start server", 87 | _drop: DetectDrop::new("HighLevel", &dropped.high), 88 | }) 89 | .unwrap_err(); 90 | 91 | (high, dropped) 92 | } 93 | 94 | #[test] 95 | fn test_downcast_ref() { 96 | maybe_install_handler().unwrap(); 97 | 98 | let (err, dropped) = make_chain(); 99 | 100 | assert!(!err.is::()); 101 | assert!(err.downcast_ref::().is_none()); 102 | 103 | assert!(err.is::()); 104 | let high = err.downcast_ref::().unwrap(); 105 | assert_eq!(high.to_string(), "failed to start server"); 106 | 107 | assert!(err.is::()); 108 | let mid = err.downcast_ref::().unwrap(); 109 | assert_eq!(mid.to_string(), "failed to load config"); 110 | 111 | assert!(err.is::()); 112 | let low = err.downcast_ref::().unwrap(); 113 | assert_eq!(low.to_string(), "no such file or directory"); 114 | 115 | assert!(dropped.none()); 116 | drop(err); 117 | assert!(dropped.all()); 118 | } 119 | 120 | #[test] 121 | fn test_downcast_high() { 122 | maybe_install_handler().unwrap(); 123 | 124 | let (err, dropped) = make_chain(); 125 | 126 | let err = err.downcast::().unwrap(); 127 | assert!(!dropped.high.get()); 128 | assert!(dropped.low.get() && dropped.mid.get()); 129 | 130 | drop(err); 131 | assert!(dropped.all()); 132 | } 133 | 134 | #[test] 135 | fn test_downcast_mid() { 136 | maybe_install_handler().unwrap(); 137 | 138 | let (err, dropped) = make_chain(); 139 | 140 | let err = err.downcast::().unwrap(); 141 | assert!(!dropped.mid.get()); 142 | assert!(dropped.low.get() && dropped.high.get()); 143 | 144 | drop(err); 145 | assert!(dropped.all()); 146 | } 147 | 148 | #[test] 149 | fn test_downcast_low() { 150 | maybe_install_handler().unwrap(); 151 | 152 | let (err, dropped) = make_chain(); 153 | 154 | let err = err.downcast::().unwrap(); 155 | assert!(!dropped.low.get()); 156 | assert!(dropped.mid.get() && dropped.high.get()); 157 | 158 | drop(err); 159 | assert!(dropped.all()); 160 | } 161 | 162 | #[test] 163 | fn test_unsuccessful_downcast() { 164 | maybe_install_handler().unwrap(); 165 | 166 | let (err, dropped) = make_chain(); 167 | 168 | let err = err.downcast::().unwrap_err(); 169 | assert!(dropped.none()); 170 | 171 | drop(err); 172 | assert!(dropped.all()); 173 | } 174 | -------------------------------------------------------------------------------- /eyre/tests/test_context_access.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "anyhow")] 2 | 3 | mod common; 4 | 5 | use crate::common::maybe_install_handler; 6 | 7 | #[test] 8 | fn test_context() { 9 | use eyre::{eyre, Report}; 10 | 11 | maybe_install_handler().unwrap(); 12 | 13 | let error: Report = eyre!("oh no!"); 14 | let _ = error.context(); 15 | } 16 | -------------------------------------------------------------------------------- /eyre/tests/test_convert.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod drop; 3 | 4 | use self::common::maybe_install_handler; 5 | use self::drop::{DetectDrop, Flag}; 6 | use eyre::{Report, Result}; 7 | use std::error::Error as StdError; 8 | 9 | #[test] 10 | fn test_convert() { 11 | maybe_install_handler().unwrap(); 12 | 13 | let has_dropped = Flag::new(); 14 | let error: Report = Report::new(DetectDrop::new("TestConvert", &has_dropped)); 15 | let box_dyn = Box::::from(error); 16 | assert_eq!("oh no!", box_dyn.to_string()); 17 | drop(box_dyn); 18 | assert!(has_dropped.get()); 19 | } 20 | 21 | #[test] 22 | fn test_question_mark() -> Result<(), Box> { 23 | fn f() -> Result<()> { 24 | Ok(()) 25 | } 26 | f()?; 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /eyre/tests/test_downcast.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod drop; 3 | 4 | use self::common::*; 5 | use self::drop::{DetectDrop, Flag}; 6 | use eyre::Report; 7 | use std::error::Error as StdError; 8 | use std::fmt::{self, Display}; 9 | use std::io; 10 | 11 | #[test] 12 | fn test_downcast() { 13 | maybe_install_handler().unwrap(); 14 | 15 | #[cfg(not(eyre_no_fmt_arguments_as_str))] 16 | assert_eq!( 17 | "oh no!", 18 | bail_literal().unwrap_err().downcast::<&str>().unwrap(), 19 | ); 20 | 21 | #[cfg(eyre_no_fmt_arguments_as_str)] 22 | assert_eq!( 23 | "oh no!", 24 | bail_literal().unwrap_err().downcast::().unwrap(), 25 | ); 26 | 27 | assert_eq!( 28 | "oh no!", 29 | bail_fmt().unwrap_err().downcast::().unwrap(), 30 | ); 31 | assert_eq!( 32 | "oh no!", 33 | bail_error() 34 | .unwrap_err() 35 | .downcast::() 36 | .unwrap() 37 | .to_string(), 38 | ); 39 | } 40 | 41 | #[test] 42 | fn test_downcast_ref() { 43 | maybe_install_handler().unwrap(); 44 | 45 | #[cfg(not(eyre_no_fmt_arguments_as_str))] 46 | assert_eq!( 47 | "oh no!", 48 | *bail_literal().unwrap_err().downcast_ref::<&str>().unwrap(), 49 | ); 50 | 51 | #[cfg(eyre_no_fmt_arguments_as_str)] 52 | assert_eq!( 53 | "oh no!", 54 | *bail_literal() 55 | .unwrap_err() 56 | .downcast_ref::() 57 | .unwrap(), 58 | ); 59 | 60 | assert_eq!( 61 | "oh no!", 62 | bail_fmt().unwrap_err().downcast_ref::().unwrap(), 63 | ); 64 | assert_eq!( 65 | "oh no!", 66 | bail_error() 67 | .unwrap_err() 68 | .downcast_ref::() 69 | .unwrap() 70 | .to_string(), 71 | ); 72 | } 73 | 74 | #[test] 75 | fn test_downcast_mut() { 76 | maybe_install_handler().unwrap(); 77 | 78 | #[cfg(not(eyre_no_fmt_arguments_as_str))] 79 | assert_eq!( 80 | "oh no!", 81 | *bail_literal().unwrap_err().downcast_mut::<&str>().unwrap(), 82 | ); 83 | 84 | #[cfg(eyre_no_fmt_arguments_as_str)] 85 | assert_eq!( 86 | "oh no!", 87 | *bail_literal() 88 | .unwrap_err() 89 | .downcast_mut::() 90 | .unwrap(), 91 | ); 92 | 93 | assert_eq!( 94 | "oh no!", 95 | bail_fmt().unwrap_err().downcast_mut::().unwrap(), 96 | ); 97 | assert_eq!( 98 | "oh no!", 99 | bail_error() 100 | .unwrap_err() 101 | .downcast_mut::() 102 | .unwrap() 103 | .to_string(), 104 | ); 105 | } 106 | 107 | #[test] 108 | fn test_drop() { 109 | maybe_install_handler().unwrap(); 110 | 111 | let has_dropped = Flag::new(); 112 | let error: Report = Report::new(DetectDrop::new("DetectDrop", &has_dropped)); 113 | drop(error.downcast::().unwrap()); 114 | assert!(has_dropped.get()); 115 | } 116 | 117 | #[test] 118 | fn test_large_alignment() { 119 | maybe_install_handler().unwrap(); 120 | 121 | #[repr(align(64))] 122 | #[derive(Debug)] 123 | struct LargeAlignedError(&'static str); 124 | 125 | impl Display for LargeAlignedError { 126 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 127 | f.write_str(self.0) 128 | } 129 | } 130 | 131 | impl StdError for LargeAlignedError {} 132 | 133 | let error = Report::new(LargeAlignedError("oh no!")); 134 | assert_eq!( 135 | "oh no!", 136 | error.downcast_ref::().unwrap().0 137 | ); 138 | } 139 | 140 | #[test] 141 | fn test_unsuccessful_downcast() { 142 | maybe_install_handler().unwrap(); 143 | 144 | let mut error = bail_error().unwrap_err(); 145 | assert!(error.downcast_ref::<&str>().is_none()); 146 | assert!(error.downcast_mut::<&str>().is_none()); 147 | assert!(error.downcast::<&str>().is_err()); 148 | } 149 | -------------------------------------------------------------------------------- /eyre/tests/test_fmt.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use self::common::maybe_install_handler; 4 | use eyre::{bail, Result, WrapErr}; 5 | use std::io; 6 | 7 | fn f() -> Result<()> { 8 | bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!")); 9 | } 10 | 11 | fn g() -> Result<()> { 12 | f().wrap_err("f failed") 13 | } 14 | 15 | fn h() -> Result<()> { 16 | g().wrap_err("g failed") 17 | } 18 | 19 | const EXPECTED_ALTDISPLAY_F: &str = "oh no!"; 20 | 21 | const EXPECTED_ALTDISPLAY_G: &str = "f failed: oh no!"; 22 | 23 | const EXPECTED_ALTDISPLAY_H: &str = "g failed: f failed: oh no!"; 24 | 25 | const EXPECTED_DEBUG_F: &str = "oh no!"; 26 | 27 | const EXPECTED_DEBUG_G: &str = "\ 28 | f failed 29 | 30 | Caused by: 31 | oh no!\ 32 | "; 33 | 34 | const EXPECTED_DEBUG_H: &str = "\ 35 | g failed 36 | 37 | Caused by: 38 | 0: f failed 39 | 1: oh no!\ 40 | "; 41 | 42 | const EXPECTED_ALTDEBUG_F: &str = "\ 43 | Custom { 44 | kind: PermissionDenied, 45 | error: \"oh no!\", 46 | }\ 47 | "; 48 | 49 | const EXPECTED_ALTDEBUG_G: &str = "\ 50 | Error { 51 | msg: \"f failed\", 52 | source: Custom { 53 | kind: PermissionDenied, 54 | error: \"oh no!\", 55 | }, 56 | }\ 57 | "; 58 | 59 | const EXPECTED_ALTDEBUG_H: &str = "\ 60 | Error { 61 | msg: \"g failed\", 62 | source: Error { 63 | msg: \"f failed\", 64 | source: Custom { 65 | kind: PermissionDenied, 66 | error: \"oh no!\", 67 | }, 68 | }, 69 | }\ 70 | "; 71 | 72 | #[test] 73 | fn test_display() { 74 | maybe_install_handler().unwrap(); 75 | 76 | assert_eq!("g failed", h().unwrap_err().to_string()); 77 | } 78 | 79 | #[test] 80 | fn test_altdisplay() { 81 | maybe_install_handler().unwrap(); 82 | 83 | assert_eq!(EXPECTED_ALTDISPLAY_F, format!("{:#}", f().unwrap_err())); 84 | assert_eq!(EXPECTED_ALTDISPLAY_G, format!("{:#}", g().unwrap_err())); 85 | assert_eq!(EXPECTED_ALTDISPLAY_H, format!("{:#}", h().unwrap_err())); 86 | } 87 | 88 | #[test] 89 | #[cfg_attr(any(backtrace, track_caller), ignore)] 90 | fn test_debug() { 91 | maybe_install_handler().unwrap(); 92 | 93 | assert_eq!(EXPECTED_DEBUG_F, format!("{:?}", f().unwrap_err())); 94 | assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err())); 95 | assert_eq!(EXPECTED_DEBUG_H, format!("{:?}", h().unwrap_err())); 96 | } 97 | 98 | #[test] 99 | fn test_altdebug() { 100 | maybe_install_handler().unwrap(); 101 | 102 | assert_eq!(EXPECTED_ALTDEBUG_F, format!("{:#?}", f().unwrap_err())); 103 | assert_eq!(EXPECTED_ALTDEBUG_G, format!("{:#?}", g().unwrap_err())); 104 | assert_eq!(EXPECTED_ALTDEBUG_H, format!("{:#?}", h().unwrap_err())); 105 | } 106 | -------------------------------------------------------------------------------- /eyre/tests/test_location.rs: -------------------------------------------------------------------------------- 1 | use std::panic::Location; 2 | 3 | struct LocationHandler { 4 | actual: Option<&'static str>, 5 | expected: &'static str, 6 | } 7 | 8 | impl LocationHandler { 9 | fn new(expected: &'static str) -> Self { 10 | LocationHandler { 11 | actual: None, 12 | expected, 13 | } 14 | } 15 | } 16 | 17 | impl eyre::EyreHandler for LocationHandler { 18 | fn debug( 19 | &self, 20 | _error: &(dyn std::error::Error + 'static), 21 | _f: &mut std::fmt::Formatter<'_>, 22 | ) -> std::fmt::Result { 23 | // we assume that if the compiler is new enough to support 24 | // `track_caller` that we will always have `actual` be `Some`, so we can 25 | // safely skip the assertion if the location is `None` which should only 26 | // happen in older rust versions. 27 | if let Some(actual) = self.actual { 28 | assert_eq!(self.expected, actual); 29 | } 30 | 31 | Ok(()) 32 | } 33 | 34 | fn track_caller(&mut self, location: &'static Location<'static>) { 35 | dbg!(location); 36 | self.actual = Some(location.file()); 37 | } 38 | } 39 | 40 | #[test] 41 | fn test_wrap_err() { 42 | let _ = eyre::set_hook(Box::new(|_e| { 43 | let expected_location = file!(); 44 | Box::new(LocationHandler::new(expected_location)) 45 | })); 46 | 47 | use eyre::WrapErr; 48 | let err = read_path("totally_fake_path") 49 | .wrap_err("oopsie") 50 | .unwrap_err(); 51 | 52 | // should panic if the location isn't in our crate 53 | println!("{:?}", err); 54 | } 55 | 56 | #[cfg(not(miri))] 57 | fn read_path(path: &str) -> Result { 58 | std::fs::read_to_string(path) 59 | } 60 | 61 | #[cfg(miri)] 62 | fn read_path(_path: &str) -> Result { 63 | // Miri doesn't support reading files, so we just return an error 64 | Err(std::io::Error::new( 65 | std::io::ErrorKind::Other, 66 | "Miri doesn't support reading files", 67 | )) 68 | } 69 | 70 | #[test] 71 | fn test_wrap_err_with() { 72 | let _ = eyre::set_hook(Box::new(|_e| { 73 | let expected_location = file!(); 74 | Box::new(LocationHandler::new(expected_location)) 75 | })); 76 | 77 | use eyre::WrapErr; 78 | let err = read_path("totally_fake_path") 79 | .wrap_err_with(|| "oopsie") 80 | .unwrap_err(); 81 | 82 | // should panic if the location isn't in our crate 83 | println!("{:?}", err); 84 | } 85 | 86 | #[test] 87 | fn test_option_ok_or_eyre() { 88 | let _ = eyre::set_hook(Box::new(|_e| { 89 | let expected_location = file!(); 90 | Box::new(LocationHandler::new(expected_location)) 91 | })); 92 | 93 | use eyre::OptionExt; 94 | let err = None::<()>.ok_or_eyre("oopsie").unwrap_err(); 95 | 96 | // should panic if the location isn't in our crate 97 | println!("{:?}", err); 98 | } 99 | 100 | #[cfg(feature = "anyhow")] 101 | #[test] 102 | fn test_context() { 103 | let _ = eyre::set_hook(Box::new(|_e| { 104 | let expected_location = file!(); 105 | Box::new(LocationHandler::new(expected_location)) 106 | })); 107 | 108 | use eyre::ContextCompat; 109 | let err = read_path("totally_fake_path") 110 | .context("oopsie") 111 | .unwrap_err(); 112 | 113 | // should panic if the location isn't in our crate 114 | println!("{:?}", err); 115 | } 116 | 117 | #[cfg(feature = "anyhow")] 118 | #[test] 119 | fn test_with_context() { 120 | let _ = eyre::set_hook(Box::new(|_e| { 121 | let expected_location = file!(); 122 | Box::new(LocationHandler::new(expected_location)) 123 | })); 124 | 125 | use eyre::ContextCompat; 126 | let err = read_path("totally_fake_path") 127 | .with_context(|| "oopsie") 128 | .unwrap_err(); 129 | 130 | // should panic if the location isn't in our crate 131 | println!("{:?}", err); 132 | } 133 | 134 | #[cfg(feature = "anyhow")] 135 | #[test] 136 | fn test_option_compat_context() { 137 | let _ = eyre::set_hook(Box::new(|_e| { 138 | let expected_location = file!(); 139 | Box::new(LocationHandler::new(expected_location)) 140 | })); 141 | 142 | use eyre::ContextCompat; 143 | let err = None::<()>.context("oopsie").unwrap_err(); 144 | 145 | // should panic if the location isn't in our crate 146 | println!("{:?}", err); 147 | } 148 | 149 | #[cfg(feature = "anyhow")] 150 | #[test] 151 | fn test_option_compat_with_context() { 152 | let _ = eyre::set_hook(Box::new(|_e| { 153 | let expected_location = file!(); 154 | Box::new(LocationHandler::new(expected_location)) 155 | })); 156 | 157 | use eyre::ContextCompat; 158 | let err = None::<()>.with_context(|| "oopsie").unwrap_err(); 159 | 160 | // should panic if the location isn't in our crate 161 | println!("{:?}", err); 162 | } 163 | -------------------------------------------------------------------------------- /eyre/tests/test_macros.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::eq_op)] 2 | mod common; 3 | 4 | use self::common::*; 5 | use eyre::{ensure, eyre, Result}; 6 | use std::cell::Cell; 7 | use std::future::Future; 8 | use std::pin::Pin; 9 | use std::task::Poll; 10 | 11 | #[test] 12 | fn test_messages() { 13 | maybe_install_handler().unwrap(); 14 | 15 | assert_eq!("oh no!", bail_literal().unwrap_err().to_string()); 16 | assert_eq!("oh no!", bail_fmt().unwrap_err().to_string()); 17 | assert_eq!("oh no!", bail_error().unwrap_err().to_string()); 18 | } 19 | 20 | #[test] 21 | fn test_ensure() { 22 | maybe_install_handler().unwrap(); 23 | 24 | let f = || -> Result<()> { 25 | ensure!(1 + 1 == 2, "This is correct"); 26 | Ok(()) 27 | }; 28 | assert!(f().is_ok()); 29 | 30 | let v = 1; 31 | let f = || -> Result<()> { 32 | ensure!(v + v == 2, "This is correct, v: {}", v); 33 | Ok(()) 34 | }; 35 | assert!(f().is_ok()); 36 | 37 | let f = || -> Result<()> { 38 | ensure!(v + v == 1, "This is not correct, v: {}", v); 39 | Ok(()) 40 | }; 41 | assert!(f().is_err()); 42 | 43 | let f = || { 44 | ensure!(v + v == 1); 45 | Ok(()) 46 | }; 47 | assert_eq!( 48 | f().unwrap_err().to_string(), 49 | "Condition failed: `v + v == 1`", 50 | ); 51 | } 52 | 53 | #[test] 54 | fn test_temporaries() { 55 | struct Ready(Option); 56 | 57 | impl Unpin for Ready {} 58 | 59 | impl Future for Ready { 60 | type Output = T; 61 | 62 | fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll { 63 | Poll::Ready(self.0.take().unwrap()) 64 | } 65 | } 66 | 67 | fn require_send_sync(_: impl Send + Sync) {} 68 | 69 | require_send_sync(async { 70 | // If eyre hasn't dropped any temporary format_args it creates by the 71 | // time it's done evaluating, those will stick around until the 72 | // semicolon, which is on the other side of the await point, making the 73 | // enclosing future non-Send. 74 | let _ = Ready(Some(eyre!("..."))).await; 75 | }); 76 | 77 | fn message(cell: Cell<&str>) -> &str { 78 | cell.get() 79 | } 80 | 81 | require_send_sync(async { 82 | let _ = Ready(Some(eyre!(message(Cell::new("..."))))).await; 83 | }); 84 | } 85 | 86 | #[test] 87 | #[cfg(not(eyre_no_fmt_args_capture))] 88 | fn test_capture_format_args() { 89 | maybe_install_handler().unwrap(); 90 | 91 | let var = 42; 92 | let err = eyre!("interpolate {var}"); 93 | assert_eq!("interpolate 42", err.to_string()); 94 | } 95 | 96 | #[test] 97 | fn test_brace_escape() { 98 | maybe_install_handler().unwrap(); 99 | 100 | let err = eyre!("unterminated ${{..}} expression"); 101 | assert_eq!("unterminated ${..} expression", err.to_string()); 102 | } 103 | -------------------------------------------------------------------------------- /eyre/tests/test_no_install.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(feature = "auto-install"))] 2 | 3 | use eyre::{eyre, set_hook, DefaultHandler, Report}; 4 | 5 | #[test] 6 | fn test_no_hook_panic() { 7 | let panic_res = std::panic::catch_unwind(|| eyre!("this will never be displayed")); 8 | assert!(panic_res.is_err()); 9 | 10 | let downcast_res = panic_res.unwrap_err().downcast::(); 11 | assert_eq!( 12 | *downcast_res.unwrap(), 13 | "a handler must always be installed if the `auto-install` feature is disabled" 14 | ); 15 | 16 | assert!(set_hook(Box::new(DefaultHandler::default_with)).is_ok()); 17 | let _error: Report = eyre!("this will be displayed if returned"); 18 | } 19 | -------------------------------------------------------------------------------- /eyre/tests/test_option.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use self::common::maybe_install_handler; 4 | use eyre::OptionExt; 5 | 6 | #[test] 7 | fn test_option_ok_or_eyre() { 8 | maybe_install_handler().unwrap(); 9 | 10 | let option: Option<()> = None; 11 | 12 | let result = option.ok_or_eyre("static str error"); 13 | 14 | assert_eq!(result.unwrap_err().to_string(), "static str error"); 15 | } 16 | -------------------------------------------------------------------------------- /eyre/tests/test_pyo3.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "pyo3")] 2 | 3 | use pyo3::prelude::*; 4 | 5 | use eyre::{bail, Result, WrapErr}; 6 | use std::ffi::CStr; 7 | 8 | fn f() -> Result<()> { 9 | use std::io; 10 | bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!")); 11 | } 12 | 13 | fn g() -> Result<()> { 14 | f().wrap_err("f failed") 15 | } 16 | 17 | fn h() -> Result<()> { 18 | g().wrap_err("g failed") 19 | } 20 | 21 | #[test] 22 | fn test_pyo3_exception_contents() { 23 | use pyo3::types::IntoPyDict; 24 | 25 | let err = h().unwrap_err(); 26 | let expected_contents = format!("{:?}", err); 27 | let pyerr = PyErr::from(err); 28 | 29 | Python::with_gil(|py| { 30 | let locals = [("err", pyerr)].into_py_dict(py).unwrap(); 31 | let pyerr = py 32 | .run( 33 | CStr::from_bytes_with_nul(b"raise err\0").unwrap(), 34 | None, 35 | Some(&locals), 36 | ) 37 | .unwrap_err(); 38 | assert_eq!(pyerr.value(py).to_string(), expected_contents); 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /eyre/tests/test_repr.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod drop; 3 | 4 | use self::common::maybe_install_handler; 5 | use self::drop::{DetectDrop, Flag}; 6 | use eyre::Report; 7 | use std::mem; 8 | 9 | #[test] 10 | fn test_error_size() { 11 | assert_eq!(mem::size_of::(), mem::size_of::()); 12 | } 13 | 14 | #[test] 15 | fn test_null_pointer_optimization() { 16 | assert_eq!( 17 | mem::size_of::>(), 18 | mem::size_of::() 19 | ); 20 | } 21 | 22 | #[test] 23 | fn test_autotraits() { 24 | fn assert() {} 25 | assert::(); 26 | } 27 | 28 | #[test] 29 | fn test_drop() { 30 | maybe_install_handler().unwrap(); 31 | 32 | let has_dropped = Flag::new(); 33 | drop(Report::new(DetectDrop::new("TestDrop", &has_dropped))); 34 | assert!(has_dropped.get()); 35 | } 36 | -------------------------------------------------------------------------------- /eyre/tests/test_source.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use self::common::maybe_install_handler; 4 | use eyre::{eyre, Report}; 5 | use std::error::Error as StdError; 6 | use std::fmt::{self, Display}; 7 | use std::io; 8 | 9 | #[derive(Debug)] 10 | enum TestError { 11 | Io(io::Error), 12 | } 13 | 14 | impl Display for TestError { 15 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 16 | match self { 17 | TestError::Io(e) => Display::fmt(e, formatter), 18 | } 19 | } 20 | } 21 | 22 | impl StdError for TestError { 23 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 24 | match self { 25 | TestError::Io(io) => Some(io), 26 | } 27 | } 28 | } 29 | 30 | #[test] 31 | fn test_literal_source() { 32 | maybe_install_handler().unwrap(); 33 | 34 | let error: Report = eyre!("oh no!"); 35 | assert!(error.source().is_none()); 36 | } 37 | 38 | #[test] 39 | fn test_variable_source() { 40 | maybe_install_handler().unwrap(); 41 | 42 | let msg = "oh no!"; 43 | let error = eyre!(msg); 44 | assert!(error.source().is_none()); 45 | 46 | let msg = msg.to_owned(); 47 | let error: Report = eyre!(msg); 48 | assert!(error.source().is_none()); 49 | } 50 | 51 | #[test] 52 | fn test_fmt_source() { 53 | maybe_install_handler().unwrap(); 54 | 55 | let error: Report = eyre!("{} {}!", "oh", "no"); 56 | assert!(error.source().is_none()); 57 | } 58 | 59 | #[test] 60 | fn test_io_source() { 61 | maybe_install_handler().unwrap(); 62 | 63 | let io = io::Error::new(io::ErrorKind::Other, "oh no!"); 64 | let error: Report = eyre!(TestError::Io(io)); 65 | assert_eq!("oh no!", error.source().unwrap().to_string()); 66 | } 67 | 68 | #[test] 69 | fn test_eyre_from_eyre() { 70 | maybe_install_handler().unwrap(); 71 | 72 | let error: Report = eyre!("oh no!").wrap_err("context"); 73 | let error = eyre!(error); 74 | assert_eq!("oh no!", error.source().unwrap().to_string()); 75 | } 76 | --------------------------------------------------------------------------------