├── .editorconfig ├── .envrc ├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── echo-server.rs └── structured-context.rs ├── flake.lock ├── flake.nix ├── rust-toolchain.toml └── src ├── context.rs ├── context ├── any.rs ├── iter.rs ├── map.rs ├── map │ └── facade.rs ├── singleton.rs ├── singleton │ └── facade.rs └── unit.rs ├── converter.rs ├── core.rs ├── core └── data.rs ├── kind.rs ├── lib.rs ├── overlay.rs ├── overlay ├── error.rs └── result.rs ├── report.rs └── report └── inner.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | 10 | [*.nix] 11 | indent_size = 2 12 | 13 | [*.md] 14 | indent_size = 2 15 | trim_trailing_whitespace = false 16 | 17 | [*.rs] 18 | indent_size = 4 19 | 20 | [{*.json,*.yml,*.yaml,*.toml}] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | echo "direnv: loading .envrc" 2 | use flake 3 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | env: 8 | DEV_SHELL_NAME: ci 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | setup: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup Nix 20 | uses: aldoborrero/use-nix-action@v4 21 | with: 22 | nix_path: nixpkgs=channel:nixos-unstable 23 | 24 | check: 25 | runs-on: ubuntu-latest 26 | needs: setup 27 | 28 | steps: 29 | - name: Checkout Repository 30 | uses: actions/checkout@v3 31 | 32 | - name: Check 33 | run: | 34 | cargo check 35 | cargo clippy 36 | 37 | - name: Format 38 | run: cargo fmt --all -- --check 39 | 40 | test: 41 | runs-on: ubuntu-latest 42 | needs: setup 43 | 44 | steps: 45 | - name: Checkout Repository 46 | uses: actions/checkout@v3 47 | 48 | - name: Test 49 | run: cargo test 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | **/*.rs.bk 4 | *.pdb 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anyerr" 7 | version = "0.1.1" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anyerr" 3 | version = "0.1.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Dynamic error library with rich error wrapping and context support" 7 | authors = ["Justin Chen oosquare@outlook.com"] 8 | repository = "https://github.com/oosquare/anyerr" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Justin Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `anyerr` 2 | 3 | [GitHub](https://github.com/oosquare/anyerr) | [Crates.io](https://crates.io/crates/anyerr) | [Docs.rs](https://docs.rs/anyerr) 4 | 5 | `anyerr` is a flexible and powerful error-handling library for Rust. It provides a highly customizable error type called `AnyError`, allowing you to capture and manage rich error information, including custom error kinds, backtraces, and contextual data. 6 | 7 | ## Features 8 | 9 | - **Error Composition**: Combine and wrap errors while preserving original information. 10 | - **Customizable Error Kinds**: Use predefined error kinds or create your own. 11 | - **Contextual Data Support**: Attach rich, structured context to your errors for better debugging. 12 | - **Backtrace Support**: Automatically captures backtraces to simplify error diagnosis. 13 | - **Error Reporting**: Customize and write formated and detailed error messages to `stdout`, loggers and so on. 14 | 15 | ## Installation 16 | 17 | Add `anyerr` to your `Cargo.toml`: 18 | 19 | ```toml 20 | [dependencies] 21 | anyerr = "0.1.1" 22 | ``` 23 | 24 | ## Getting Started 25 | 26 | ### Defining a Custom Error Type 27 | 28 | `AnyError` is the core type of this library. It works similarly to `Box`, but with added capabilities for handling contexts and custom error kinds. You can define your own error type with `AnyError` by choosing appropriate components. 29 | 30 | Here's an example: 31 | 32 | ```rust 33 | // Make this module accessible to your whole crate. 34 | mod err { 35 | use anyerr::AnyError as AnyErrorTemplate; 36 | use anyerr::context::LiteralKeyStringMapContext; 37 | 38 | pub use anyerr::{Intermediate, Overlay}; 39 | pub use anyerr::kind::DefaultErrorKind as ErrKind; 40 | pub use anyerr::Report; 41 | 42 | pub type AnyError = AnyErrorTemplate; 43 | pub type AnyResult = Result; 44 | } 45 | ``` 46 | 47 | ### Creating and Using Errors 48 | 49 | You can create errors with different levels of detail, depending on your needs. 50 | 51 | ```rust 52 | use err::*; 53 | 54 | fn fail() -> AnyResult<()> { 55 | // Use `AnyError::minimal()` to create a simple [`String`]-based error. 56 | Err(AnyError::minimal("this function always fails")) 57 | } 58 | 59 | fn check_positive(x: i32) -> AnyResult<()> { 60 | if x > 0 { 61 | return Ok(()); 62 | } 63 | // Use `AnyError::quick()` to quickly create an error with an error 64 | // message and an error kind. 65 | Err(AnyError::quick( 66 | "expects `x` to be a positive number", 67 | ErrKind::ValueValidation 68 | )) 69 | } 70 | 71 | fn try_add_username( 72 | usernames: &mut Vec, 73 | new_username: String 74 | ) -> AnyResult { 75 | let res = usernames.iter() 76 | .enumerate() 77 | .find(|(_, username)| **username == new_username) 78 | .map(|(index, _)| index); 79 | if let Some(index) = res { 80 | // Use `AnyError::builder()` to create an error with all essential 81 | // context you'll need. 82 | let err = AnyError::builder() 83 | .message("the username already exists") 84 | .kind(ErrKind::RuleViolation) 85 | .context("new_username", new_username) 86 | .context("index", index) 87 | .build(); 88 | Err(err) 89 | } else { 90 | usernames.push(new_username); 91 | Ok(usernames.len() - 1) 92 | } 93 | } 94 | 95 | fn parse_i32(input: &str) -> AnyResult { 96 | // Use `AnyError::wrap()` to wrap any other error type. 97 | input.parse::().map_err(AnyError::wrap) 98 | } 99 | ``` 100 | 101 | Using `AnyError` is also rather straightforward. 102 | 103 | ```rust 104 | use err::*; 105 | 106 | fn main() { 107 | let mut usernames = Vec::new(); 108 | 109 | let res = try_add_username(&mut usernames, "foo").unwrap(); 110 | assert_eq!(res, 0); 111 | 112 | let err = try_add_username(&mut usernames, "foo").unwrap_err(); 113 | assert_eq!(err.to_string(), "the username already exists"); // Or `err.message()`. 114 | assert_eq!(err.kind(), ErrKind::RuleViolation); 115 | assert_eq!(err.get("new_username"), Some("\"foo\"")); 116 | assert_eq!(err.get("index"), Some("0")); 117 | } 118 | ``` 119 | 120 | ### Chaining Errors 121 | 122 | `AnyError` allows attaching chaining errors for better logging and easier debugging. 123 | 124 | ```rust 125 | use err::*; 126 | 127 | struct UserRepository { 128 | conn: Arc, 129 | } 130 | 131 | impl UserRepository { 132 | pub fn find_by_username(&self, username: &str) -> AnyResult { 133 | // Don't build SQL statements yourself in practice. 134 | let statement = format!("SELECT * FROM users WHERE users.username = '{username}'"); 135 | let data = self.conn.query(&statement) 136 | .overlay(("could not get a `User` due to SQL execution error", ErrKind::EntityAbsence)) 137 | .context("username", username) 138 | .context("statement", statement)?; 139 | let entity = User::try_from(data) 140 | .overlay(("could not get a `User` due to serialization error", Errkind::EntityAbsence)) 141 | .context("username", username)?; 142 | Ok(entity) 143 | } 144 | } 145 | ``` 146 | 147 | ### Error Reporting 148 | 149 | Use `Report` to display a formated error message with details: 150 | 151 | ```rust 152 | use err::*; 153 | 154 | fn source_error() -> AnyResult<()> { 155 | let err = AnyError::builder() 156 | .message("the source error is here") 157 | .kind(ErrKind::InfrastructureFailure) 158 | .context("key1", "value1") 159 | .context("key2", "value2") 160 | .build(); 161 | Err(err) 162 | } 163 | 164 | fn intermediate_error() -> AnyResult<()> { 165 | source_error() 166 | .overlay("the intermediate error is here") 167 | .context("key3", "value3")?; 168 | Ok(()) 169 | } 170 | 171 | fn toplevel_error() -> AnyResult<()> { 172 | intermediate_error() 173 | .overlay("the toplevel error is here")?; 174 | Ok(()) 175 | } 176 | 177 | fn main() -> impl Termination { 178 | Report::capture(|| { 179 | toplevel_error()?; 180 | Ok(()) 181 | }) 182 | } 183 | ``` 184 | 185 | The expected output is 186 | 187 | ```plain 188 | Error: 189 | (Unknown) the toplevel error is here 190 | Caused by: 191 | (Unknown) the intermediate error is here 192 | [key3 = "value3"] 193 | Caused by: 194 | (InfrastructureFailure) the source error is here 195 | [key1 = "value1", key2 = "value2"] 196 | 197 | Stack backtrace: 198 | 0: anyerr::core::data::ErrorDataBuilder::build 199 | at ./src/core/data.rs:210:28 200 | 1: anyerr::core::AnyErrorBuilder::build 201 | at ./src/core.rs:415:24 202 | 2: anyerr::source_error 203 | at ./src/main.rs:18:15 204 | 3: anyerr::intermediate_error 205 | at ./src/main.rs:28:5 206 | 4: anyerr::toplevel_error 207 | at ./src/main.rs:35:5 208 | 5: anyerr::main::{{closure}} 209 | at ./src/main.rs:40:43 210 | 6: anyerr::report::Report::capture 211 | at ./src/report.rs:52:15 212 | 7: anyerr::main 213 | at ./src/main.rs:40:5 214 | ... 215 | ``` 216 | 217 | ### Advanced Usage 218 | 219 | See API documentation for more features and advanced usages of different types in this crate. 220 | 221 | ### Examples 222 | 223 | Please refer to example codes in the [examples](https://github.com/oosquare/anyerr/tree/main/examples) directory. 224 | 225 | ## License 226 | 227 | Copyright (C) 2025 Justin Chen 228 | 229 | This project is licensed under the [MIT License](https://github.com/oosquare/anyerr/blob/main/LICENSE). 230 | 231 | -------------------------------------------------------------------------------- /examples/echo-server.rs: -------------------------------------------------------------------------------- 1 | mod err { 2 | use anyerr::context::LiteralKeyStringMapContext; 3 | use anyerr::AnyError as AnyErrorTemplate; 4 | 5 | pub use anyerr::kind::NoErrorKind as ErrKind; 6 | pub use anyerr::Report; 7 | pub use anyerr::{Intermediate, Overlay}; 8 | 9 | pub type AnyError = AnyErrorTemplate; 10 | pub type AnyResult = Result; 11 | } 12 | 13 | use std::io::{Read, Write}; 14 | use std::net::{TcpListener, TcpStream}; 15 | use std::process::Termination; 16 | use std::thread; 17 | use std::time::Duration; 18 | 19 | use err::*; 20 | 21 | const SERVER_IP: &str = "127.0.0.1"; 22 | const SERVER_PORT: &str = "8080"; 23 | 24 | fn main() -> impl Termination { 25 | Report::capture(|| { 26 | let listener = TcpListener::bind(format!("{SERVER_IP}:{SERVER_PORT}")) 27 | .map_err(AnyError::wrap) 28 | .overlay("could not bind the listener to the endpoint") 29 | .context("ip", SERVER_IP) 30 | .context("port", SERVER_PORT)?; 31 | 32 | eprintln!("Started listening on {SERVER_IP}:{SERVER_PORT}"); 33 | 34 | for connection in listener.incoming() { 35 | let Ok(stream) = connection else { 36 | continue; 37 | }; 38 | 39 | thread::spawn(move || { 40 | handle_connection(stream).unwrap_or_else(|err| { 41 | let report = Report::wrap(err).kind(false); 42 | eprintln!("{report}"); 43 | }); 44 | }); 45 | } 46 | 47 | Ok(()) 48 | }) 49 | .kind(false) 50 | } 51 | 52 | fn handle_connection(mut stream: TcpStream) -> AnyResult<()> { 53 | let client_addr = stream 54 | .peer_addr() 55 | .map_or("".into(), |addr| addr.to_string()); 56 | let mut buffer = [0u8; 256]; 57 | let mut total_read = 0; 58 | 59 | eprintln!("{client_addr} started the connection"); 60 | thread::sleep(Duration::from_secs(3)); 61 | 62 | loop { 63 | let size_read = stream 64 | .read(&mut buffer) 65 | .map_err(AnyError::wrap) 66 | .overlay("could not read bytes from the client") 67 | .context("client_addr", &client_addr) 68 | .context("total_read", total_read)?; 69 | total_read += size_read; 70 | 71 | if size_read == 0 { 72 | eprintln!("{client_addr} closed the connection"); 73 | return Ok(()); 74 | } 75 | 76 | thread::sleep(Duration::from_secs(3)); 77 | 78 | let mut cursor = 0; 79 | while cursor < size_read { 80 | let size_written = stream 81 | .write(&buffer[cursor..size_read]) 82 | .map_err(AnyError::wrap) 83 | .overlay("could not write bytes to the client") 84 | .context("client_addr", &client_addr) 85 | .context("total_read", total_read) 86 | .context("cursor", cursor)?; 87 | cursor += size_written; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /examples/structured-context.rs: -------------------------------------------------------------------------------- 1 | mod err { 2 | use std::fmt::{Display, Formatter, Result as FmtResult}; 3 | 4 | use anyerr::context::map::AnyMapContext; 5 | use anyerr::AnyError as AnyErrorTemplate; 6 | 7 | pub use anyerr::kind::NoErrorKind as ErrKind; 8 | 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 10 | pub enum ContextKey { 11 | ErrorCode, 12 | Timeout, 13 | Other(&'static str), 14 | } 15 | 16 | impl Display for ContextKey { 17 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 18 | match self { 19 | Self::ErrorCode => write!(f, "error-code"), 20 | Self::Timeout => write!(f, "timeout"), 21 | Self::Other(key) => write!(f, "{key}"), 22 | } 23 | } 24 | } 25 | 26 | type CustomKeyAnyMapContext = AnyMapContext; 27 | 28 | pub type AnyError = AnyErrorTemplate; 29 | pub type AnyResult = Result; 30 | } 31 | 32 | use err::*; 33 | 34 | fn fails() -> AnyResult<()> { 35 | let err = AnyError::builder() 36 | .message("an unknown error occurred") 37 | .context(ContextKey::ErrorCode, 42u32) 38 | .context(ContextKey::Timeout, false) 39 | .context(ContextKey::Other("function"), "fails()") 40 | .build(); 41 | Err(err) 42 | } 43 | 44 | fn main() { 45 | let err = fails().unwrap_err(); 46 | 47 | let error_code: &u32 = err.value_as(&ContextKey::ErrorCode).unwrap(); 48 | let is_timeout: &bool = err.value_as(&ContextKey::Timeout).unwrap(); 49 | let function_name: &&str = err.value_as(&ContextKey::Other("function")).unwrap(); 50 | 51 | eprintln!("The error code is {error_code}"); 52 | eprintln!("Whether the function failed due to timeout: {is_timeout}"); 53 | eprintln!("The name of the failed function: {function_name}"); 54 | } 55 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1736143030, 9 | "narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1737469691, 24 | "narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs-lib": { 38 | "locked": { 39 | "lastModified": 1735774519, 40 | "narHash": "sha256-CewEm1o2eVAnoqb6Ml+Qi9Gg/EfNAxbRx1lANGVyoLI=", 41 | "type": "tarball", 42 | "url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz" 43 | }, 44 | "original": { 45 | "type": "tarball", 46 | "url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz" 47 | } 48 | }, 49 | "nixpkgs_2": { 50 | "locked": { 51 | "lastModified": 1736320768, 52 | "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", 53 | "owner": "NixOS", 54 | "repo": "nixpkgs", 55 | "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", 56 | "type": "github" 57 | }, 58 | "original": { 59 | "owner": "NixOS", 60 | "ref": "nixpkgs-unstable", 61 | "repo": "nixpkgs", 62 | "type": "github" 63 | } 64 | }, 65 | "root": { 66 | "inputs": { 67 | "flake-parts": "flake-parts", 68 | "nixpkgs": "nixpkgs", 69 | "rust-overlay": "rust-overlay" 70 | } 71 | }, 72 | "rust-overlay": { 73 | "inputs": { 74 | "nixpkgs": "nixpkgs_2" 75 | }, 76 | "locked": { 77 | "lastModified": 1737512878, 78 | "narHash": "sha256-dgF6htdmfNnZzVInifks6npnCAyVsIHWSpWNs10RSW0=", 79 | "owner": "oxalica", 80 | "repo": "rust-overlay", 81 | "rev": "06b8ed0eee289fe94c66f1202ced9a6a2c59a14c", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "owner": "oxalica", 86 | "repo": "rust-overlay", 87 | "type": "github" 88 | } 89 | } 90 | }, 91 | "root": "root", 92 | "version": 7 93 | } 94 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Rust application environment."; 3 | 4 | inputs = { 5 | flake-parts.url = "github:hercules-ci/flake-parts"; 6 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 7 | rust-overlay.url = "github:oxalica/rust-overlay"; 8 | }; 9 | 10 | outputs = { self, flake-parts, ... }@inputs: 11 | flake-parts.lib.mkFlake { inherit inputs; } { 12 | systems = [ 13 | "x86_64-linux" 14 | "aarch64-linux" 15 | "x86_64-darwin" 16 | "aarch64-darwin" 17 | ]; 18 | 19 | perSystem = { self', inputs', system, ... }: let 20 | pkgs = import inputs.nixpkgs { 21 | inherit system; 22 | overlays = [ (import inputs.rust-overlay) ]; 23 | }; 24 | rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; 25 | in { 26 | devShells.default = let 27 | mkShell = pkgs.mkShell.override { stdenv = pkgs.stdenvNoCC; }; 28 | in 29 | mkShell { 30 | buildInputs = [ rust ]; 31 | packages = with pkgs; [ nil ]; 32 | }; 33 | 34 | devShells.ci = let 35 | mkShell = pkgs.mkShell.override { stdenv = pkgs.stdenvNoCC; }; 36 | in 37 | mkShell { 38 | buildInputs = [ rust ]; 39 | }; 40 | }; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.83.0" 3 | profile = "default" 4 | components = ["rustc", "rust-std", "cargo", "rust-docs", "rustfmt", "clippy", "rust-src", "rust-analyzer"] 5 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | pub mod any; 2 | pub mod iter; 3 | pub mod map; 4 | pub mod singleton; 5 | pub mod unit; 6 | 7 | use std::any::Any; 8 | use std::borrow::Borrow; 9 | use std::fmt::{Debug, Display}; 10 | use std::hash::Hash; 11 | 12 | use crate::converter::{Convertable, Converter}; 13 | 14 | pub use any::{AnyValue, DynAnyValue}; 15 | pub use map::{LiteralKeyAnyMapContext, StringKeyAnyMapContext}; 16 | pub use map::{LiteralKeyStringMapContext, StringKeyStringMapContext}; 17 | pub use singleton::{AnySingletonContext, FixedSingletonContext, StringSingletonContext}; 18 | pub use unit::UnitContext; 19 | 20 | /// The most fundamental trait of all context storage. 21 | /// 22 | /// The [`AbstractContext`] trait requires the structure of each context to be 23 | /// a map, and allows iteration over its entries. 24 | pub trait AbstractContext: Default + Debug + Send + Sync + 'static { 25 | /// The key of each entry. 26 | type Key; 27 | 28 | /// The value of each entry. 29 | type Value; 30 | 31 | /// The entry stored in this context. 32 | type Entry: Entry; 33 | 34 | /// The iterator over this context's entries. 35 | type Iter<'a>: Iter<'a, Entry = Self::Entry> 36 | where 37 | Self: 'a; 38 | 39 | /// Returns the context's iterator. 40 | fn iter(&self) -> Self::Iter<'_>; 41 | } 42 | 43 | /// The context that stores nothing. 44 | /// 45 | /// This kind of contexts fit very well in the circumstance where you will 46 | /// never employ the context information in key-value pairs, by getting rid of 47 | /// almost all useless relavent methods and the memory overhead with which 48 | /// other context storages may bring. 49 | /// 50 | /// For contexts implementing this trait, refer to the [`crate::context::unit`] 51 | /// module. 52 | /// 53 | /// # Example 54 | /// 55 | /// ```rust 56 | /// # use anyerr::context::{AbstractContext, UnitContext}; 57 | /// // `UnitContext` implements `NoContext`. 58 | /// let context = UnitContext; 59 | /// let mut iter = context.iter(); 60 | /// assert!(iter.next().is_none()); 61 | /// ``` 62 | #[allow(private_bounds)] 63 | pub trait NoContext: AbstractContext {} 64 | 65 | /// The context that is insertable. 66 | /// 67 | /// Types that implements the [`Context`] trait allow insertions to the internal 68 | /// storage and queries by keys. Conversions before insertions are also 69 | /// supported. 70 | pub trait Context: AbstractContext { 71 | /// The default converter used by the context, which transforms values to 72 | /// [`AbstractContext::Value`] before insertions. 73 | type Converter: Converter; 74 | 75 | /// Inserts the context information represented as a key-value pair. Any 76 | /// types that are compatible with [`AbstractContext::Key`] and 77 | /// [`AbstractContext::Value`] through the [`Into`] trait respectively are 78 | /// expected. 79 | fn insert(&mut self, key: Q, value: R) 80 | where 81 | Q: Into, 82 | R: Into; 83 | 84 | /// Converts the context information represented as a key-value pair using 85 | /// the specified converter `C` and then inserts the converted pair. Any 86 | /// types that are compatible with [`AbstractContext::Key`] and 87 | /// [`AbstractContext::Value`] through the [`Into`] and [`Convertable`] 88 | /// trait respectively are expected. 89 | fn insert_with(&mut self, key: Q, value: R) 90 | where 91 | Q: Into, 92 | C: Converter, 93 | R: Convertable, 94 | { 95 | self.insert(key, value.to()); 96 | } 97 | 98 | /// Returns the value corresponding to the given key. 99 | fn get(&self, key: &Q) -> Option<&::ValueBorrowed> 100 | where 101 | ::KeyBorrowed: Borrow, 102 | Q: Debug + Eq + Hash + ?Sized; 103 | } 104 | 105 | /// The insertable context which holds at most one entry. 106 | /// 107 | /// Accessing the entry it contains can be done with the method offered by the 108 | /// [`SingletonContext`] trait, without specifying the key. 109 | /// 110 | /// For contexts implementing this trait, refer to the 111 | /// [`crate::context::singleton`] module. 112 | /// 113 | /// # Example 114 | /// 115 | /// ```rust 116 | /// # use anyerr::context::{Context, SingletonContext, StringSingletonContext}; 117 | /// // `StringSingletonContext` implements `SingletonContext`. 118 | /// let mut context = StringSingletonContext::new(); 119 | /// assert_eq!(context.value(), None); 120 | /// context.insert((), "context"); 121 | /// assert_eq!(context.value(), Some("context")); 122 | /// context.insert((), "context2"); 123 | /// assert_eq!(context.value(), Some("context2")); 124 | /// ``` 125 | pub trait SingletonContext: Context { 126 | /// Returns the value of the only entry in the context if it exists. 127 | fn value(&self) -> Option<&::ValueBorrowed>; 128 | } 129 | 130 | /// The context where each entry's value is a [`String`]. 131 | /// 132 | /// For contexts implementing this trait, refer to the [`crate::context::map`] 133 | /// module. 134 | /// 135 | /// # Example 136 | /// 137 | /// ```rust 138 | /// # use anyerr::context::{Context, StringContext, LiteralKeyStringMapContext}; 139 | /// # use anyerr::converter::DebugConverter; 140 | /// // `LiteralKeyStringMapContext` implements `StringContext`. 141 | /// let mut context = LiteralKeyStringMapContext::new(); 142 | /// context.insert_with::("i32", 42); 143 | /// context.insert_with::("&str", "context"); 144 | /// assert_eq!(context.get("i32"), Some("42")); 145 | /// assert_eq!(context.get("&str"), Some("\"context\"")); 146 | /// ``` 147 | pub trait StringContext 148 | where 149 | Self: Context>, 150 | { 151 | } 152 | 153 | /// The context where each entry's value is a [`Box`]. 154 | /// 155 | /// Accessing the entry it contains and conveniently casting the result to a 156 | /// concrete type can be done with the method offered by the [`AnyContext`] 157 | /// trait. 158 | /// 159 | /// For contexts implementing this trait, refer to the [`crate::context::map`] 160 | /// module. 161 | /// 162 | /// # Example 163 | /// 164 | /// ```rust 165 | /// # use anyerr::context::{AnyContext, Context, LiteralKeyAnyMapContext}; 166 | /// # use anyerr::converter::BoxConverter; 167 | /// // `LiteralKeyAnyMapContext` implements `AnyContext`. 168 | /// let mut context = LiteralKeyAnyMapContext::new(); 169 | /// context.insert_with::("error-code", 42i32); 170 | /// context.insert_with::("cause", "unknown"); 171 | /// assert_eq!(context.value_as::("error-code"), Some(&42i32)); 172 | /// assert_eq!(context.value_as::<&str, _>("cause"), Some(&"unknown")); 173 | /// ``` 174 | pub trait AnyContext 175 | where 176 | Self: Context, Entry: Entry>, 177 | { 178 | /// Returns the value corresponding to the given key and tries to cast it 179 | /// to the type `T`. Returns `None` if the entry doesn't exist or the 180 | /// downcasting fails. 181 | fn value_as(&self, key: &Q) -> Option<&T> 182 | where 183 | ::KeyBorrowed: Borrow, 184 | Q: Debug + Eq + Hash + ?Sized, 185 | T: Any, 186 | { 187 | self.get(key).and_then(|value| value.downcast_ref::()) 188 | } 189 | } 190 | 191 | /// The common representation of entries in different kinds of contexts. 192 | pub trait Entry: Debug + Send + Sync + 'static { 193 | /// The identifier used to indexing an entry. 194 | type Key: Borrow + Debug + Send + Sync + 'static; 195 | 196 | /// The borrowed type of [`Entry::Key`]. 197 | type KeyBorrowed: Debug + Display + Eq + Hash + ?Sized + Send + Sync + 'static; 198 | 199 | /// The value corresponding to the key. 200 | type Value: Borrow + Debug + Send + Sync + 'static; 201 | 202 | /// The borrowed type of [`Entry::Value`]. 203 | type ValueBorrowed: Debug + ?Sized + Send + Sync + 'static; 204 | 205 | /// Creates a new entry with the given key and value. 206 | fn new(key: Q, value: R) -> Self 207 | where 208 | Q: Into, 209 | R: Into; 210 | 211 | /// Returns the entry's key. 212 | fn key(&self) -> &Self::KeyBorrowed; 213 | 214 | /// Returns the entry's value. 215 | fn value(&self) -> &Self::ValueBorrowed; 216 | } 217 | 218 | /// A dedicated iterator of the context storage. 219 | pub trait Iter<'a>: Default + Iterator { 220 | /// The entry whose reference will be yielded by this iterator. 221 | type Entry: 'a; 222 | 223 | /// Combines two iterator into a new iterator, which will iterate over the 224 | /// union of two sets of entries. 225 | fn compose(self, other: Self) -> Self; 226 | } 227 | -------------------------------------------------------------------------------- /src/context/any.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::fmt::Debug; 3 | 4 | /// A type alias of the trait object of the [`AnyValue`] trait. 5 | pub type DynAnyValue = dyn AnyValue + Send + Sync + 'static; 6 | 7 | /// A [`Send`] and [`Sync`] type that is capable of type reflection and 8 | /// displaying itself. 9 | pub trait AnyValue: Any + Debug + Send + Sync + 'static { 10 | /// Returns a reference to itself as a trait object of the [`Any`] trait, 11 | /// i.e. upcasts itself to a trait object of [`Any`]. 12 | fn as_any(&self) -> &dyn Any; 13 | } 14 | 15 | impl AnyValue for T 16 | where 17 | T: Any + Debug + Send + Sync, 18 | { 19 | fn as_any(&self) -> &dyn Any { 20 | self 21 | } 22 | } 23 | 24 | impl dyn AnyValue + Send + Sync + 'static { 25 | /// Returns `true` if the inner type is the same as `T`. 26 | pub fn is(&self) -> bool { 27 | self.as_any().is::() 28 | } 29 | 30 | /// Returns some reference to the inner value if it is of type `T`, or 31 | /// `None` if it isn't. 32 | pub fn downcast_ref(&self) -> Option<&T> { 33 | self.as_any().downcast_ref::() 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::*; 40 | 41 | #[test] 42 | fn any_value_rtti_succeeds() { 43 | let x: Box = Box::new(String::from("any value")); 44 | assert!(x.is::() && !x.is::()); 45 | assert_eq!(x.downcast_ref::().unwrap(), "any value"); 46 | } 47 | 48 | #[test] 49 | fn any_value_debug_succeeds() { 50 | let x: Box = Box::new(String::from("any value")); 51 | assert_eq!(format!("{x:?}"), "\"any value\""); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/context/iter.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::context::{Entry, Iter}; 4 | 5 | /// The common iterator implementation of all insertable contexts. 6 | #[derive(Debug)] 7 | pub enum CommonIter<'a, E, I> 8 | where 9 | E: Entry + 'a, 10 | I: Iterator, 11 | { 12 | Node { 13 | iter: I, 14 | next: Option>>, 15 | }, 16 | None, 17 | } 18 | 19 | impl<'a, E, I> CommonIter<'a, E, I> 20 | where 21 | E: Entry + 'a, 22 | I: Iterator, 23 | { 24 | pub fn new() -> Self { 25 | Self::None 26 | } 27 | 28 | fn append(&mut self, other: Self) { 29 | if matches!(other, Self::None) { 30 | #[allow(clippy::needless_return)] 31 | return; 32 | } else if let Self::Node { next, .. } = self { 33 | if let Some(next) = next { 34 | next.append(other); 35 | } else { 36 | *next = Some(Box::new(other)); 37 | } 38 | } else { 39 | *self = other; 40 | } 41 | } 42 | } 43 | 44 | impl<'a, E, I> Default for CommonIter<'a, E, I> 45 | where 46 | E: Entry + 'a, 47 | I: Iterator, 48 | { 49 | fn default() -> Self { 50 | Self::new() 51 | } 52 | } 53 | 54 | impl<'a, E, I> From for CommonIter<'a, E, I> 55 | where 56 | E: Entry + 'a, 57 | I: Iterator, 58 | { 59 | fn from(iter: I) -> Self { 60 | Self::Node { iter, next: None } 61 | } 62 | } 63 | 64 | impl<'a, E, I> Iterator for CommonIter<'a, E, I> 65 | where 66 | E: Entry + 'a, 67 | I: Iterator, 68 | { 69 | type Item = &'a E; 70 | 71 | fn next(&mut self) -> Option { 72 | while let Self::Node { iter, next, .. } = self { 73 | if let Some(item) = iter.next() { 74 | return Some(item); 75 | } else if let Some(next) = next.take() { 76 | *self = *next; 77 | } else { 78 | *self = Self::None; 79 | } 80 | } 81 | None 82 | } 83 | } 84 | 85 | impl<'a, E, I> Iter<'a> for CommonIter<'a, E, I> 86 | where 87 | E: Entry + 'a, 88 | I: Iterator, 89 | { 90 | type Entry = E; 91 | 92 | fn compose(mut self, other: Self) -> Self { 93 | self.append(other); 94 | self 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/context/map.rs: -------------------------------------------------------------------------------- 1 | mod facade; 2 | 3 | pub use facade::*; 4 | 5 | use std::borrow::Borrow; 6 | use std::fmt::{Debug, Display}; 7 | use std::hash::Hash; 8 | use std::marker::PhantomData; 9 | use std::slice::Iter as SliceIter; 10 | 11 | use crate::context::iter::CommonIter; 12 | use crate::context::{AbstractContext, Context, Entry}; 13 | use crate::converter::Converter; 14 | 15 | /// The iterator of [`MapContext`]. 16 | pub type MapIter<'a, E> = CommonIter<'a, E, SliceIter<'a, E>>; 17 | 18 | /// The common implementation of all map-like contexts. 19 | #[derive(Debug, PartialEq, Eq)] 20 | pub struct MapContext { 21 | entries: Vec, 22 | _phantom: PhantomData, 23 | } 24 | 25 | impl MapContext { 26 | /// Creates a new [`MapContext`]. 27 | pub fn new() -> Self { 28 | Self::from(Vec::::new()) 29 | } 30 | } 31 | 32 | impl From> for MapContext { 33 | fn from(entries: Vec) -> Self { 34 | Self { 35 | entries, 36 | _phantom: Default::default(), 37 | } 38 | } 39 | } 40 | 41 | impl From> for MapContext 42 | where 43 | Q: Into<::Key>, 44 | R: Into<::Value>, 45 | { 46 | fn from(entries: Vec<(Q, R)>) -> Self { 47 | entries.into_iter().collect() 48 | } 49 | } 50 | 51 | impl FromIterator for MapContext { 52 | fn from_iter>(iter: T) -> Self { 53 | iter.into_iter().collect::>().into() 54 | } 55 | } 56 | 57 | impl FromIterator<(Q, R)> for MapContext 58 | where 59 | Q: Into<::Key>, 60 | R: Into<::Value>, 61 | { 62 | fn from_iter>(iter: T) -> Self { 63 | iter.into_iter() 64 | .map(|(key, value)| E::new(key.into(), value.into())) 65 | .collect() 66 | } 67 | } 68 | 69 | impl Default for MapContext { 70 | fn default() -> Self { 71 | Self::new() 72 | } 73 | } 74 | 75 | impl AbstractContext for MapContext { 76 | type Key = E::Key; 77 | 78 | type Value = E::Value; 79 | 80 | type Entry = E; 81 | 82 | type Iter<'a> 83 | = MapIter<'a, E> 84 | where 85 | E: 'a; 86 | 87 | fn iter(&self) -> Self::Iter<'_> { 88 | self.entries.iter().into() 89 | } 90 | } 91 | 92 | impl Context for MapContext { 93 | type Converter = C; 94 | 95 | fn insert(&mut self, key: Q, value: R) 96 | where 97 | Q: Into, 98 | R: Into, 99 | { 100 | self.entries.push(Self::Entry::new(key, value)); 101 | } 102 | 103 | fn get(&self, key: &Q) -> Option<&::ValueBorrowed> 104 | where 105 | ::KeyBorrowed: Borrow, 106 | Q: Debug + Eq + Hash + ?Sized, 107 | { 108 | self.entries 109 | .iter() 110 | .find(|entry| entry.key().borrow() == key) 111 | .map(Entry::value) 112 | } 113 | } 114 | 115 | /// The common implementation of entries of map-like contexts, typically 116 | /// used by [`MapContext`]. 117 | #[derive(Debug, PartialEq, Eq, Hash)] 118 | pub struct MapEntry 119 | where 120 | K: Borrow + Debug + Send + Sync + 'static, 121 | KB: Debug + Display + Eq + Hash + ?Sized + Send + Sync + 'static, 122 | V: Borrow + Debug + Send + Sync + 'static, 123 | VB: Debug + ?Sized + Send + Sync + 'static, 124 | { 125 | key: K, 126 | value: V, 127 | _phantom: PhantomData<(Box, Box)>, 128 | } 129 | 130 | impl From<(Q, R)> for MapEntry 131 | where 132 | K: Borrow + Debug + Send + Sync + 'static, 133 | KB: Debug + Display + Eq + Hash + ?Sized + Send + Sync + 'static, 134 | V: Borrow + Debug + Send + Sync + 'static, 135 | VB: Debug + ?Sized + Send + Sync + 'static, 136 | Q: Into<::Key>, 137 | R: Into<::Value>, 138 | { 139 | fn from((key, value): (Q, R)) -> Self { 140 | Self::new(key.into(), value.into()) 141 | } 142 | } 143 | 144 | impl Entry for MapEntry 145 | where 146 | K: Borrow + Debug + Send + Sync + 'static, 147 | KB: Debug + Display + Eq + Hash + ?Sized + Send + Sync + 'static, 148 | V: Borrow + Debug + Send + Sync + 'static, 149 | VB: Debug + ?Sized + Send + Sync + 'static, 150 | { 151 | type Key = K; 152 | 153 | type KeyBorrowed = KB; 154 | 155 | type Value = V; 156 | 157 | type ValueBorrowed = VB; 158 | 159 | fn new(key: Q, value: R) -> Self 160 | where 161 | Q: Into, 162 | R: Into, 163 | { 164 | Self { 165 | key: key.into(), 166 | value: value.into(), 167 | _phantom: Default::default(), 168 | } 169 | } 170 | 171 | fn key(&self) -> &Self::KeyBorrowed { 172 | self.key.borrow() 173 | } 174 | 175 | fn value(&self) -> &Self::ValueBorrowed { 176 | self.value.borrow() 177 | } 178 | } 179 | 180 | #[cfg(test)] 181 | mod tests { 182 | use crate::context::Iter; 183 | use crate::converter::DebugConverter; 184 | 185 | use super::*; 186 | 187 | type TestEntry = MapEntry; 188 | type TestContext = MapContext; 189 | 190 | #[test] 191 | fn string_entry_getter_succeeds() { 192 | let entry = TestEntry::new("key", "1"); 193 | assert_eq!("key", entry.key()); 194 | assert_eq!("1", entry.value()); 195 | } 196 | 197 | #[test] 198 | fn string_map_context_operation_succeeds() { 199 | let mut context = TestContext::new(); 200 | context.insert("key1", "1"); 201 | context.insert_with::("key2", 2); 202 | context.insert_with::("key3", "3"); 203 | assert_eq!(context.get("key1").unwrap(), "1"); 204 | assert_eq!(context.get("key2").unwrap(), "2"); 205 | assert_eq!(context.get("key3").unwrap(), "\"3\""); 206 | } 207 | 208 | #[test] 209 | fn string_map_context_iter_from_succeeds() { 210 | let context = TestContext::from(vec![ 211 | TestEntry::new("key1", "1"), 212 | TestEntry::new("key2", "2"), 213 | ]); 214 | let mut iter = context.iter(); 215 | assert_eq!(Some(&TestEntry::new("key1", "1")), iter.next()); 216 | assert_eq!(Some(&TestEntry::new("key2", "2")), iter.next()); 217 | assert_eq!(None, iter.next()); 218 | } 219 | 220 | #[test] 221 | fn string_map_context_iter_concat_succeeds() { 222 | let context1 = TestContext::from(vec![ 223 | TestEntry::new("key1", "1"), 224 | TestEntry::new("key2", "2"), 225 | ]); 226 | let context2 = TestContext::from(vec![ 227 | TestEntry::new("key3", "3"), 228 | TestEntry::new("key4", "4"), 229 | ]); 230 | let mut iter = context1.iter().compose(context2.iter()); 231 | assert_eq!(Some(&TestEntry::new("key1", "1")), iter.next()); 232 | assert_eq!(Some(&TestEntry::new("key2", "2")), iter.next()); 233 | assert_eq!(Some(&TestEntry::new("key3", "3")), iter.next()); 234 | assert_eq!(Some(&TestEntry::new("key4", "4")), iter.next()); 235 | assert_eq!(None, iter.next()); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/context/map/facade.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; 3 | use std::hash::Hash; 4 | 5 | use crate::context::any::DynAnyValue; 6 | use crate::context::map::{MapContext, MapEntry, MapIter}; 7 | use crate::context::{AbstractContext, AnyContext, Entry, StringContext}; 8 | use crate::converter::{BoxConverter, DebugConverter}; 9 | 10 | /// A map entry whose value is a [`String`]. 11 | pub type StringMapEntry = MapEntry; 12 | /// A context whose entries are [`StringMapEntry`]. 13 | pub type StringMapContext = MapContext, DebugConverter>; 14 | /// The iterator of [`StringMapContext`]. 15 | pub type StringMapIter<'a, K, KB> = MapIter<'a, StringMapEntry>; 16 | 17 | /// A [`StringMapContext`] which uses [`String`] as its keys. 18 | pub type StringKeyStringMapContext = StringMapContext; 19 | /// A [`StringMapEntry`] which uses [`String`] as its keys. 20 | pub type StringKeyStringMapEntry = ::Entry; 21 | /// The iterator of [`StringKeyStringMapContext`]. 22 | pub type StringKeyStringMapIter<'a> = ::Iter<'a>; 23 | 24 | /// A [`StringMapContext`] which uses `&'static str` as its keys. 25 | pub type LiteralKeyStringMapContext = StringMapContext<&'static str, str>; 26 | /// A [`StringMapEntry`] which uses `&'static str` as its keys. 27 | pub type LiteralKeyStringMapEntry = ::Entry; 28 | /// The iterator of [`LiteralKeyStringMapContext`]. 29 | pub type LiteralKeyStringMapIter<'a> = ::Iter<'a>; 30 | 31 | /// A map entry whose value is a [`Box`]. 32 | pub type AnyMapEntry = MapEntry, DynAnyValue>; 33 | /// A context whose entries are [`AnyMapEntry`]. 34 | pub type AnyMapContext = MapContext, BoxConverter>; 35 | /// The iterator of [`AnyMapContext`]. 36 | pub type AnyMapIter<'a, K, KB> = MapIter<'a, AnyMapEntry>; 37 | 38 | /// A [`AnyMapContext`] which uses [`String`] as its keys. 39 | pub type StringKeyAnyMapContext = AnyMapContext; 40 | /// A [`AnyMapEntry`] which uses [`String`] as its keys. 41 | pub type StringKeyAnyMapEntry = ::Entry; 42 | /// The iterator of [`StringKeyAnyMapContext`]. 43 | pub type StringKeyAnyMapIter<'a> = ::Iter<'a>; 44 | 45 | /// A [`AnyMapContext`] which uses `&'static str` as its keys. 46 | pub type LiteralKeyAnyMapContext = AnyMapContext<&'static str, str>; 47 | /// A [`StringMapEntry`] which uses `&'static str` as its keys. 48 | pub type LiteralKeyAnyMapEntry = ::Entry; 49 | /// The iterator of [`LiteralKeyAnyMapContext`]. 50 | pub type LiteralKeyAnyMapIter<'a> = ::Iter<'a>; 51 | 52 | impl Display for StringMapEntry 53 | where 54 | K: Borrow + Debug + Send + Sync + 'static, 55 | KB: Debug + Display + Eq + Hash + ?Sized + Send + Sync, 56 | { 57 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 58 | write!(f, "{} = {}", self.key(), self.value()) 59 | } 60 | } 61 | 62 | impl StringContext for StringMapContext 63 | where 64 | K: Borrow + Debug + Send + Sync + 'static, 65 | KB: Debug + Display + Eq + Hash + ?Sized + Send + Sync, 66 | { 67 | } 68 | 69 | impl Display for AnyMapEntry 70 | where 71 | K: Borrow + Debug + Send + Sync + 'static, 72 | KB: Debug + Display + Eq + Hash + ?Sized + Send + Sync, 73 | { 74 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 75 | write!(f, "{} = {:?}", self.key(), self.value()) 76 | } 77 | } 78 | 79 | impl AnyContext for AnyMapContext 80 | where 81 | K: Borrow + Debug + Send + Sync + 'static, 82 | KB: Debug + Display + Eq + Hash + ?Sized + Send + Sync, 83 | { 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use crate::context::Context; 89 | 90 | use super::*; 91 | 92 | type TestContext = AnyMapContext<&'static str, str>; 93 | 94 | #[test] 95 | fn any_map_context_operation() { 96 | let mut ctx = TestContext::new(); 97 | ctx.insert_with::("i32", 1i32); 98 | ctx.insert_with::("string", "test"); 99 | assert_eq!(ctx.value_as::("i32"), Some(&1i32)); 100 | assert_eq!(ctx.value_as::<&str, _>("string"), Some(&"test")); 101 | assert_eq!(ctx.value_as::<(), _>("i32"), None); 102 | assert_eq!(ctx.value_as::<(), _>("string"), None); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/context/singleton.rs: -------------------------------------------------------------------------------- 1 | mod facade; 2 | 3 | use std::borrow::Borrow; 4 | use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; 5 | use std::hash::Hash; 6 | use std::marker::PhantomData; 7 | use std::option::Iter as InnerIter; 8 | 9 | use crate::context::iter::CommonIter; 10 | use crate::context::{AbstractContext, Context, Entry, SingletonContext}; 11 | use crate::converter::IntoConverter; 12 | 13 | pub use facade::*; 14 | 15 | pub type OptionIter<'a, E> = CommonIter<'a, E, InnerIter<'a, E>>; 16 | 17 | /// The common implementation of [`SingletonContext`]. 18 | #[derive(Debug)] 19 | pub struct OptionContext 20 | where 21 | E: Entry, 22 | { 23 | entry: Option, 24 | } 25 | 26 | impl OptionContext 27 | where 28 | E: Entry, 29 | { 30 | pub fn new() -> Self { 31 | Default::default() 32 | } 33 | } 34 | 35 | impl Default for OptionContext 36 | where 37 | E: Entry, 38 | { 39 | fn default() -> Self { 40 | Self { entry: None } 41 | } 42 | } 43 | 44 | impl From for OptionContext 45 | where 46 | E: Entry, 47 | { 48 | fn from(entry: E) -> Self { 49 | Self { entry: Some(entry) } 50 | } 51 | } 52 | 53 | impl AbstractContext for OptionContext 54 | where 55 | E: Entry, 56 | { 57 | type Key = E::Key; 58 | 59 | type Value = E::Value; 60 | 61 | type Entry = E; 62 | 63 | type Iter<'a> 64 | = OptionIter<'a, E> 65 | where 66 | Self: 'a; 67 | 68 | fn iter(&self) -> Self::Iter<'_> { 69 | if self.entry.is_some() { 70 | OptionIter::from(self.entry.iter()) 71 | } else { 72 | OptionIter::new() 73 | } 74 | } 75 | } 76 | 77 | impl Context for OptionContext 78 | where 79 | E: Entry, 80 | { 81 | type Converter = IntoConverter; 82 | 83 | fn insert(&mut self, key: Q, value: R) 84 | where 85 | Q: Into, 86 | R: Into, 87 | { 88 | self.entry = Some(Self::Entry::new(key.into(), value.into())) 89 | } 90 | 91 | fn get(&self, _key: &Q) -> Option<&::ValueBorrowed> 92 | where 93 | ::KeyBorrowed: Borrow, 94 | Q: Debug + Eq + Hash + ?Sized, 95 | { 96 | self.value() 97 | } 98 | } 99 | 100 | impl SingletonContext for OptionContext 101 | where 102 | E: Entry, 103 | { 104 | fn value(&self) -> Option<&::ValueBorrowed> { 105 | self.entry.as_ref().map(Entry::value) 106 | } 107 | } 108 | 109 | /// The entry used by [`OptionContext`]. 110 | #[derive(Debug, PartialEq, Eq, Hash)] 111 | pub struct OptionEntry 112 | where 113 | V: Borrow + Debug + Send + Sync + 'static, 114 | VB: Debug + ?Sized + Send + Sync + 'static, 115 | { 116 | value: V, 117 | _phantom: PhantomData>, 118 | } 119 | 120 | impl From for OptionEntry 121 | where 122 | V: Borrow + Debug + Send + Sync + 'static, 123 | VB: Debug + ?Sized + Send + Sync + 'static, 124 | { 125 | fn from(value: V) -> Self { 126 | Self::new(OptionKey::SELF_VALUE, value) 127 | } 128 | } 129 | 130 | impl Entry for OptionEntry 131 | where 132 | V: Borrow + Debug + Send + Sync + 'static, 133 | VB: Debug + ?Sized + Send + Sync + 'static, 134 | { 135 | type Key = OptionKey; 136 | 137 | type KeyBorrowed = OptionKey; 138 | 139 | type Value = V; 140 | 141 | type ValueBorrowed = VB; 142 | 143 | fn new(_key: Q, value: R) -> Self 144 | where 145 | Q: Into, 146 | R: Into, 147 | { 148 | Self { 149 | value: value.into(), 150 | _phantom: Default::default(), 151 | } 152 | } 153 | 154 | fn key(&self) -> &Self::KeyBorrowed { 155 | &OptionKey::SELF_VALUE 156 | } 157 | 158 | fn value(&self) -> &Self::ValueBorrowed { 159 | self.value.borrow() 160 | } 161 | } 162 | 163 | /// The key of [`OptionEntry`]. 164 | /// 165 | /// It's in fact equivlaent to the unit type, i.e. `()`, and conversions 166 | /// between `()` are also offered. Usually you just need to use `()` in where 167 | /// the key is expected. 168 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] 169 | pub struct OptionKey; 170 | 171 | impl OptionKey { 172 | const SELF_VALUE: OptionKey = OptionKey; 173 | const UNIT_VALUE: () = (); 174 | } 175 | 176 | impl Display for OptionKey { 177 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 178 | write!(f, "OptionKey") 179 | } 180 | } 181 | 182 | impl From<()> for OptionKey { 183 | fn from(_value: ()) -> Self { 184 | Self::SELF_VALUE 185 | } 186 | } 187 | 188 | impl Borrow<()> for OptionKey { 189 | fn borrow(&self) -> &() { 190 | &Self::UNIT_VALUE 191 | } 192 | } 193 | 194 | #[cfg(test)] 195 | mod tests { 196 | use crate::context::Iter; 197 | 198 | use super::*; 199 | 200 | type TestEntry = OptionEntry; 201 | type TestContext = OptionContext; 202 | 203 | #[test] 204 | fn option_entry_getter_succeeds() { 205 | let entry = TestEntry::new((), "test"); 206 | assert_eq!(entry.value(), "test"); 207 | } 208 | 209 | #[test] 210 | fn option_context_operations_succeeds() { 211 | let mut ctx = TestContext::new(); 212 | assert!(ctx.get(&()).is_none()); 213 | 214 | ctx.insert((), "test"); 215 | assert_eq!(ctx.get(&()).unwrap(), "test"); 216 | ctx.insert((), "test1"); 217 | assert_eq!(ctx.get(&()).unwrap(), "test1"); 218 | } 219 | 220 | #[test] 221 | fn option_iter_next_succeeds() { 222 | let mut ctx = TestContext::new(); 223 | let mut iter = ctx.iter(); 224 | assert_eq!(iter.next(), None); 225 | 226 | ctx.insert((), "test"); 227 | let mut iter = ctx.iter(); 228 | assert_eq!(iter.next().unwrap().value(), "test"); 229 | assert_eq!(iter.next(), None); 230 | } 231 | 232 | #[test] 233 | fn option_iter_compose_succeeds() { 234 | let mut ctx1 = TestContext::new(); 235 | ctx1.insert((), "test1"); 236 | let mut ctx2 = TestContext::new(); 237 | ctx2.insert((), "test2"); 238 | 239 | let mut iter = ctx1.iter().compose(ctx2.iter()); 240 | assert_eq!(iter.next().unwrap().value(), "test1"); 241 | assert_eq!(iter.next().unwrap().value(), "test2"); 242 | assert_eq!(iter.next(), None); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/context/singleton/facade.rs: -------------------------------------------------------------------------------- 1 | use crate::context::singleton::{OptionContext, OptionEntry}; 2 | use crate::context::{AbstractContext, DynAnyValue}; 3 | 4 | /// A singleton entry whose value is a [`String`]. 5 | pub type StringSingletonEntry = OptionEntry; 6 | /// A context whose entry is [`StringSingletonEntry`]. 7 | pub type StringSingletonContext = OptionContext; 8 | /// The iterator of [`StringSingletonContext`]. 9 | pub type StringSingletonIter<'a> = ::Iter<'a>; 10 | 11 | /// A singleton entry whose value is a [`Box`]. 12 | pub type AnySingletonEntry = OptionEntry, DynAnyValue>; 13 | /// A context whose entry is [`AnySingletonEntry`]. 14 | pub type AnySingletonContext = OptionContext; 15 | /// The iterator of [`AnySingletonContext`]. 16 | pub type AnySingletonIter<'a> = ::Iter<'a>; 17 | 18 | /// A singleton entry whose value is a fixed generic type `T`. 19 | pub type FixedSingletonEntry = OptionEntry; 20 | /// A context whose entry is [`FixedSingletonEntry`]. 21 | pub type FixedSingletonContext = OptionContext>; 22 | /// The iterator of [`FixedSingletonContext`]. 23 | pub type FixedSingletonIter<'a, T> = as AbstractContext>::Iter<'a>; 24 | -------------------------------------------------------------------------------- /src/context/unit.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter, Result as FmtResult}; 2 | use std::marker::PhantomData; 3 | 4 | use crate::context::{AbstractContext, Entry, Iter, NoContext}; 5 | 6 | /// The context that stores nothing. 7 | /// 8 | /// [`UnitContext`] is a ZST, thus integrating your error type with it leads to 9 | /// zero memory overhead. 10 | #[derive(Debug)] 11 | pub struct UnitContext; 12 | 13 | impl Default for UnitContext { 14 | fn default() -> Self { 15 | Self 16 | } 17 | } 18 | 19 | impl AbstractContext for UnitContext { 20 | type Key = Dummy; 21 | 22 | type Value = Dummy; 23 | 24 | type Entry = DummyEntry; 25 | 26 | type Iter<'a> = UnitIter<'a>; 27 | 28 | fn iter(&self) -> Self::Iter<'_> { 29 | Self::Iter::default() 30 | } 31 | } 32 | 33 | impl NoContext for UnitContext {} 34 | 35 | /// An uninhabit type, used as dummy keys or values. 36 | #[derive(Debug, PartialEq, Eq, Hash)] 37 | pub enum Dummy {} 38 | 39 | impl Display for Dummy { 40 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 41 | write!(f, "{self:?}") 42 | } 43 | } 44 | 45 | /// An uninhabit type, used as entries. 46 | #[derive(Debug)] 47 | pub enum DummyEntry {} 48 | 49 | impl Entry for DummyEntry { 50 | type Key = Dummy; 51 | 52 | type KeyBorrowed = Dummy; 53 | 54 | type Value = Dummy; 55 | 56 | type ValueBorrowed = Dummy; 57 | 58 | fn new(_key: Q, _value: V) -> Self 59 | where 60 | Q: Into, 61 | V: Into, 62 | { 63 | unreachable!("`_key` and `_value` are instances of the `Dummy` type, which is uninhabited") 64 | } 65 | 66 | fn key(&self) -> &Self::KeyBorrowed { 67 | unreachable!("`_key` and `_value` are instances of the `Dummy` type, which is uninhabited") 68 | } 69 | 70 | fn value(&self) -> &Self::ValueBorrowed { 71 | unreachable!("`_key` and `_value` are instances of the `Dummy` type, which is uninhabited") 72 | } 73 | } 74 | 75 | /// The iterator of [`UnitContext`], producing nothing. 76 | #[derive(Debug, Default)] 77 | pub struct UnitIter<'a> { 78 | _phantom: PhantomData<&'a ()>, 79 | } 80 | 81 | impl<'a> Iterator for UnitIter<'a> { 82 | type Item = &'a DummyEntry; 83 | 84 | fn next(&mut self) -> Option { 85 | None 86 | } 87 | } 88 | 89 | impl<'a> Iter<'a> for UnitIter<'a> { 90 | type Entry = DummyEntry; 91 | 92 | fn compose(self, _other: Self) -> Self { 93 | self 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/converter.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::context::AnyValue; 4 | 5 | /// The trait used to select the method to transforming values. 6 | /// 7 | /// Implementers of this trait define how values are mapped to other types. For 8 | /// example, the [`DebugConverter`] implements a conversion using the 9 | /// [`Debug::fmt()`] method to format values as strings. This is a trait that 10 | /// doesn't actually perform the conversion itself, but specifies how values 11 | /// should be transformed. 12 | /// 13 | /// For more converters, refer to the [`crate::converter`] module. To learn 14 | /// about the underlying type that works with the conversion, see the 15 | /// [`Convertable`] trait. 16 | pub trait Converter: Debug + Send + Sync + 'static {} 17 | 18 | /// A converter that uses the [`Debug`] trait to format values 19 | #[derive(Debug)] 20 | pub struct DebugConverter; 21 | 22 | impl Converter for DebugConverter {} 23 | 24 | /// A converter that uses the [`Into`] trait to convert values into another 25 | /// type. 26 | #[derive(Debug)] 27 | pub struct IntoConverter; 28 | 29 | impl Converter for IntoConverter {} 30 | 31 | /// A converter that converts values into a type-erased [`Box`] containing any 32 | /// value. 33 | #[derive(Debug)] 34 | pub struct BoxConverter; 35 | 36 | impl Converter for BoxConverter {} 37 | 38 | /// The trait marking a type that is able to be converted to another one using 39 | /// a [`Converter`]. 40 | /// 41 | /// The [`Convertable`] trait allows types to be converted into another type `T` 42 | /// using a specified converter `C`. 43 | pub trait Convertable: Sized { 44 | /// Converts the value into another type `T` using the specified converter 45 | /// `C`. The conversion should not fail, so not a [`Result`] but a 46 | /// `T` is expected. 47 | /// 48 | /// # Examples 49 | /// 50 | /// ```rust 51 | /// # use anyerr::converter::{Convertable, DebugConverter}; 52 | /// assert_eq!(<_ as Convertable>::to(42), "42"); 53 | /// assert_eq!(<_ as Convertable>::to("str"), "\"str\""); 54 | /// ``` 55 | fn to(self) -> T; 56 | } 57 | 58 | impl> Convertable for S { 59 | fn to(self) -> T { 60 | format!("{self:?}").into() 61 | } 62 | } 63 | 64 | impl, T> Convertable for S { 65 | fn to(self) -> T { 66 | self.into() 67 | } 68 | } 69 | 70 | impl Convertable for S 71 | where 72 | S: AnyValue, 73 | T: From>, 74 | { 75 | fn to(self) -> T { 76 | let res: Box = Box::new(self); 77 | res.into() 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | 85 | type BoxAnyValue = Box; 86 | 87 | #[test] 88 | fn converter_convert_succeeds() { 89 | assert_eq!(<_ as Convertable>::to(1), "1"); 90 | assert_eq!( 91 | <_ as Convertable>::to("1"), 92 | r#""1""# 93 | ); 94 | 95 | assert_eq!( 96 | <_ as Convertable>::to("str"), 97 | String::from("str") 98 | ); 99 | 100 | let res = <_ as Convertable>::to("1"); 101 | assert_eq!(format!("{res:?}"), "\"1\""); 102 | } 103 | 104 | #[test] 105 | fn converter_convert_succeeds_when_delegated_by_function() { 106 | fn do_with, T>(source: S) -> T { 107 | source.to() 108 | } 109 | 110 | assert_eq!(do_with::(1), "1"); 111 | assert_eq!(do_with::("1"), "\"1\""); 112 | 113 | assert_eq!( 114 | do_with::("str"), 115 | String::from("str") 116 | ); 117 | 118 | let res = do_with::("1"); 119 | assert_eq!(format!("{res:?}"), "\"1\""); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | mod data; 2 | 3 | use std::any::{Any, TypeId}; 4 | use std::backtrace::Backtrace; 5 | use std::borrow::Borrow; 6 | use std::error::Error; 7 | use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; 8 | use std::hash::Hash; 9 | use std::mem::{self, ManuallyDrop}; 10 | 11 | use crate::context::{AbstractContext, Context, Entry}; 12 | use crate::converter::Convertable; 13 | use crate::kind::Kind; 14 | 15 | use data::{ErrorData, ErrorDataBuilder}; 16 | 17 | /// The central type for general error handling and reporting. 18 | /// 19 | /// With the [`Error`] trait implemented, [`AnyError`] can be used in any place 20 | /// where you expect an error type should work. Not only can it carry either a 21 | /// [`String`]-based error message or an arbitrary error type, but also supports 22 | /// sophisticated error wrapping and context recording, with the help of 23 | /// [`Overlay`] and [`Context`] trait and so on. 24 | /// 25 | /// An [`AnyError`] typically holds the following information: 26 | /// 27 | /// - An error message, which describes the reason why the error occurs 28 | /// - An error kind implementing [`Kind`], which is supplied on your own 29 | /// - A backtrace capturing a snapshot of the call stack at that point 30 | /// - The source error wrapped in this [`AnyError`] 31 | /// - Some additional context 32 | /// 33 | /// A leaf [`AnyError`] can be instantiated with associative functions or its 34 | /// builder through [`AnyError::builder()`], while an intermediate [`AnyError`] 35 | /// which contains another error is often produced by the builder or the 36 | /// [`Overlay`] trait. 37 | /// 38 | /// [`Overlay`]: `crate::overlay::Overlay` 39 | #[derive(Debug)] 40 | pub struct AnyError(Box>) 41 | where 42 | C: AbstractContext, 43 | K: Kind; 44 | 45 | impl AnyError 46 | where 47 | C: AbstractContext, 48 | K: Kind, 49 | { 50 | /// Makes an [`AnyError`] with the given error message and use 51 | /// `K::default()` as the default kind. 52 | /// 53 | /// # Example 54 | /// 55 | /// ```rust 56 | /// # use anyerr::AnyError as AnyErrorTemplate; 57 | /// # use anyerr::kind::DefaultErrorKind; 58 | /// # use anyerr::context::LiteralKeyStringMapContext; 59 | /// type AnyError = AnyErrorTemplate; 60 | /// let err = AnyError::minimal("an error occurred"); 61 | /// assert_eq!(err.to_string(), "an error occurred"); 62 | /// ``` 63 | pub fn minimal>(message: S) -> Self { 64 | Self::from(ErrorData::::Simple { 65 | kind: K::default(), 66 | message: message.into(), 67 | backtrace: Backtrace::capture(), 68 | context: C::default(), 69 | }) 70 | } 71 | 72 | /// Makes an [`AnyError`] with the given error message and kind, which is a 73 | /// quick way to report an error if no additional context is required. 74 | /// 75 | /// # Example 76 | /// 77 | /// ```rust 78 | /// # use anyerr::AnyError as AnyErrorTemplate; 79 | /// # use anyerr::kind::DefaultErrorKind; 80 | /// # use anyerr::context::LiteralKeyStringMapContext; 81 | /// type AnyError = AnyErrorTemplate; 82 | /// let err = AnyError::quick("a positive number is expected", DefaultErrorKind::ValueValidation); 83 | /// assert_eq!(err.to_string(), "a positive number is expected"); 84 | /// assert_eq!(err.kind(), DefaultErrorKind::ValueValidation); 85 | /// ``` 86 | pub fn quick>(message: S, kind: K) -> Self { 87 | Self::from(ErrorData::::Simple { 88 | kind, 89 | message: message.into(), 90 | backtrace: Backtrace::capture(), 91 | context: C::default(), 92 | }) 93 | } 94 | 95 | /// Wraps an arbitrary error in an [`AnyError`] with a backtrace attached. 96 | /// Note that if this error is already an [`AnyError`], it'll be returned 97 | /// directly. 98 | /// 99 | /// # Example 100 | /// 101 | /// ```rust 102 | /// # use anyerr::AnyError as AnyErrorTemplate; 103 | /// # use anyerr::kind::DefaultErrorKind; 104 | /// # use anyerr::context::LiteralKeyStringMapContext; 105 | /// type AnyError = AnyErrorTemplate; 106 | /// let err = "not i32".parse::().map_err(AnyError::wrap); 107 | /// // do anything with `err` in a universal fashion on the fly ... 108 | /// ``` 109 | pub fn wrap(err: E) -> Self 110 | where 111 | E: Error + Any + Send + Sync + 'static, 112 | { 113 | if TypeId::of::() == TypeId::of::() { 114 | // SAFETY: we already checked `E` is actually `Self` 115 | unsafe { mem::transmute_copy(&ManuallyDrop::new(err)) } 116 | } else { 117 | Self::from(ErrorData::Wrapped { 118 | backtrace: Backtrace::capture(), 119 | inner: Box::new(err), 120 | }) 121 | } 122 | } 123 | 124 | /// Returns a dedicated builder [`AnyErrorBuilder`] to instantiate an 125 | /// [`AnyError`]. 126 | pub fn builder() -> AnyErrorBuilder { 127 | AnyErrorBuilder::new() 128 | } 129 | 130 | /// Returns the kind of this error. 131 | pub fn kind(&self) -> K { 132 | self.0.kind() 133 | } 134 | 135 | /// Returns the error message of this error. 136 | pub fn message(&self) -> String { 137 | self.0.message() 138 | } 139 | 140 | /// Returns the backtrace captured where the deepest error occurred. 141 | pub fn backtrace(&self) -> &Backtrace { 142 | self.0.backtrace() 143 | } 144 | 145 | /// Returns an iterator which iterates over either all attached context 146 | /// or those added to the most outer error. 147 | pub fn context(&self, depth: ContextDepth) -> C::Iter<'_> { 148 | self.0.context(depth) 149 | } 150 | 151 | /// Returns true if the inner type is the same as `E`. Note that the error 152 | /// is not equivalent to the source error, which stands for the current 153 | /// [`AnyError`]'s cause, while the former means the external error type 154 | /// wrapped in this [`AnyError`]. Apart from the case where an external 155 | /// error is wrapped, an [`AnyError`] is considered to be itself, i.e. 156 | /// [`AnyError::is()`] returns `true` if and only if `E` is [`AnyError`]. 157 | pub fn is(&self) -> bool 158 | where 159 | E: Error + Send + Sync + 'static, 160 | { 161 | match &*self.0 { 162 | ErrorData::Simple { .. } => TypeId::of::() == TypeId::of::(), 163 | ErrorData::Layered { .. } => TypeId::of::() == TypeId::of::(), 164 | ErrorData::Wrapped { inner, .. } => inner.is::(), 165 | } 166 | } 167 | 168 | /// Attempts to downcast the [`AnyError`] to the inner error of type `E`. 169 | /// 170 | /// # Errors 171 | /// 172 | /// This function will return the original [`AnyError`] if the actual type 173 | /// of the inner error is not `E`. 174 | pub fn downcast(self) -> Result 175 | where 176 | E: Error + Send + Sync + 'static, 177 | { 178 | match *self.0 { 179 | ErrorData::Simple { .. } | ErrorData::Layered { .. } => { 180 | if TypeId::of::() == TypeId::of::() { 181 | // SAFETY: it has been proved that `E` is actually `Self` 182 | Ok(unsafe { mem::transmute_copy(&ManuallyDrop::new(self)) }) 183 | } else { 184 | Err(self) 185 | } 186 | } 187 | ErrorData::Wrapped { inner, backtrace } => inner 188 | .downcast::() 189 | .map(|res| *res) 190 | .map_err(|inner| Self::from(ErrorData::Wrapped { backtrace, inner })), 191 | } 192 | } 193 | 194 | /// Returns some reference to the inner error if it is of type `E`, or 195 | /// `None` if it isn't. 196 | pub fn downcast_ref(&self) -> Option<&E> 197 | where 198 | E: Error + Send + Sync + 'static, 199 | { 200 | match &*self.0 { 201 | ErrorData::Simple { .. } | ErrorData::Layered { .. } => { 202 | (self as &dyn Any).downcast_ref::() 203 | } 204 | ErrorData::Wrapped { inner, .. } => inner.downcast_ref::(), 205 | } 206 | } 207 | 208 | /// Returns some mutable reference to the inner error if it is of type `E`, 209 | /// or `None` if it isn't. 210 | pub fn downcast_mut(&mut self) -> Option<&mut E> 211 | where 212 | E: Error + Send + Sync + 'static, 213 | { 214 | let use_inner = matches!(&*self.0, ErrorData::Wrapped { .. }); 215 | if !use_inner { 216 | (self as &mut dyn Any).downcast_mut::() 217 | } else { 218 | let ErrorData::Wrapped { inner, .. } = &mut *self.0 else { 219 | unreachable!("`self.data` matches `ErrorData::Wrapped {{ .. }}`"); 220 | }; 221 | inner.downcast_mut::() 222 | } 223 | } 224 | } 225 | 226 | impl AnyError 227 | where 228 | C: crate::context::SingletonContext, 229 | K: Kind, 230 | { 231 | /// Returns the context information carried by this [`AnyError`], 232 | /// where `C` is a [`SingletonContext`]. 233 | /// 234 | /// # Example 235 | /// 236 | /// ```rust 237 | /// # use anyerr::AnyError as AnyErrorTemplate; 238 | /// # use anyerr::kind::DefaultErrorKind; 239 | /// # use anyerr::context::StringSingletonContext; 240 | /// type AnyError = AnyErrorTemplate; 241 | /// let err = AnyError::builder().message("err").context((), "ctx").build(); 242 | /// assert_eq!(err.value(), Some("ctx")); 243 | /// ``` 244 | /// 245 | /// [`SingletonContext`]: `crate::context::SingletonContext` 246 | pub fn value(&self) -> Option<&::ValueBorrowed> { 247 | self.0.value() 248 | } 249 | } 250 | 251 | impl AnyError 252 | where 253 | C: crate::context::StringContext, 254 | K: Kind, 255 | { 256 | /// Returns the context information carried by this [`AnyError`] by 257 | /// `key`, where `C` is a [`StringContext`]. 258 | /// 259 | /// # Example 260 | /// 261 | /// ```rust 262 | /// # use anyerr::AnyError as AnyErrorTemplate; 263 | /// # use anyerr::kind::DefaultErrorKind; 264 | /// # use anyerr::context::LiteralKeyStringMapContext; 265 | /// type AnyError = AnyErrorTemplate; 266 | /// let err = AnyError::builder() 267 | /// .message("err") 268 | /// .context("&str", "value") 269 | /// .context("i32", 42) 270 | /// .build(); 271 | /// assert_eq!(err.get("&str"), Some("\"value\"")); 272 | /// assert_eq!(err.get("i32"), Some("42")); 273 | /// ``` 274 | /// 275 | /// [`StringContext`]: `crate::context::StringContext` 276 | pub fn get(&self, key: &Q) -> Option<&::ValueBorrowed> 277 | where 278 | ::KeyBorrowed: Borrow, 279 | Q: Debug + Eq + Hash + ?Sized, 280 | { 281 | self.0.get(key) 282 | } 283 | } 284 | 285 | impl AnyError 286 | where 287 | C: crate::context::AnyContext, 288 | K: Kind, 289 | { 290 | /// Returns the context information carried by this [`AnyError`] by 291 | /// `key` and then attempts to convert the result to a `T`, where `C` is a 292 | /// [`AnyContext`]. 293 | /// 294 | /// # Example 295 | /// 296 | /// ```rust 297 | /// # use anyerr::AnyError as AnyErrorTemplate; 298 | /// # use anyerr::kind::DefaultErrorKind; 299 | /// # use anyerr::context::StringKeyAnyMapContext; 300 | /// type AnyError = AnyErrorTemplate; 301 | /// let err = AnyError::builder() 302 | /// .message("err") 303 | /// .context("&str", "value") 304 | /// .context("i32", 42) 305 | /// .build(); 306 | /// assert_eq!(err.value_as::<&'static str, _>("&str"), Some(&"value")); 307 | /// assert_eq!(err.value_as::("i32"), Some(&42)); 308 | /// ``` 309 | /// 310 | /// [`AnyContext`]: `crate::context::AnyContext` 311 | pub fn value_as(&self, key: &Q) -> Option<&T> 312 | where 313 | ::KeyBorrowed: Borrow, 314 | Q: Debug + Eq + Hash + ?Sized, 315 | T: Any, 316 | { 317 | self.0.value_as::(key) 318 | } 319 | } 320 | 321 | impl From> for AnyError 322 | where 323 | C: AbstractContext, 324 | K: Kind, 325 | { 326 | fn from(data: ErrorData) -> Self { 327 | Self(Box::new(data)) 328 | } 329 | } 330 | 331 | impl Display for AnyError 332 | where 333 | C: AbstractContext, 334 | K: Kind, 335 | { 336 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 337 | Display::fmt(&self.0, f) 338 | } 339 | } 340 | 341 | impl Error for AnyError 342 | where 343 | C: AbstractContext, 344 | K: Kind, 345 | { 346 | fn source(&self) -> Option<&(dyn Error + 'static)> { 347 | self.0.source() 348 | } 349 | } 350 | 351 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 352 | pub enum ContextDepth { 353 | All, 354 | Shallowest, 355 | } 356 | 357 | /// The builder of [`AnyError`]. 358 | /// 359 | /// The only way to get an [`AnyErrorBuilder`] is calling the 360 | /// [`AnyError::builder()`] function. [`AnyErrorBuilder`] can build almost all 361 | /// kinds of [`AnyError`] except wrapping an external error, which can be done 362 | /// with [`AnyError::wrap()`]. 363 | /// 364 | /// For some cases where you'd like to provide more context for your error, you 365 | /// may refer to the following example: 366 | /// 367 | /// ```rust 368 | /// # use anyerr::AnyError as AnyErrorTemplate; 369 | /// # use anyerr::kind::DefaultErrorKind; 370 | /// # use anyerr::context::LiteralKeyStringMapContext; 371 | /// type AnyError = AnyErrorTemplate; 372 | /// 373 | /// let err = AnyError::builder() 374 | /// .kind(DefaultErrorKind::ValueValidation) 375 | /// .message("could not parse `&str` to `u32`") 376 | /// .context("string", "-1") 377 | /// .context("target-type", String::from("u32")) 378 | /// .context("expected", -1) 379 | /// .build(); 380 | /// 381 | /// assert_eq!(err.kind(), DefaultErrorKind::ValueValidation); 382 | /// assert_eq!(err.to_string(), "could not parse `&str` to `u32`"); 383 | /// ``` 384 | pub struct AnyErrorBuilder(ErrorDataBuilder) 385 | where 386 | C: AbstractContext, 387 | K: Kind; 388 | 389 | impl AnyErrorBuilder 390 | where 391 | C: AbstractContext, 392 | K: Kind, 393 | { 394 | fn new() -> Self { 395 | Self(ErrorDataBuilder::new()) 396 | } 397 | 398 | /// Specifies the error kind of the resulting error. 399 | pub fn kind(self, kind: K) -> Self { 400 | Self(self.0.kind(kind)) 401 | } 402 | 403 | /// Specifies the error message of the resulting error. 404 | pub fn message>(self, message: S) -> Self { 405 | Self(self.0.message(message)) 406 | } 407 | 408 | /// Specifies the cause of the resulting error. 409 | pub fn source(self, source: AnyError) -> Self { 410 | Self(self.0.source(source)) 411 | } 412 | 413 | /// Returns the error with the provided data for each fields. 414 | pub fn build(self) -> AnyError { 415 | AnyError::from(self.0.build()) 416 | } 417 | } 418 | 419 | impl AnyErrorBuilder 420 | where 421 | C: Context, 422 | K: Kind, 423 | { 424 | /// Adds some context represented as a key-value pair to the resulting 425 | /// error. 426 | pub fn context(self, key: Q, value: R) -> Self 427 | where 428 | Q: Into, 429 | R: Convertable, 430 | { 431 | Self(self.0.context(key, value)) 432 | } 433 | } 434 | 435 | #[cfg(test)] 436 | mod tests { 437 | use std::num::ParseIntError; 438 | 439 | use crate::context::StringKeyStringMapContext; 440 | use crate::kind::DefaultErrorKind; 441 | 442 | use super::*; 443 | 444 | type DefaultAnyError = AnyError; 445 | type DefaultErrorData = ErrorData; 446 | 447 | #[test] 448 | fn any_error_builder_succeeds() { 449 | let inner = "-1".parse::().unwrap_err(); 450 | let source = DefaultAnyError::wrap(inner); 451 | 452 | let err = DefaultAnyError::builder() 453 | .kind(DefaultErrorKind::ValueValidation) 454 | .message("could not parse `&str` to `u32`") 455 | .context("string", "-1") 456 | .context("target-type", String::from("u32")) 457 | .context("expected", -1) 458 | .source(source) 459 | .build(); 460 | 461 | assert_eq!(err.kind(), DefaultErrorKind::ValueValidation); 462 | assert_eq!(err.to_string(), "could not parse `&str` to `u32`"); 463 | assert_eq!(err.context(ContextDepth::All).count(), 3); 464 | assert!(err.source().is_some()); 465 | } 466 | 467 | #[test] 468 | fn any_error_wrap_succeeds() { 469 | { 470 | let inner = "".parse::().unwrap_err(); 471 | let err = DefaultAnyError::wrap(inner.clone()); 472 | assert_eq!(err.to_string(), inner.to_string()); 473 | } 474 | { 475 | let inner = DefaultAnyError::minimal("error"); 476 | let err = DefaultAnyError::wrap(inner); 477 | assert!(err.source().is_none()); 478 | } 479 | } 480 | 481 | #[test] 482 | fn any_error_downcast_succeeds() { 483 | { 484 | let mut err = DefaultAnyError::minimal("error"); 485 | assert!(err.downcast_ref::().is_some()); 486 | assert!(err.downcast_mut::().is_some()); 487 | assert!(err.downcast::().is_ok()); 488 | } 489 | { 490 | let source = DefaultAnyError::minimal("inner"); 491 | let mut err = DefaultAnyError::from(DefaultErrorData::Layered { 492 | kind: DefaultErrorKind::Unknown, 493 | message: "error".into(), 494 | context: StringKeyStringMapContext::new(), 495 | source, 496 | }); 497 | assert!(err.downcast_ref::().is_some()); 498 | assert!(err.downcast_mut::().is_some()); 499 | assert!(err.downcast::().is_ok()); 500 | } 501 | { 502 | let inner = "".parse::().unwrap_err(); 503 | let mut err = DefaultAnyError::wrap(inner); 504 | assert!(err.downcast_ref::().is_some()); 505 | assert!(err.downcast_mut::().is_some()); 506 | assert!(err.downcast::().is_ok()); 507 | } 508 | } 509 | 510 | #[test] 511 | fn any_error_propagation_succeeds() { 512 | fn try_parse(val: &str) -> Result { 513 | val.parse() 514 | } 515 | 516 | fn try_increment(val: &str) -> Result { 517 | let val = try_parse(val).map_err(AnyError::wrap)?; 518 | Ok(val + 1) 519 | } 520 | 521 | assert_eq!(try_increment("1").unwrap(), 2); 522 | assert!(try_increment("").is_err()); 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /src/core/data.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::backtrace::Backtrace; 3 | use std::borrow::Borrow; 4 | use std::error::Error; 5 | use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; 6 | use std::hash::Hash; 7 | 8 | use crate::context::{AbstractContext, Context, Entry, Iter}; 9 | use crate::converter::Convertable; 10 | use crate::core::{AnyError, ContextDepth}; 11 | use crate::kind::Kind; 12 | 13 | #[derive(Debug)] 14 | pub enum ErrorData 15 | where 16 | C: AbstractContext, 17 | K: Kind, 18 | { 19 | Simple { 20 | kind: K, 21 | message: String, 22 | backtrace: Backtrace, 23 | context: C, 24 | }, 25 | Layered { 26 | kind: K, 27 | message: String, 28 | context: C, 29 | source: AnyError, 30 | }, 31 | Wrapped { 32 | backtrace: Backtrace, 33 | inner: Box, 34 | }, 35 | } 36 | 37 | impl ErrorData 38 | where 39 | C: AbstractContext, 40 | K: Kind, 41 | { 42 | pub fn kind(&self) -> K { 43 | match self { 44 | Self::Simple { kind, .. } => *kind, 45 | Self::Layered { kind, .. } => *kind, 46 | Self::Wrapped { .. } => K::RAW_KIND, 47 | } 48 | } 49 | 50 | pub fn message(&self) -> String { 51 | match self { 52 | Self::Simple { message, .. } => message.into(), 53 | Self::Layered { message, .. } => message.into(), 54 | Self::Wrapped { inner, .. } => inner.to_string(), 55 | } 56 | } 57 | 58 | pub fn backtrace(&self) -> &Backtrace { 59 | match self { 60 | Self::Simple { backtrace, .. } => backtrace, 61 | Self::Layered { source, .. } => source.backtrace(), 62 | Self::Wrapped { backtrace, .. } => backtrace, 63 | } 64 | } 65 | 66 | pub fn context(&self, depth: ContextDepth) -> C::Iter<'_> { 67 | match self { 68 | Self::Simple { context, .. } => context.iter(), 69 | Self::Layered { 70 | context, source, .. 71 | } => match depth { 72 | ContextDepth::All => context.iter().compose(source.context(depth)), 73 | ContextDepth::Shallowest => context.iter(), 74 | }, 75 | Self::Wrapped { .. } => C::Iter::default(), 76 | } 77 | } 78 | } 79 | 80 | impl ErrorData 81 | where 82 | C: crate::context::SingletonContext, 83 | K: Kind, 84 | { 85 | pub fn value(&self) -> Option<&::ValueBorrowed> { 86 | match self { 87 | Self::Simple { context, .. } => context.value(), 88 | Self::Layered { context, .. } => context.value(), 89 | Self::Wrapped { .. } => None, 90 | } 91 | } 92 | } 93 | 94 | impl ErrorData 95 | where 96 | C: crate::context::StringContext, 97 | K: Kind, 98 | { 99 | pub fn get(&self, key: &Q) -> Option<&::ValueBorrowed> 100 | where 101 | ::KeyBorrowed: Borrow, 102 | Q: Debug + Eq + Hash + ?Sized, 103 | { 104 | match self { 105 | Self::Simple { context, .. } => context.get(key), 106 | Self::Layered { context, .. } => context.get(key), 107 | Self::Wrapped { .. } => None, 108 | } 109 | } 110 | } 111 | 112 | impl ErrorData 113 | where 114 | C: crate::context::AnyContext, 115 | K: Kind, 116 | { 117 | pub fn value_as(&self, key: &Q) -> Option<&T> 118 | where 119 | ::KeyBorrowed: Borrow, 120 | Q: Debug + Eq + Hash + ?Sized, 121 | T: Any, 122 | { 123 | match self { 124 | Self::Simple { context, .. } => context.value_as::(key), 125 | Self::Layered { context, .. } => context.value_as::(key), 126 | Self::Wrapped { .. } => None, 127 | } 128 | } 129 | } 130 | 131 | impl Display for ErrorData 132 | where 133 | C: AbstractContext, 134 | K: Kind, 135 | { 136 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 137 | match self { 138 | Self::Simple { message, .. } => write!(f, "{message}"), 139 | Self::Layered { message, .. } => write!(f, "{message}"), 140 | Self::Wrapped { inner, .. } => write!(f, "{inner}"), 141 | } 142 | } 143 | } 144 | 145 | impl Error for ErrorData 146 | where 147 | C: AbstractContext, 148 | K: Kind, 149 | { 150 | fn source(&self) -> Option<&(dyn Error + 'static)> { 151 | match self { 152 | Self::Simple { .. } => None, 153 | Self::Layered { source, .. } => Some(source), 154 | Self::Wrapped { .. } => None, 155 | } 156 | } 157 | } 158 | 159 | pub struct ErrorDataBuilder 160 | where 161 | C: AbstractContext, 162 | K: Kind, 163 | { 164 | kind: K, 165 | message: String, 166 | context: C, 167 | source: Option>, 168 | } 169 | 170 | impl ErrorDataBuilder 171 | where 172 | C: AbstractContext, 173 | K: Kind, 174 | { 175 | pub fn new() -> Self { 176 | Self { 177 | kind: K::default(), 178 | message: String::new(), 179 | context: C::default(), 180 | source: None, 181 | } 182 | } 183 | 184 | pub fn kind(mut self, kind: K) -> Self { 185 | self.kind = kind; 186 | self 187 | } 188 | 189 | pub fn message>(mut self, message: S) -> Self { 190 | self.message = message.into(); 191 | self 192 | } 193 | 194 | pub fn source(mut self, source: AnyError) -> Self { 195 | self.source = Some(source); 196 | self 197 | } 198 | 199 | pub fn build(self) -> ErrorData { 200 | match self.source { 201 | Some(source) => ErrorData::Layered { 202 | kind: self.kind, 203 | message: self.message, 204 | context: self.context, 205 | source, 206 | }, 207 | None => ErrorData::Simple { 208 | kind: self.kind, 209 | message: self.message, 210 | backtrace: Backtrace::capture(), 211 | context: self.context, 212 | }, 213 | } 214 | } 215 | } 216 | 217 | impl ErrorDataBuilder 218 | where 219 | C: Context, 220 | K: Kind, 221 | { 222 | pub fn context(mut self, key: Q, value: R) -> Self 223 | where 224 | Q: Into, 225 | R: Convertable, 226 | { 227 | self.context.insert_with::(key, value); 228 | self 229 | } 230 | } 231 | 232 | #[cfg(test)] 233 | mod tests { 234 | use crate::context::map::LiteralKeyStringMapEntry; 235 | use crate::context::{Entry, LiteralKeyStringMapContext}; 236 | use crate::kind::DefaultErrorKind; 237 | 238 | use super::*; 239 | 240 | type DefaultErrorData = ErrorData; 241 | type DefaultErrorDataBuilder = ErrorDataBuilder; 242 | 243 | #[test] 244 | fn error_data_message_succeeds() { 245 | { 246 | let data = DefaultErrorData::Simple { 247 | kind: DefaultErrorKind::Unknown, 248 | message: "simple".into(), 249 | backtrace: Backtrace::capture(), 250 | context: LiteralKeyStringMapContext::new(), 251 | }; 252 | assert_eq!(data.message(), "simple"); 253 | assert_eq!(data.to_string(), "simple"); 254 | } 255 | { 256 | let data = DefaultErrorData::Layered { 257 | kind: DefaultErrorKind::Unknown, 258 | message: "layered".into(), 259 | context: LiteralKeyStringMapContext::new(), 260 | source: AnyError::from(DefaultErrorData::Simple { 261 | kind: DefaultErrorKind::Unknown, 262 | message: "simple".into(), 263 | backtrace: Backtrace::capture(), 264 | context: LiteralKeyStringMapContext::new(), 265 | }), 266 | }; 267 | assert_eq!(data.message(), "layered"); 268 | assert_eq!(data.to_string(), "layered"); 269 | } 270 | { 271 | let data = DefaultErrorData::Wrapped { 272 | backtrace: Backtrace::capture(), 273 | inner: "wrapped".into(), 274 | }; 275 | assert_eq!(data.message(), "wrapped"); 276 | assert_eq!(data.to_string(), "wrapped"); 277 | } 278 | } 279 | 280 | #[test] 281 | fn error_data_context_succeeds() { 282 | { 283 | let data = DefaultErrorData::Simple { 284 | kind: DefaultErrorKind::Unknown, 285 | message: "simple".into(), 286 | backtrace: Backtrace::capture(), 287 | context: LiteralKeyStringMapContext::from(vec![("key", "1")]), 288 | }; 289 | 290 | let mut iter = data.context(ContextDepth::All); 291 | assert_eq!( 292 | iter.next(), 293 | Some(&LiteralKeyStringMapEntry::new("key", "1")) 294 | ); 295 | assert_eq!(iter.next(), None); 296 | 297 | let mut iter = data.context(ContextDepth::Shallowest); 298 | assert_eq!( 299 | iter.next(), 300 | Some(&LiteralKeyStringMapEntry::new("key", "1")) 301 | ); 302 | assert_eq!(iter.next(), None); 303 | } 304 | { 305 | let data = DefaultErrorData::Layered { 306 | kind: DefaultErrorKind::Unknown, 307 | message: "layered".into(), 308 | context: LiteralKeyStringMapContext::from(vec![("key2", "2")]), 309 | source: AnyError::from(DefaultErrorData::Simple { 310 | kind: DefaultErrorKind::Unknown, 311 | message: "simple".into(), 312 | backtrace: Backtrace::capture(), 313 | context: LiteralKeyStringMapContext::from(vec![("key1", "1")]), 314 | }), 315 | }; 316 | 317 | let mut iter = data.context(ContextDepth::All); 318 | assert_eq!( 319 | iter.next(), 320 | Some(&LiteralKeyStringMapEntry::new("key2", "2")) 321 | ); 322 | assert_eq!( 323 | iter.next(), 324 | Some(&LiteralKeyStringMapEntry::new("key1", "1")) 325 | ); 326 | assert_eq!(iter.next(), None); 327 | 328 | let mut iter = data.context(ContextDepth::Shallowest); 329 | assert_eq!( 330 | iter.next(), 331 | Some(&LiteralKeyStringMapEntry::new("key2", "2")) 332 | ); 333 | assert_eq!(iter.next(), None); 334 | } 335 | { 336 | let data = DefaultErrorData::Wrapped { 337 | backtrace: Backtrace::capture(), 338 | inner: "wrapped".into(), 339 | }; 340 | 341 | let mut iter = data.context(ContextDepth::All); 342 | assert_eq!(iter.next(), None); 343 | 344 | let mut iter = data.context(ContextDepth::Shallowest); 345 | assert_eq!(iter.next(), None); 346 | } 347 | } 348 | 349 | #[test] 350 | fn error_data_builder_build() { 351 | { 352 | let data = DefaultErrorDataBuilder::new() 353 | .kind(DefaultErrorKind::ValueValidation) 354 | .message("simple") 355 | .context("key", "1") 356 | .build(); 357 | assert!(matches!(data, ErrorData::Simple { .. })); 358 | assert_eq!(data.kind(), DefaultErrorKind::ValueValidation); 359 | } 360 | { 361 | let data = DefaultErrorDataBuilder::new() 362 | .message("layered") 363 | .context("key", "1") 364 | .source(AnyError::from(DefaultErrorData::Simple { 365 | kind: DefaultErrorKind::Unknown, 366 | message: "simple".into(), 367 | backtrace: Backtrace::capture(), 368 | context: LiteralKeyStringMapContext::from(vec![("key1", "1")]), 369 | })) 370 | .build(); 371 | assert_eq!(data.kind(), DefaultErrorKind::default()); 372 | assert!(matches!(data, ErrorData::Layered { .. })); 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/kind.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; 2 | use std::hash::Hash; 3 | 4 | /// The error kind used by [`AnyError`]. 5 | /// 6 | /// Each [`AnyError`] instance should have an error kind which represents the 7 | /// basic category of the error. Though almost all Rust applications never use 8 | /// dynamic error types to do anything but error reporting, an error kind 9 | /// sometimes helps control different levels of logging, auditing and other 10 | /// work in a more fine-grained way. If a [`String`]-only error reporting 11 | /// mechanism is indeed your preference, you can just stick to the predefined 12 | /// [`NoErrorKind`], which is typically for this usecase. 13 | /// 14 | /// [`AnyError`]: `crate::core::AnyError` 15 | pub trait Kind: 16 | Debug + Display + Clone + Copy + PartialEq + Eq + Hash + Default + Send + Sync + 'static 17 | { 18 | /// The kind which indicates that the [`AnyError`] instance wraps an 19 | /// external error. 20 | /// 21 | /// [`AnyError`]: `crate::core::AnyError` 22 | const RAW_KIND: Self; 23 | 24 | /// The kind which indicates that the error kind is not specified. 25 | const UNKNOWN_KIND: Self; 26 | 27 | /// Returns true if the error kind is [`Kind::RAW_KIND`]. 28 | fn is_raw(&self) -> bool { 29 | *self == Self::RAW_KIND 30 | } 31 | 32 | /// Returns true if the error kind is [`Kind::UNKNOWN_KIND`]. 33 | fn is_unknown(&self) -> bool { 34 | *self == Self::UNKNOWN_KIND 35 | } 36 | } 37 | 38 | /// A predefined error kind based on the crate author's development experience. 39 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] 40 | #[non_exhaustive] 41 | pub enum DefaultErrorKind { 42 | ValueValidation, 43 | RuleViolation, 44 | EntityAbsence, 45 | InfrastructureFailure, 46 | Raw, 47 | #[default] 48 | Unknown, 49 | } 50 | 51 | impl Display for DefaultErrorKind { 52 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 53 | let value = match self { 54 | Self::ValueValidation => "ValueValidation", 55 | Self::RuleViolation => "RuleViolation", 56 | Self::EntityAbsence => "EntityAbsence", 57 | Self::InfrastructureFailure => "InfrastructureFailure", 58 | Self::Raw => "Raw", 59 | Self::Unknown => "Unknown", 60 | }; 61 | write!(f, "{value}") 62 | } 63 | } 64 | 65 | impl Kind for DefaultErrorKind { 66 | const RAW_KIND: Self = DefaultErrorKind::Raw; 67 | 68 | const UNKNOWN_KIND: Self = DefaultErrorKind::Unknown; 69 | } 70 | 71 | /// A predefined error kind that is used when no error kind is needed. 72 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] 73 | pub enum NoErrorKind { 74 | #[default] 75 | Anything, 76 | } 77 | 78 | impl Display for NoErrorKind { 79 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 80 | write!(f, "Anything") 81 | } 82 | } 83 | 84 | impl Kind for NoErrorKind { 85 | const RAW_KIND: Self = Self::Anything; 86 | 87 | const UNKNOWN_KIND: Self = Self::Anything; 88 | } 89 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `anyerr` is a comprehensive error handling library designed to offer 2 | //! flexibility, extensibility, and an ergonomic way to handle errors in Rust 3 | //! applications. 4 | //! 5 | //! This library provides a central [`AnyError`] type that can carry arbitrary 6 | //! error information, including a custom error kind, a backtrace, contextual 7 | //! data and so on. It enables developers to create error types composing 8 | //! different levels of errors without sacrificing the ability to preserve rich 9 | //! context information. 10 | //! 11 | //! ## Key Features 12 | //! 13 | //! - **Error Composition**: Wrap and combine errors while preserving their 14 | //! original information and access the underlying errors if needed. 15 | //! - **Customizable Error Kind**: Make use of predefined error kinds offered 16 | //! by this crate or define your own error kinds by implementing the [`Kind`] 17 | //! trait. 18 | //! - **Contextual Data**: Attach rich context information to errors using 19 | //! different pluggable context types. 20 | //! - **Backtrace Support**: Automatically captures backtraces for easier 21 | //! debugging. 22 | //! - **Error Reporting**: Customize and write formated and detailed error 23 | //! messages to `stdout`, loggers and so on. 24 | //! 25 | //! ## Getting Started 26 | //! 27 | //! ### Defining a Custom Error Type 28 | //! 29 | //! [`AnyError`] is the core of this crate. It works in a way resembled to 30 | //! [`Box`], by implementing the [`Error`] trait and leverage the 31 | //! functionality of the [`Any`] trait, while it's also [`Send`] and [`Sync`], 32 | //! allowing safely accesses across multiple concurrent threads. [`AnyError`] 33 | //! is easy to get started with, though, it's not somthing like 34 | //! [`Box`] that can be used directly in your codebase, but a highly 35 | //! customizable type requiring you to make decisions about its components. 36 | //! 37 | //! An [`AnyError`] has two generic type parameters `C` and `K`, stand 38 | //! for the context storage and the error kind respectively. 39 | //! 40 | //! [`AbstractContext`] is implemented for `C`, so is [`Context`] usually but 41 | //! it's not required. With `C` implementing [`Context`], you can attach 42 | //! additional contextual data to an [`AnyError`] for better debugging. An 43 | //! example of one of the most useful contexts is 44 | //! [`LiteralKeyStringMapContext`], which holds entries of a `&'static str` 45 | //! and `String` pair structure, and stores the the [`Debug`] representation 46 | //! of values. 47 | //! 48 | //! `K` is required to implement the trait [`Kind`], specifying a general kind 49 | //! of the error. Although a structured error handling style is not preferred 50 | //! under this circumstance, an error kind enables more fine-grained logging 51 | //! and tracing or enhances experience of other aspects. [`DefaultErrorKind`] 52 | //! is a [`Kind`] provided by this crate, and the design of its variant is 53 | //! based on the author's web backend developemnt experience. 54 | //! 55 | //! Once you have chosen the components you need, you can define your custom 56 | //! error type, by supplying [`AnyError`] with the selected context and error 57 | //! kind. Here's an example: 58 | //! 59 | //! ```rust 60 | //! // Make this module accessible to your whole crate. 61 | //! mod err { 62 | //! use anyerr::AnyError as AnyErrorTemplate; 63 | //! use anyerr::context::LiteralKeyStringMapContext; 64 | //! 65 | //! pub use anyerr::{Intermediate, Overlay}; // These are helper traits. 66 | //! pub use anyerr::kind::DefaultErrorKind as ErrKind; 67 | //! pub use anyerr::Report; 68 | //! 69 | //! pub type AnyError = AnyErrorTemplate; 70 | //! pub type AnyResult = Result; 71 | //! } 72 | //! // Include this in wherever you need `AnyError`. 73 | //! use err::*; 74 | //! ``` 75 | //! 76 | //! ### Creating and Using Errors 77 | //! 78 | //! Here's how to create [`AnyError`] in your application: 79 | //! 80 | //! ```rust 81 | //! # mod err { 82 | //! # use anyerr::AnyError as AnyErrorTemplate; 83 | //! # use anyerr::context::LiteralKeyStringMapContext; 84 | //! # 85 | //! # pub use anyerr::{Intermediate, Overlay}; 86 | //! # pub use anyerr::kind::DefaultErrorKind as ErrKind; 87 | //! # pub use anyerr::Report; 88 | //! # 89 | //! # pub type AnyError = AnyErrorTemplate; 90 | //! # pub type AnyResult = Result; 91 | //! # } 92 | //! use err::*; 93 | //! 94 | //! fn fail() -> AnyResult<()> { 95 | //! // Use `AnyError::minimal()` to create a simple [`String`]-based error. 96 | //! Err(AnyError::minimal("this function always fails")) 97 | //! } 98 | //! 99 | //! fn check_positive(x: i32) -> AnyResult<()> { 100 | //! if x > 0 { 101 | //! return Ok(()); 102 | //! } 103 | //! // Use `AnyError::quick()` to quickly create an error with an error 104 | //! // message and an error kind. 105 | //! Err(AnyError::quick( 106 | //! "expects `x` to be a positive number", 107 | //! ErrKind::ValueValidation 108 | //! )) 109 | //! } 110 | //! 111 | //! fn try_add_username( 112 | //! usernames: &mut Vec, 113 | //! new_username: String 114 | //! ) -> AnyResult { 115 | //! let res = usernames.iter() 116 | //! .enumerate() 117 | //! .find(|(_, username)| **username == new_username) 118 | //! .map(|(index, _)| index); 119 | //! if let Some(index) = res { 120 | //! // Use `AnyError::builder()` to create an error with all essential 121 | //! // context you'll need. 122 | //! let err = AnyError::builder() 123 | //! .message("the username already exists") 124 | //! .kind(ErrKind::RuleViolation) 125 | //! .context("new_username", new_username) 126 | //! .context("index", index) 127 | //! .build(); 128 | //! Err(err) 129 | //! } else { 130 | //! usernames.push(new_username); 131 | //! Ok(usernames.len() - 1) 132 | //! } 133 | //! } 134 | //! 135 | //! fn parse_i32(input: &str) -> AnyResult { 136 | //! // Use `AnyError::wrap()` to wrap any other error type. 137 | //! input.parse::().map_err(AnyError::wrap) 138 | //! } 139 | //! ``` 140 | //! 141 | //! Let's take the third function `try_add_username()` as an example to 142 | //! demonstrate how we can use [`AnyError`]: 143 | //! 144 | //! ```rust 145 | //! # mod err { 146 | //! # use anyerr::AnyError as AnyErrorTemplate; 147 | //! # use anyerr::context::LiteralKeyStringMapContext; 148 | //! # 149 | //! # pub use anyerr::{Intermediate, Overlay}; 150 | //! # pub use anyerr::kind::DefaultErrorKind as ErrKind; 151 | //! # pub use anyerr::Report; 152 | //! # 153 | //! # pub type AnyError = AnyErrorTemplate; 154 | //! # pub type AnyResult = Result; 155 | //! # } 156 | //! # 157 | //! use err::*; 158 | //! # 159 | //! # fn try_add_username>( 160 | //! # usernames: &mut Vec, 161 | //! # new_username: S 162 | //! # ) -> AnyResult { 163 | //! # let new_username = new_username.into(); 164 | //! # let res = usernames.iter() 165 | //! # .enumerate() 166 | //! # .find(|(_, username)| **username == new_username) 167 | //! # .map(|(index, _)| index); 168 | //! # if let Some(index) = res { 169 | //! # // Use `AnyError::builder()` to create an error with all essential 170 | //! # // context you'll need. 171 | //! # let err = AnyError::builder() 172 | //! # .message("the username already exists") 173 | //! # .kind(ErrKind::RuleViolation) 174 | //! # .context("new_username", new_username) 175 | //! # .context("index", index) 176 | //! # .build(); 177 | //! # Err(err) 178 | //! # } else { 179 | //! # usernames.push(new_username); 180 | //! # Ok(usernames.len() - 1) 181 | //! # } 182 | //! # } 183 | //! 184 | //! fn main() { 185 | //! let mut usernames = Vec::new(); 186 | //! 187 | //! let res = try_add_username(&mut usernames, "foo").unwrap(); 188 | //! assert_eq!(res, 0); 189 | //! 190 | //! let err = try_add_username(&mut usernames, "foo").unwrap_err(); 191 | //! assert_eq!(err.to_string(), "the username already exists"); // Or `err.message()`. 192 | //! assert_eq!(err.kind(), ErrKind::RuleViolation); 193 | //! assert_eq!(err.get("new_username"), Some("\"foo\"")); 194 | //! assert_eq!(err.get("index"), Some("0")); 195 | //! } 196 | //! ``` 197 | //! 198 | //! ### Error Wrapping and Chaining 199 | //! 200 | //! The `AnyError` type supports convenient error wrapping, allowing you to 201 | //! maintain the original error while adding additional context. Methods in 202 | //! the [`Overlay`] and [`Intermediate`] helper traits provides ergonomic 203 | //! means for you to make an overlay of your existing error and attach rich 204 | //! context to it. 205 | //! 206 | //! Say we'd like to reteive a `User` entity by its username from the 207 | //! `UserRepository`. It's acknowledged that the query may fails due to a 208 | //! variety of reasons, but we don't care about the details but whether we 209 | //! could get that entity. The following codeblock demonstrates this idea. 210 | //! 211 | //! ```no_run,rust 212 | //! # mod err { 213 | //! # use anyerr::AnyError as AnyErrorTemplate; 214 | //! # use anyerr::context::LiteralKeyStringMapContext; 215 | //! # 216 | //! # pub use anyerr::{Intermediate, Overlay}; 217 | //! # pub use anyerr::kind::DefaultErrorKind as ErrKind; 218 | //! # pub use anyerr::Report; 219 | //! # 220 | //! # pub type AnyError = AnyErrorTemplate; 221 | //! # pub type AnyResult = Result; 222 | //! # } 223 | //! # 224 | //! # use std::sync::Arc; 225 | //! use err::*; 226 | //! # 227 | //! # struct User; 228 | //! # 229 | //! # struct Data; 230 | //! # 231 | //! # type DeserializationError = AnyError; 232 | //! # 233 | //! # impl TryFrom for User { 234 | //! # type Error = DeserializationError; 235 | //! # 236 | //! # fn try_from(_data: Data) -> Result { 237 | //! # Err(DeserializationError::minimal("Could not deserialize a `User` from the given data")) 238 | //! # } 239 | //! # } 240 | //! # struct Connection; 241 | //! # 242 | //! # type DbError = AnyError; 243 | //! # 244 | //! # impl Connection { 245 | //! # fn query(&self, statement: &str) -> Result { 246 | //! # Err(DbError::minimal("could not run the SQL query")) 247 | //! # } 248 | //! # } 249 | //! 250 | //! struct UserRepository { 251 | //! conn: Arc, 252 | //! } 253 | //! 254 | //! impl UserRepository { 255 | //! pub fn find_by_username(&self, username: &str) -> AnyResult { 256 | //! // Don't build SQL statements yourself in practice. 257 | //! let statement = format!("SELECT * FROM users WHERE users.username = '{username}'"); 258 | //! let data = self.conn.query(&statement) 259 | //! .overlay(("could not get a `User` due to SQL execution error", ErrKind::EntityAbsence)) 260 | //! .context("username", username) 261 | //! .context("statement", statement)?; 262 | //! let entity = User::try_from(data) 263 | //! .overlay(("could not get a `User` due to serialization error", ErrKind::EntityAbsence)) 264 | //! .context("username", username)?; 265 | //! Ok(entity) 266 | //! } 267 | //! } 268 | //! ``` 269 | //! 270 | //! ### Error Reporting 271 | //! 272 | //! You might have the experience that you wrote the code which iterated over 273 | //! the error chain and formated causes. It's pretty tedious to manually and 274 | //! repeatly write such code. Therefore, this crate does this for you by 275 | //! providing [`Report`]. [`Report`] captures your function's result and then 276 | //! you can output the error report directly to terminals, loggers or whatever. 277 | //! 278 | //! ```rust 279 | //! # mod err { 280 | //! # use anyerr::AnyError as AnyErrorTemplate; 281 | //! # use anyerr::context::LiteralKeyStringMapContext; 282 | //! # 283 | //! # pub use anyerr::{Intermediate, Overlay}; 284 | //! # pub use anyerr::kind::DefaultErrorKind as ErrKind; 285 | //! # pub use anyerr::Report; 286 | //! # 287 | //! # pub type AnyError = AnyErrorTemplate; 288 | //! # pub type AnyResult = Result; 289 | //! # } 290 | //! # 291 | //! use err::*; 292 | //! 293 | //! fn source_error() -> AnyResult<()> { 294 | //! let err = AnyError::builder() 295 | //! .message("the source error is here") 296 | //! .kind(ErrKind::InfrastructureFailure) 297 | //! .context("key1", "value1") 298 | //! .context("key2", "value2") 299 | //! .build(); 300 | //! Err(err) 301 | //! } 302 | //! 303 | //! fn intermediate_error() -> AnyResult<()> { 304 | //! source_error() 305 | //! .overlay("the intermediate error is here") 306 | //! .context("key3", "value3")?; 307 | //! Ok(()) 308 | //! } 309 | //! 310 | //! fn toplevel_error() -> AnyResult<()> { 311 | //! intermediate_error() 312 | //! .overlay("the toplevel error is here")?; 313 | //! Ok(()) 314 | //! } 315 | //! 316 | //! let report1 = Report::wrap(toplevel_error().unwrap_err()).pretty(false); 317 | //! let report2 = Report::capture(|| -> AnyResult<()> { toplevel_error() }); 318 | //! println!("Error: {report1}"); 319 | //! println!("{report2}"); 320 | //! ``` 321 | //! 322 | //! The output of `report1`: 323 | //! 324 | //! ```ignored,plain 325 | //! Error: (Unknown) the toplevel error is here: (Unknown) the intermediate error is here: (InfrastructureFailure) the source error is here [key3 = "value3", key1 = "value1", key2 = "value2"] 326 | //! ``` 327 | //! 328 | //! The output of `report2`: 329 | //! 330 | //! ```ignored,plain 331 | //! Error: 332 | //! (InfrastructureFailure) the toplevel error is here 333 | //! Caused by: 334 | //! (Unknown) the intermediate error is here 335 | //! [key3 = "value3"] 336 | //! Caused by: 337 | //! (Unknown) the source error is here 338 | //! [key1 = "value1", key2 = "value2"] 339 | //! 340 | //! Stack backtrace: 341 | //! 0: anyerr::core::data::ErrorDataBuilder::build 342 | //! at ./src/core/data.rs:210:28 343 | //! 1: anyerr::core::AnyErrorBuilder::build 344 | //! at ./src/core.rs:415:24 345 | //! 2: anyerr::source_error 346 | //! at ./src/main.rs:18:15 347 | //! 3: anyerr::intermediate_error 348 | //! at ./src/main.rs:28:5 349 | //! 4: anyerr::toplevel_error 350 | //! at ./src/main.rs:35:5 351 | //! 5: anyerr::main::{{closure}} 352 | //! at ./src/main.rs:40:43 353 | //! 6: anyerr::report::Report::capture 354 | //! at ./src/report.rs:52:15 355 | //! 7: anyerr::main 356 | //! at ./src/main.rs:40:5 357 | //! ... 358 | //! ``` 359 | //! 360 | //! Using [`Report`] in `main()`'s returning position is also allowed: 361 | //! 362 | //! ```no_run,rust 363 | //! # mod err { 364 | //! # use anyerr::AnyError as AnyErrorTemplate; 365 | //! # use anyerr::context::LiteralKeyStringMapContext; 366 | //! # 367 | //! # pub use anyerr::{Intermediate, Overlay}; 368 | //! # pub use anyerr::kind::DefaultErrorKind as ErrKind; 369 | //! # pub use anyerr::Report; 370 | //! # 371 | //! # pub type AnyError = AnyErrorTemplate; 372 | //! # pub type AnyResult = Result; 373 | //! # } 374 | //! # 375 | //! use std::process::Termination; 376 | //! use err::*; 377 | //! # 378 | //! # fn source_error() -> AnyResult<()> { 379 | //! # let err = AnyError::builder() 380 | //! # .message("the source error is here") 381 | //! # .kind(ErrKind::InfrastructureFailure) 382 | //! # .context("key1", "value1") 383 | //! # .context("key2", "value2") 384 | //! # .build(); 385 | //! # Err(err) 386 | //! # } 387 | //! # 388 | //! # fn intermediate_error() -> AnyResult<()> { 389 | //! # source_error() 390 | //! # .overlay("the intermediate error is here") 391 | //! # .context("key3", "value3")?; 392 | //! # Ok(()) 393 | //! # } 394 | //! # 395 | //! # fn toplevel_error() -> AnyResult<()> { 396 | //! # intermediate_error() 397 | //! # .overlay("the toplevel error is here")?; 398 | //! # Ok(()) 399 | //! # } 400 | //! 401 | //! fn main() -> impl Termination { 402 | //! Report::capture(|| { 403 | //! toplevel_error()?; 404 | //! Ok(()) 405 | //! }) 406 | //! } 407 | //! ``` 408 | //! 409 | //! For more information about error reporting customization, see the 410 | //! documentations of [`Report`]. 411 | //! 412 | //! ## Advanced Usage 413 | //! 414 | //! ### Different Context Types 415 | //! 416 | //! This crate allows using different context types, such as 417 | //! [`SingletonContext`], [`StringContext`], [`AnyContext`] or the ones you 418 | //! developed by yourself, depending on how you want to manage and retrieve 419 | //! additional information from your errors. It's even viable that you don't 420 | //! want your error type to carry a context storage, through the [`NoContext`] 421 | //! trait. Each context type offers unique capabilities for structuring error 422 | //! metadata. 423 | //! 424 | //! For more information, refer to the types in the [`crate::context`] module. 425 | //! 426 | //! ### Usage without an Error Kind 427 | //! 428 | //! For some reasons, you may not want each error to have an error kind. This 429 | //! crate offers you [`NoErrorKind`], which actually has only one variant as 430 | //! its default value. By selecting [`NoErrorKind`], you no longer need to 431 | //! do anything with error kinds. 432 | //! 433 | //! [`Any`]: std::any::Any 434 | //! [`AbstractContext`]: crate::context::AbstractContext 435 | //! [`Context`]: crate::context::Context 436 | //! [`Debug`]: std::fmt::Debug 437 | //! [`DefaultErrorKind`]: crate::kind::DefaultErrorKind 438 | //! [`Error`]: std::error::Error 439 | //! [`Kind`]: crate::kind::Kind 440 | //! [`LiteralKeyStringMapContext`]: crate::context::LiteralKeyStringMapContext 441 | //! [`NoContext`]: crate::context::NoContext 442 | //! [`NoErrorKind`]: crate::kind::NoErrorKind 443 | //! [`SingletonContext`]: crate::context::SingletonContext 444 | //! [`StringContext`]: crate::context::StringContext 445 | //! [`AnyContext`]: crate::context::AnyContext 446 | 447 | pub mod context; 448 | pub mod converter; 449 | pub mod core; 450 | pub mod kind; 451 | pub mod overlay; 452 | pub mod report; 453 | 454 | pub use core::AnyError; 455 | pub use overlay::{Intermediate, Overlay}; 456 | pub use report::Report; 457 | -------------------------------------------------------------------------------- /src/overlay.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod result; 3 | 4 | /// The type that can wrap others of the same type recursively with additional 5 | /// data attached. 6 | /// 7 | /// A top-level error that contains another one, forming a multi-layer 8 | /// structure, is resembled to an overlay over the inner error, which is where 9 | /// the inspiration of the name [`Overlay`] comes from. Implemented for 10 | /// error-related types, the [`Overlay`] allows add context information and 11 | /// wrap errors conveniently. 12 | /// 13 | /// For ergonomic consideration, the whole error wrapping procedure of adding 14 | /// a new message and kind and attaching more context is split to multiple 15 | /// steps in a builder-like fashion. The follow example demostrate how the 16 | /// [`Overlay`] and other helper traits make error handling more easily. 17 | /// 18 | /// You may also need to go through the [`Intermediate`] trait for advanced 19 | /// usage. 20 | /// 21 | /// # Example 22 | /// 23 | /// ```rust 24 | /// # use anyerr::{AnyError as AnyErrorTemplate, Intermediate, Overlay}; 25 | /// # use anyerr::kind::DefaultErrorKind; 26 | /// # use anyerr::context::LiteralKeyStringMapContext; 27 | /// type AnyError = AnyErrorTemplate; 28 | /// 29 | /// fn parse_text(text: &str) -> Result { 30 | /// text.parse::().map_err(AnyError::wrap) 31 | /// } 32 | /// 33 | /// fn try_increment(text: &str) -> Result { 34 | /// let x = parse_text(text) 35 | /// .overlay("failed to parse the given text to `u32`") 36 | /// .context("text", text)?; 37 | /// Ok(x + 1) 38 | /// } 39 | /// 40 | /// assert_eq!(try_increment("0").unwrap(), 1); 41 | /// assert!(try_increment("-1").is_err()); 42 | /// ``` 43 | pub trait Overlay: Sized { 44 | /// The type of the final result of the wrapping procedure. 45 | type Output: Overlay; 46 | 47 | /// The intermediate type used to add more context to the result. 48 | type Intermediate: Intermediate; 49 | 50 | /// Starts the wrapping procedure by adding some most essential data, such 51 | /// as the error messages and the error kind. 52 | /// 53 | /// Different types implementing [`Overlay`] may accept different sorts of 54 | /// values as this method's input, and these are determined by whether 55 | /// those values have the [`Applicable`] trait implemented. Refer to 56 | /// implementors of the [`Applicable`] trait for more information. 57 | fn overlay(self, value: V) -> Self::Intermediate 58 | where 59 | V: Applicable, 60 | { 61 | value.apply(self) 62 | } 63 | } 64 | 65 | /// The intermediate type helps attach additional context to the resulting 66 | /// overlay. 67 | /// 68 | /// Each implementors of the [`Intermediate`] are produced by the [`Overlay`] 69 | /// trait, and is only used as a temporary builder to add some optional context 70 | /// to the final output. 71 | pub trait Intermediate: Sized { 72 | /// The type of the final result of the wrapping procedure. 73 | type Output: Overlay; 74 | 75 | /// Attachs additional context to the final output. 76 | /// 77 | /// Different types implementing [`Intermediate`] may accept different 78 | /// sorts of values as this method's input, and these are determined by 79 | /// whether those values have the [`Applicable`] trait implemented. Refer 80 | /// to implementors of the [`Applicable`] trait for more information. 81 | fn context(self, key: Q, value: R) -> Self 82 | where 83 | (Q, R): Applicable, 84 | { 85 | (key, value).apply(self) 86 | } 87 | 88 | /// Instantiates the output with all provided data. 89 | fn build(self) -> Self::Output; 90 | } 91 | 92 | /// The helper which determines whether a type can be applied to the target. 93 | pub trait Applicable { 94 | /// The type of the result produced by applying the value to the target. 95 | type Output; 96 | 97 | /// Makes the `target` do something with the `self`. 98 | fn apply(self, target: T) -> Self::Output; 99 | } 100 | -------------------------------------------------------------------------------- /src/overlay/error.rs: -------------------------------------------------------------------------------- 1 | use crate::context::{AbstractContext, Context}; 2 | use crate::converter::Convertable; 3 | use crate::core::{AnyError, AnyErrorBuilder}; 4 | use crate::kind::Kind; 5 | use crate::overlay::{Applicable, Intermediate, Overlay}; 6 | 7 | impl Overlay for AnyError 8 | where 9 | C: AbstractContext, 10 | K: Kind, 11 | { 12 | type Output = Self; 13 | 14 | type Intermediate = IntermediateAnyError; 15 | } 16 | 17 | /// The final [`AnyError`] in a intermediate state. 18 | pub struct IntermediateAnyError 19 | where 20 | C: AbstractContext, 21 | K: Kind, 22 | { 23 | builder: AnyErrorBuilder, 24 | } 25 | 26 | impl From> for IntermediateAnyError 27 | where 28 | C: AbstractContext, 29 | K: Kind, 30 | { 31 | fn from(builder: AnyErrorBuilder) -> Self { 32 | Self { builder } 33 | } 34 | } 35 | 36 | impl From> for AnyError 37 | where 38 | C: AbstractContext, 39 | K: Kind, 40 | { 41 | fn from(value: IntermediateAnyError) -> Self { 42 | value.builder.build() 43 | } 44 | } 45 | 46 | impl Intermediate for IntermediateAnyError 47 | where 48 | C: AbstractContext, 49 | K: Kind, 50 | { 51 | type Output = AnyError; 52 | 53 | fn build(self) -> Self::Output { 54 | self.into() 55 | } 56 | } 57 | 58 | impl Applicable> for String 59 | where 60 | C: AbstractContext, 61 | K: Kind, 62 | { 63 | type Output = IntermediateAnyError; 64 | 65 | /// Makes a new [`IntermediateAnyError`] which wraps `target` and sets 66 | /// its error message to `self`. 67 | fn apply(self, target: AnyError) -> Self::Output { 68 | AnyError::builder().message(self).source(target).into() 69 | } 70 | } 71 | 72 | impl Applicable> for &str 73 | where 74 | C: AbstractContext, 75 | K: Kind, 76 | { 77 | type Output = IntermediateAnyError; 78 | 79 | /// Makes a new [`IntermediateAnyError`] which wraps `target` and sets 80 | /// its error message to `self`. 81 | fn apply(self, target: AnyError) -> Self::Output { 82 | AnyError::builder().message(self).source(target).into() 83 | } 84 | } 85 | 86 | impl Applicable> for (S, K) 87 | where 88 | C: AbstractContext, 89 | K: Kind, 90 | S: Into, 91 | { 92 | type Output = IntermediateAnyError; 93 | 94 | /// Makes a new [`IntermediateAnyError`] which wraps `target` and sets 95 | /// its error message and error kind to `self`'s corresponding components. 96 | fn apply(self, target: AnyError) -> Self::Output { 97 | AnyError::builder() 98 | .message(self.0) 99 | .kind(self.1) 100 | .source(target) 101 | .into() 102 | } 103 | } 104 | 105 | impl Applicable> for (Q, R) 106 | where 107 | C: Context, 108 | K: Kind, 109 | Q: Into, 110 | R: Convertable, 111 | { 112 | type Output = IntermediateAnyError; 113 | 114 | /// Add context information represented as a key-value pairs to `target` 115 | /// using `self`'s components. 116 | fn apply(self, target: IntermediateAnyError) -> Self::Output { 117 | target.builder.context(self.0, self.1).into() 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use std::error::Error; 124 | 125 | use crate::context::LiteralKeyStringMapContext; 126 | use crate::kind::DefaultErrorKind; 127 | 128 | use super::*; 129 | 130 | type ErrKind = DefaultErrorKind; 131 | type DefaultAnyError = AnyError; 132 | 133 | #[test] 134 | fn any_error_overlay_succeeds_when_message_is_given() { 135 | let source = DefaultAnyError::minimal("source error"); 136 | let err = source.overlay(String::from("wrapper error")).build(); 137 | assert_eq!(err.to_string(), "wrapper error"); 138 | assert_eq!(err.source().unwrap().to_string(), "source error"); 139 | 140 | let source = DefaultAnyError::minimal("source error"); 141 | let err = source.overlay("wrapper error").build(); 142 | assert_eq!(err.to_string(), "wrapper error"); 143 | assert_eq!(err.source().unwrap().to_string(), "source error"); 144 | } 145 | 146 | #[test] 147 | fn any_error_overlay_succeeds_when_message_and_kind_are_given() { 148 | let source = DefaultAnyError::minimal("source error"); 149 | let err = source 150 | .overlay(("wrapper error", ErrKind::ValueValidation)) 151 | .build(); 152 | assert_eq!(err.to_string(), "wrapper error"); 153 | assert_eq!(err.kind(), ErrKind::ValueValidation); 154 | assert_eq!(err.source().unwrap().to_string(), "source error"); 155 | } 156 | 157 | #[test] 158 | fn intermediate_any_error_context_succeeds() { 159 | let source = DefaultAnyError::minimal("source error"); 160 | let err = source 161 | .overlay("wrapper error") 162 | .context("key", "value") 163 | .build(); 164 | assert_eq!(err.to_string(), "wrapper error"); 165 | assert_eq!(err.get("key"), Some("\"value\"")); 166 | } 167 | 168 | #[test] 169 | fn intermediate_any_error_into_any_error_succeeds_with_try_operator() { 170 | fn source_error_func() -> Result<(), DefaultAnyError> { 171 | Err(AnyError::minimal("source error")) 172 | } 173 | fn wrapper_error_func() -> Result<(), DefaultAnyError> { 174 | source_error_func().map_err(|err| err.overlay("wrapper error"))?; 175 | Ok(()) 176 | } 177 | let err = wrapper_error_func().unwrap_err(); 178 | assert_eq!(err.to_string(), "wrapper error"); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/overlay/result.rs: -------------------------------------------------------------------------------- 1 | use crate::context::{AbstractContext, Context}; 2 | use crate::converter::Convertable; 3 | use crate::core::AnyError; 4 | use crate::kind::Kind; 5 | use crate::overlay::error::IntermediateAnyError; 6 | use crate::overlay::{Applicable, Intermediate, Overlay}; 7 | 8 | impl Overlay for Result> 9 | where 10 | C: AbstractContext, 11 | K: Kind, 12 | { 13 | type Output = Result>; 14 | 15 | type Intermediate = Result>; 16 | } 17 | 18 | impl Intermediate for Result> 19 | where 20 | C: AbstractContext, 21 | K: Kind, 22 | { 23 | type Output = Result>; 24 | 25 | fn build(self) -> Self::Output { 26 | self.map_err(Into::into) 27 | } 28 | } 29 | 30 | impl Applicable>> for String 31 | where 32 | C: AbstractContext, 33 | K: Kind, 34 | { 35 | type Output = Result>; 36 | 37 | /// Delegates the parameters to [`AnyError`]'s implementation. 38 | fn apply(self, target: Result>) -> Self::Output { 39 | target.map_err(|err| err.overlay(self)) 40 | } 41 | } 42 | 43 | impl Applicable>> for &str 44 | where 45 | C: AbstractContext, 46 | K: Kind, 47 | { 48 | type Output = Result>; 49 | 50 | /// Delegates the parameters to [`AnyError`]'s implementation. 51 | fn apply(self, target: Result>) -> Self::Output { 52 | target.map_err(|err| err.overlay(self)) 53 | } 54 | } 55 | 56 | impl Applicable>> for (S, K) 57 | where 58 | C: AbstractContext, 59 | K: Kind, 60 | S: Into, 61 | { 62 | type Output = Result>; 63 | 64 | /// Delegates the parameters to [`AnyError`]'s implementation. 65 | fn apply(self, target: Result>) -> Self::Output { 66 | target.map_err(|err| err.overlay(self)) 67 | } 68 | } 69 | 70 | impl Applicable>> for (Q, R) 71 | where 72 | C: Context, 73 | K: Kind, 74 | Q: Into, 75 | R: Convertable, 76 | { 77 | type Output = Result>; 78 | 79 | /// Delegates the parameters to [`IntermediateAnyError`]'s implementation. 80 | fn apply(self, target: Result>) -> Self::Output { 81 | target.map_err(|err| err.context(self.0, self.1)) 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use std::error::Error; 88 | 89 | use crate::context::LiteralKeyStringMapContext; 90 | use crate::kind::DefaultErrorKind; 91 | 92 | use super::*; 93 | 94 | type ErrKind = DefaultErrorKind; 95 | type DefaultAnyError = AnyError; 96 | 97 | #[test] 98 | fn result_overlay_succeeds_when_message_is_given() { 99 | let source = Err::<(), _>(DefaultAnyError::minimal("source error")); 100 | let res = source.overlay(String::from("wrapper error")).build(); 101 | let err = res.unwrap_err(); 102 | assert_eq!(err.to_string(), "wrapper error"); 103 | assert_eq!(err.source().unwrap().to_string(), "source error"); 104 | 105 | let source = Err::<(), _>(DefaultAnyError::minimal("source error")); 106 | let res = source.overlay("wrapper error").build(); 107 | let err = res.unwrap_err(); 108 | assert_eq!(err.to_string(), "wrapper error"); 109 | assert_eq!(err.source().unwrap().to_string(), "source error"); 110 | } 111 | 112 | #[test] 113 | fn result_overlay_succeeds_when_message_and_kind_are_given() { 114 | let source = Err::<(), _>(DefaultAnyError::minimal("source error")); 115 | let res = source 116 | .overlay(("wrapper error", ErrKind::ValueValidation)) 117 | .build(); 118 | let err = res.unwrap_err(); 119 | assert_eq!(err.to_string(), "wrapper error"); 120 | assert_eq!(err.kind(), ErrKind::ValueValidation); 121 | assert_eq!(err.source().unwrap().to_string(), "source error"); 122 | } 123 | 124 | #[test] 125 | fn result_overlay_succeeds_when_result_is_ok() { 126 | let source = Ok::(1); 127 | let res = source.overlay("no error").build(); 128 | assert_eq!(res.unwrap(), 1); 129 | } 130 | 131 | #[test] 132 | fn intermediate_result_context_succeeds() { 133 | let source = Err::<(), _>(DefaultAnyError::minimal("source error")); 134 | let res = source 135 | .overlay(String::from("wrapper error")) 136 | .context("i32", 1) 137 | .context("&str", "value") 138 | .build(); 139 | let err = res.unwrap_err(); 140 | assert_eq!(err.to_string(), "wrapper error"); 141 | assert_eq!(err.get("i32"), Some("1")); 142 | assert_eq!(err.get("&str"), Some("\"value\"")); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/report.rs: -------------------------------------------------------------------------------- 1 | mod inner; 2 | 3 | use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; 4 | use std::process::{ExitCode, Termination}; 5 | 6 | use crate::context::AbstractContext; 7 | use crate::kind::Kind; 8 | use crate::AnyError; 9 | 10 | use inner::ReportInner; 11 | 12 | /// An error reporter which displays data carried by an [`AnyError`]. 13 | /// 14 | /// [`Report`] captures your function's result, such as [`Result<(), AnyError>`], 15 | /// and displays the error message and other information if an error occurred. 16 | /// It can also be used as `main()`'s returned value, handling the process's 17 | /// termination by implementing the [`Termination`] trait. 18 | pub struct Report(ReportVariant) 19 | where 20 | C: AbstractContext, 21 | K: Kind; 22 | 23 | impl Report 24 | where 25 | C: AbstractContext, 26 | K: Kind, 27 | { 28 | /// Creates a [`Report`] with the given error inside. 29 | pub fn wrap(error: AnyError) -> Self { 30 | error.into() 31 | } 32 | 33 | /// Captures the result of a given function and creates a [`Report`]. 34 | /// 35 | /// # Example 36 | /// 37 | /// ```no_run,rust 38 | /// # use std::process::Termination; 39 | /// # use anyerr::AnyError as AnyErrorTemplate; 40 | /// # use anyerr::kind::DefaultErrorKind; 41 | /// # use anyerr::context::LiteralKeyStringMapContext; 42 | /// # use anyerr::report::Report; 43 | /// type AnyError = AnyErrorTemplate; 44 | /// 45 | /// fn main() -> impl Termination { 46 | /// Report::capture(|| -> Result<(), AnyError> { 47 | /// Err(AnyError::minimal("an error occurred")) 48 | /// }) 49 | /// } 50 | /// ``` 51 | pub fn capture(func: impl FnOnce() -> Result<(), AnyError>) -> Self { 52 | match func() { 53 | Ok(_) => Self(ReportVariant::Success), 54 | Err(err) => err.into(), 55 | } 56 | } 57 | 58 | /// Prints a pretty error report if `pretty` is `true`, otherwise prints 59 | /// a compact error report in one line. 60 | pub fn pretty(self, pretty: bool) -> Self { 61 | match self.0 { 62 | ReportVariant::Failure(report) => report.pretty(pretty).into(), 63 | v => Self(v), 64 | } 65 | } 66 | 67 | /// Prints error kinds if `kind` is `true`. 68 | pub fn kind(self, kind: bool) -> Self { 69 | match self.0 { 70 | ReportVariant::Failure(report) => report.kind(kind).into(), 71 | v => Self(v), 72 | } 73 | } 74 | 75 | /// Prints the backtrace if `backtrace` is `true`. 76 | pub fn backtrace(self, backtrace: bool) -> Self { 77 | match self.0 { 78 | ReportVariant::Failure(report) => report.backtrace(backtrace).into(), 79 | v => Self(v), 80 | } 81 | } 82 | 83 | /// Prints the attached context if `context` is `true`. 84 | pub fn context(self, context: bool) -> Self { 85 | match self.0 { 86 | ReportVariant::Failure(report) => report.context(context).into(), 87 | v => Self(v), 88 | } 89 | } 90 | 91 | fn render(&self, f: &mut Formatter<'_>) -> FmtResult { 92 | match &self.0 { 93 | ReportVariant::Failure(report) => report.render(f), 94 | _ => Ok(()), 95 | } 96 | } 97 | } 98 | 99 | impl From> for Report 100 | where 101 | C: AbstractContext, 102 | K: Kind, 103 | { 104 | fn from(value: ReportInner) -> Self { 105 | Self(ReportVariant::Failure(value)) 106 | } 107 | } 108 | 109 | impl From> for Report 110 | where 111 | C: AbstractContext, 112 | K: Kind, 113 | { 114 | fn from(error: AnyError) -> Self { 115 | Self(ReportVariant::Failure(ReportInner::from(error))) 116 | } 117 | } 118 | 119 | impl Termination for Report 120 | where 121 | C: AbstractContext, 122 | K: Kind, 123 | { 124 | fn report(self) -> ExitCode { 125 | match self.0 { 126 | ReportVariant::Success => ExitCode::SUCCESS, 127 | ReportVariant::Failure(report) => { 128 | eprintln!("{report}"); 129 | ExitCode::FAILURE 130 | } 131 | } 132 | } 133 | } 134 | 135 | impl Debug for Report 136 | where 137 | C: AbstractContext, 138 | K: Kind, 139 | { 140 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 141 | self.render(f) 142 | } 143 | } 144 | 145 | impl Display for Report 146 | where 147 | C: AbstractContext, 148 | K: Kind, 149 | { 150 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 151 | self.render(f) 152 | } 153 | } 154 | 155 | enum ReportVariant 156 | where 157 | C: AbstractContext, 158 | K: Kind, 159 | { 160 | Success, 161 | Failure(ReportInner), 162 | } 163 | -------------------------------------------------------------------------------- /src/report/inner.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; 3 | 4 | use crate::context::AbstractContext; 5 | use crate::core::ContextDepth; 6 | use crate::kind::Kind; 7 | use crate::AnyError; 8 | 9 | pub struct ReportInner 10 | where 11 | C: AbstractContext, 12 | K: Kind, 13 | { 14 | error: AnyError, 15 | pretty: bool, 16 | kind: bool, 17 | backtrace: bool, 18 | context: bool, 19 | } 20 | 21 | impl ReportInner 22 | where 23 | C: AbstractContext, 24 | K: Kind, 25 | { 26 | pub fn pretty(self, pretty: bool) -> Self { 27 | Self { pretty, ..self } 28 | } 29 | 30 | pub fn kind(self, kind: bool) -> Self { 31 | Self { kind, ..self } 32 | } 33 | 34 | pub fn backtrace(self, backtrace: bool) -> Self { 35 | Self { backtrace, ..self } 36 | } 37 | 38 | pub fn context(self, context: bool) -> Self { 39 | Self { context, ..self } 40 | } 41 | 42 | pub fn render(&self, f: &mut Formatter<'_>) -> FmtResult { 43 | if self.pretty { 44 | self.render_pretty_report(f) 45 | } else { 46 | self.render_compact_report(f) 47 | } 48 | } 49 | 50 | fn render_pretty_report(&self, f: &mut Formatter<'_>) -> FmtResult { 51 | self.render_single_pretty_error(f, "Error:", &self.error)?; 52 | 53 | let mut source = self.error.source(); 54 | loop { 55 | let error = source.and_then(|error| error.downcast_ref::>()); 56 | if let Some(error) = error { 57 | self.render_single_pretty_error(f, "Caused by:", error)?; 58 | source = error.source(); 59 | } else { 60 | break; 61 | } 62 | } 63 | 64 | self.render_backtrace(f)?; 65 | Ok(()) 66 | } 67 | 68 | fn render_single_pretty_error( 69 | &self, 70 | f: &mut Formatter<'_>, 71 | prefix: &str, 72 | error: &AnyError, 73 | ) -> FmtResult { 74 | writeln!(f, "{}", prefix)?; 75 | self.render_pretty_single_error_message(f, error)?; 76 | self.render_pretty_one_line_context(f, error)?; 77 | Ok(()) 78 | } 79 | 80 | fn render_pretty_single_error_message( 81 | &self, 82 | f: &mut Formatter<'_>, 83 | error: &AnyError, 84 | ) -> FmtResult { 85 | write!(f, " ")?; 86 | if self.kind { 87 | writeln!(f, "({}) {}", error.kind(), error) 88 | } else { 89 | writeln!(f, "{}", error) 90 | } 91 | } 92 | 93 | fn render_pretty_one_line_context( 94 | &self, 95 | f: &mut Formatter<'_>, 96 | error: &AnyError, 97 | ) -> FmtResult { 98 | if !self.context { 99 | return Ok(()); 100 | } 101 | let mut context = error.context(ContextDepth::Shallowest).peekable(); 102 | if context.peek().is_none() { 103 | return Ok(()); 104 | } 105 | write!(f, " [")?; 106 | let mut first = true; 107 | for entry in context { 108 | if first { 109 | write!(f, "{entry}")?; 110 | first = false; 111 | } else { 112 | write!(f, ", {entry}")?; 113 | } 114 | } 115 | writeln!(f, "]")?; 116 | Ok(()) 117 | } 118 | 119 | fn render_backtrace(&self, f: &mut Formatter<'_>) -> FmtResult { 120 | if !self.backtrace { 121 | return Ok(()); 122 | } 123 | 124 | writeln!(f)?; 125 | writeln!(f, "Stack backtrace:")?; 126 | writeln!(f, "{}", self.error.backtrace())?; 127 | Ok(()) 128 | } 129 | 130 | fn render_compact_report(&self, f: &mut Formatter<'_>) -> FmtResult { 131 | self.render_one_line_message(f)?; 132 | self.render_compact_one_line_context(f, &self.error)?; 133 | Ok(()) 134 | } 135 | 136 | fn render_one_line_message(&self, f: &mut Formatter<'_>) -> FmtResult { 137 | self.render_compact_single_error_message(f, &self.error)?; 138 | let mut source = self.error.source(); 139 | loop { 140 | let error = source.and_then(|error| error.downcast_ref::>()); 141 | if let Some(error) = error { 142 | write!(f, ": ")?; 143 | self.render_compact_single_error_message(f, error)?; 144 | source = error.source(); 145 | } else { 146 | break; 147 | } 148 | } 149 | Ok(()) 150 | } 151 | 152 | fn render_compact_one_line_context( 153 | &self, 154 | f: &mut Formatter<'_>, 155 | error: &AnyError, 156 | ) -> FmtResult { 157 | if !self.context { 158 | return Ok(()); 159 | } 160 | let mut context = error.context(ContextDepth::All).peekable(); 161 | if context.peek().is_none() { 162 | return Ok(()); 163 | } 164 | write!(f, " ")?; 165 | write!(f, "[")?; 166 | let mut first = true; 167 | for entry in context { 168 | if first { 169 | write!(f, "{entry}")?; 170 | first = false; 171 | } else { 172 | write!(f, ", {entry}")?; 173 | } 174 | } 175 | write!(f, "]")?; 176 | Ok(()) 177 | } 178 | 179 | fn render_compact_single_error_message( 180 | &self, 181 | f: &mut Formatter<'_>, 182 | error: &AnyError, 183 | ) -> FmtResult { 184 | if self.kind { 185 | write!(f, "({}) {}", error.kind(), error) 186 | } else { 187 | write!(f, "{}", error) 188 | } 189 | } 190 | } 191 | 192 | impl From> for ReportInner 193 | where 194 | C: AbstractContext, 195 | K: Kind, 196 | { 197 | fn from(error: AnyError) -> Self { 198 | Self { 199 | error, 200 | pretty: true, 201 | kind: true, 202 | backtrace: true, 203 | context: true, 204 | } 205 | } 206 | } 207 | 208 | impl From> for AnyError 209 | where 210 | C: AbstractContext, 211 | K: Kind, 212 | { 213 | fn from(report: ReportInner) -> Self { 214 | report.error 215 | } 216 | } 217 | 218 | impl Debug for ReportInner 219 | where 220 | C: AbstractContext, 221 | K: Kind, 222 | { 223 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 224 | self.render(f) 225 | } 226 | } 227 | 228 | impl Display for ReportInner 229 | where 230 | C: AbstractContext, 231 | K: Kind, 232 | { 233 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 234 | self.render(f) 235 | } 236 | } 237 | 238 | #[cfg(test)] 239 | mod tests { 240 | use crate::context::StringKeyStringMapContext; 241 | use crate::kind::DefaultErrorKind as ErrKind; 242 | use crate::{Intermediate, Overlay}; 243 | 244 | use super::*; 245 | 246 | type TestError = AnyError; 247 | 248 | #[test] 249 | fn report_inner_display_succeeds_when_pretty_is_true() { 250 | let report = ReportInner::from(new_test_error()) 251 | .pretty(true) 252 | .kind(true) 253 | .backtrace(false) 254 | .context(true); 255 | let mut expected = String::new(); 256 | expected.push_str("Error:\n"); 257 | expected.push_str(" (Unknown) error3\n"); 258 | expected.push_str(" [key3.1 = \"value\", key3.2 = \"value\"]\n"); 259 | expected.push_str("Caused by:\n"); 260 | expected.push_str(" (RuleViolation) error2\n"); 261 | expected.push_str(" [key2.1 = \"value\"]\n"); 262 | expected.push_str("Caused by:\n"); 263 | expected.push_str(" (ValueValidation) error1\n"); 264 | assert_eq!(report.to_string(), expected); 265 | 266 | let report = ReportInner::from(new_test_error()) 267 | .pretty(true) 268 | .kind(false) 269 | .backtrace(false) 270 | .context(true); 271 | let mut expected = String::new(); 272 | expected.push_str("Error:\n"); 273 | expected.push_str(" error3\n"); 274 | expected.push_str(" [key3.1 = \"value\", key3.2 = \"value\"]\n"); 275 | expected.push_str("Caused by:\n"); 276 | expected.push_str(" error2\n"); 277 | expected.push_str(" [key2.1 = \"value\"]\n"); 278 | expected.push_str("Caused by:\n"); 279 | expected.push_str(" error1\n"); 280 | assert_eq!(report.to_string(), expected); 281 | 282 | let report = ReportInner::from(new_test_error()) 283 | .pretty(true) 284 | .kind(true) 285 | .backtrace(false) 286 | .context(false); 287 | let mut expected = String::new(); 288 | expected.push_str("Error:\n"); 289 | expected.push_str(" (Unknown) error3\n"); 290 | expected.push_str("Caused by:\n"); 291 | expected.push_str(" (RuleViolation) error2\n"); 292 | expected.push_str("Caused by:\n"); 293 | expected.push_str(" (ValueValidation) error1\n"); 294 | assert_eq!(report.to_string(), expected); 295 | } 296 | 297 | #[test] 298 | fn report_inner_display_succeeds_when_pretty_is_false() { 299 | let report = ReportInner::from(new_test_error()).pretty(false); 300 | assert_eq!(report.to_string(), "(Unknown) error3: (RuleViolation) error2: (ValueValidation) error1 [key3.1 = \"value\", key3.2 = \"value\", key2.1 = \"value\"]"); 301 | 302 | let report = ReportInner::from(new_test_error()) 303 | .pretty(false) 304 | .kind(false); 305 | assert_eq!( 306 | report.to_string(), 307 | "error3: error2: error1 [key3.1 = \"value\", key3.2 = \"value\", key2.1 = \"value\"]" 308 | ); 309 | 310 | let report = ReportInner::from(new_test_error()) 311 | .pretty(false) 312 | .context(false); 313 | assert_eq!( 314 | report.to_string(), 315 | "(Unknown) error3: (RuleViolation) error2: (ValueValidation) error1" 316 | ); 317 | } 318 | 319 | fn new_test_error() -> TestError { 320 | let error1 = TestError::quick("error1", ErrKind::ValueValidation); 321 | let error2 = error1 322 | .overlay(("error2", ErrKind::RuleViolation)) 323 | .context("key2.1", "value") 324 | .build(); 325 | let error3 = error2 326 | .overlay("error3") 327 | .context("key3.1", "value") 328 | .context("key3.2", "value") 329 | .build(); 330 | error3 331 | } 332 | } 333 | --------------------------------------------------------------------------------