├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── error.rs └── lib.rs └── tests └── tests.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | rust: [1.36.0, stable, beta] 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - uses: actions-rs/toolchain@v1 24 | with: 25 | toolchain: ${{ matrix.rust }} 26 | default: true 27 | override: true 28 | 29 | - name: test 30 | uses: actions-rs/cargo@v1 31 | with: 32 | command: test 33 | 34 | - name: test nostd 35 | uses: actions-rs/cargo@v1 36 | with: 37 | command: test 38 | args: --no-default-features 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | custom_error.iml 5 | .idea/ 6 | launch.json -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom_error" 3 | description = "Define custom errors without boilerplate using the custom_error! macro." 4 | version = "1.9.2" 5 | authors = ["lovasoa"] 6 | license = "BSD-2-Clause" 7 | homepage = "https://github.com/lovasoa/custom_error" 8 | repository = "https://github.com/lovasoa/custom_error" 9 | readme = "README.md" 10 | categories = ["rust-patterns", "development-tools", "encoding"] 11 | keywords = ["error", "failure", "macro"] 12 | documentation = "https://docs.rs/custom_error" 13 | edition='2018' 14 | 15 | [dependencies] 16 | 17 | [badges] 18 | maintenance = { status = "passively-maintained" } 19 | 20 | [features] 21 | default = ["std"] 22 | # Disable std for use in no_std context. 23 | std = [] 24 | # Enable allocator_api and try_reserve, to define Error trait 25 | # for unstable `AllocError` and `TryReserveError` in no-std 26 | unstable = [] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Ophir LOJKINE 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust custom error 2 | 3 | [![Rust](https://github.com/lovasoa/custom_error/actions/workflows/rust.yml/badge.svg)](https://github.com/lovasoa/custom_error/actions/workflows/rust.yml) 4 | [![Crates.io](https://img.shields.io/crates/v/custom_error)](https://crates.io/crates/custom_error) 5 | [![docs.rs](https://img.shields.io/docsrs/custom_error)](https://docs.rs/custom_error) 6 | 7 | This crate contains a macro that should make it easier 8 | to define custom errors without having to write a lot of boilerplate code. 9 | 10 | The `custom_error!` macro included in this crate takes a type name 11 | and a list of possible errors and generates a rust enumeration for all the cases, 12 | together with the required `impl` blocks implementing `std::error::Error` 13 | and `std::fmt::Display`. 14 | 15 | If you only have a single case for an error you can also generate a struct 16 | instead of an enum. 17 | 18 | You can now write: 19 | 20 | ```rust 21 | extern crate custom_error; 22 | use custom_error::custom_error; 23 | 24 | // Note the use of braces rather than parentheses. 25 | custom_error!{MyError 26 | Unknown{code:u8} = "unknown error with code {code}.", 27 | Err41 = "Sit by a lake" 28 | } 29 | ``` 30 | 31 | instead of 32 | 33 | ```rust 34 | #[derive(Debug)] 35 | enum MyError { 36 | Unknown { code: u8 }, 37 | Err41, 38 | } 39 | 40 | impl std::error::Error for MyError {} 41 | 42 | impl std::fmt::Display for MyError { 43 | fn fmt(&self, f: &mut std::fmt::Formatter) 44 | -> std::fmt::Result { 45 | match self { 46 | MyError::Unknown { code } => write!(f, "unknown error with code {}." , code), 47 | MyError::Err41 => write!(f, "Sit by a lake") 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | If you only have a single error case you can also generate a struct: 54 | ```rust 55 | extern crate custom_error; 56 | use custom_error::custom_error; 57 | 58 | custom_error!{MyError{code:u8} = "error with code {code}."} 59 | ``` 60 | 61 | ## Simple error 62 | 63 | To define a simple error, you only have to indicate three things: 64 | * the name of your custom error type, 65 | * the name of the different error cases, 66 | * a human-readable description for each case. 67 | 68 | ```rust 69 | extern crate custom_error; 70 | use custom_error::custom_error; 71 | 72 | custom_error!{MyError 73 | Bad = "Something bad happened", 74 | Terrible = "This is a very serious error!!!" 75 | } 76 | ``` 77 | 78 | ## Custom error with parameters 79 | 80 | You can store data inside your errors. 81 | In order to do so, indicate the name and types of the fields to want to store in curly braces 82 | after an error type. 83 | 84 | ```rust 85 | extern crate custom_error; 86 | use custom_error::custom_error; 87 | 88 | custom_error!{SantaError 89 | BadChild{name:String, foolishness:u8} = "{name} has been bad {foolishness} times this year", 90 | TooFar = "The location you indicated is too far from the north pole", 91 | InvalidReindeer{legs:u8} = "The reindeer has {legs} legs" 92 | } 93 | 94 | assert_eq!( 95 | "Thomas has been bad 108 times this year", 96 | SantaError::BadChild{name: "Thomas".into(), foolishness: 108}.to_string()); 97 | ``` 98 | 99 | The error messages can reference your parameters using braces (`{parameter_name}`). 100 | If you need some custom logic to display your parameters, you can use 101 | [advanced custom error messages](#advanced-custom-error-messages). 102 | 103 | ## Wrapping other error types 104 | 105 | If the cause of your error is another lower-level error, you can indicate that 106 | by adding a special `source` field to one of your error cases. 107 | 108 | Thus, you can make your custom error wrap other error types, 109 | and the conversion from these foreign error types will be implemented automatically. 110 | 111 | ```rust 112 | #[macro_use] extern crate custom_error; 113 | use std::{io, io::Read, fs::File, result::Result, num::ParseIntError}; 114 | 115 | custom_error! {FileParseError 116 | Io{source: io::Error} = "unable to read from the file", 117 | Format{source: ParseIntError} = "the file does not contain a valid integer", 118 | TooLarge{value:u8} = "the number in the file ({value}) is too large" 119 | } 120 | 121 | fn parse_hex_file(filename: &str) -> Result { 122 | let mut contents = String::new(); 123 | // The '?' notation can convert from generic errors to our custom error type 124 | File::open(filename)?.read_to_string(&mut contents)?; 125 | let value = u8::from_str_radix(&contents, 16)?; 126 | if value > 42 { 127 | Err(FileParseError::TooLarge { value }) 128 | } else { 129 | Ok(value) 130 | } 131 | } 132 | 133 | fn main() { 134 | let parse_result = parse_hex_file("/i'm not a file/"); 135 | assert_eq!("unable to read from the file", parse_result.unwrap_err().to_string()); 136 | } 137 | ``` 138 | 139 | ## Visibility 140 | 141 | You can make an error type public by adding the `pub` keyword 142 | at the beginning of the declaration. 143 | 144 | ```rust 145 | custom_error!{pub MyError A="error a", B="error b"} 146 | ``` 147 | 148 | ## Attributes 149 | 150 | You can derive traits for your error types by adding attributes 151 | to the beginning of your macro invocation. 152 | 153 | ```rust 154 | custom_error!{#[derive(PartialEq,PartialOrd)] MyError A="error a", B="error b"} 155 | assert!(MyError::A < MyError::B); 156 | ``` 157 | 158 | Since [doc comments](https://doc.rust-lang.org/beta/rustdoc/print.html#the-doc-attribute) 159 | are just syntax sugar for `#[doc = "..."]`, you can use them too: 160 | 161 | ```rust 162 | custom_error!{ 163 | /// This is the documentation for my error type 164 | pub MyError A="error a", B="error b" 165 | } 166 | ``` 167 | 168 | ## Advanced custom error messages 169 | 170 | If you want to use error messages that you cannot express with 171 | simple formatting strings, you can generate your error messages with 172 | custom code. 173 | 174 | In the following example, we use this feature to display a 175 | different error message based on the cause of the underlying IO error. 176 | 177 | ```rust 178 | custom_error!{ pub MyError 179 | Io{source: Error} = @{ 180 | match source.kind() { 181 | NotFound => "The file does not exist", 182 | TimedOut => "The operation timed out", 183 | _ => "unknown error", 184 | } 185 | }, 186 | Unknown = "unknown error" 187 | } 188 | ``` 189 | 190 | ## nostd 191 | 192 | This crate supports [`no-std`](https://docs.rust-embedded.org/book/intro/no-std.html): it can be built without the rust standard library. 193 | To use the no-std version, disable the std feature in this crate in your `Cargo.toml` : 194 | 195 | ```toml 196 | [dependencies] 197 | custom_error = { version = "1", default-features = false } # nostd compatible 198 | ``` 199 | 200 | ### unstable 201 | 202 | There is also an `unstable` feature 203 | that implements the nostd error trait on `AllocError` and `TryReserveError` in no-std. 204 | 205 | ## Minimum supported rust version 206 | 207 | This crate works and is tested with rust >= [1.36](https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1360-2019-07-04) 208 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! The Error trait currently resides in std and is not available for use in no_std. 2 | //! 3 | //! We provide our own Error trait in this case and implement it for all errors in 4 | //! `core` and `alloc`. 5 | //! 6 | //! Error might go back be in libcore again: 7 | //! https://doc.rust-lang.org/nightly/src/std/error.rs.html#5 8 | //! 9 | //! Once this happens, this file will become obsolete and can be removed. 10 | 11 | use core::any::TypeId; 12 | use core::fmt::{Debug, Display}; 13 | 14 | /// A copy of the Error trait definition from libstd (for now). 15 | pub trait Error: Debug + Display { 16 | /// Human-readable error description 17 | fn description(&self) -> &str { 18 | "description() is deprecated; use Display" 19 | } 20 | 21 | /// The lower-level source of this error, if any. 22 | fn cause(&self) -> Option<&dyn Error> { 23 | self.source() 24 | } 25 | 26 | /// The lower-level source of this error, if any. 27 | fn source(&self) -> Option<&(dyn Error + 'static)> { 28 | None 29 | } 30 | 31 | /// type id 32 | fn type_id(&self) -> TypeId 33 | where 34 | Self: 'static, 35 | { 36 | TypeId::of::() 37 | } 38 | } 39 | 40 | impl Error for core::alloc::LayoutErr {} 41 | impl Error for core::array::TryFromSliceError {} 42 | impl Error for core::cell::BorrowError {} 43 | impl Error for core::cell::BorrowMutError {} 44 | impl Error for core::char::CharTryFromError {} 45 | impl Error for core::char::DecodeUtf16Error {} 46 | impl Error for core::char::ParseCharError {} 47 | impl Error for core::fmt::Error {} 48 | impl Error for core::num::ParseFloatError {} 49 | impl Error for core::num::ParseIntError {} 50 | impl Error for core::num::TryFromIntError {} 51 | impl Error for core::str::ParseBoolError {} 52 | impl Error for core::str::Utf8Error {} 53 | impl Error for alloc::string::ParseError {} 54 | 55 | #[cfg(feature = "unstable")] 56 | impl Error for core::alloc::AllocError {} 57 | #[cfg(feature = "unstable")] 58 | impl Error for alloc::collections::TryReserveError {} 59 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(missing_docs)] 2 | #![cfg_attr(not(feature = "std"), no_std)] 3 | #![cfg_attr(feature = "unstable", feature(allocator_api, try_reserve))] 4 | 5 | //! # Rust custom error 6 | //! This crate contains a [`custom_error!`](custom_error) macro that should make it easier to define custom errors 7 | //! without having to write a lot of boilerplate code. 8 | //! 9 | //! ## Crate features 10 | //! 11 | //! ### nostd 12 | //! 13 | //! This crate supports [`no-std`](https://docs.rust-embedded.org/book/intro/no-std.html): it can be built without the rust standard library. 14 | //! To use the no-std version, disable the std feature in this crate in your `Cargo.toml` : 15 | //! 16 | //! ```toml 17 | //! [dependencies] 18 | //! custom_error = { version = "1", default-features = false } # nostd compatible 19 | //! ``` 20 | //! 21 | //! ### unstable 22 | //! 23 | //! There is also an `unstable` feature 24 | //! that implements the nostd error trait on `AllocError` and `TryReserveError` in no-std. 25 | 26 | extern crate alloc; 27 | 28 | /// We define our own Error trait for no_std. 29 | #[cfg(not(feature = "std"))] 30 | mod error; 31 | 32 | /// Use the Error trait from `std` by default. 33 | #[cfg(feature = "std")] 34 | pub use std::error::Error; 35 | 36 | /// Use our own Error trait for `no_std`. 37 | #[cfg(not(feature = "std"))] 38 | pub use error::Error; 39 | 40 | /// Constructs a custom error type. 41 | /// 42 | /// # Examples 43 | /// 44 | /// ### Simple error 45 | /// 46 | /// For an error with multiple cases you can generate an enum: 47 | /// ``` 48 | /// use custom_error::custom_error; 49 | /// 50 | /// custom_error! { pub MyError 51 | /// Bad = "Something bad happened", 52 | /// Terrible = "This is a very serious error!!!" 53 | /// } 54 | /// assert_eq!("Something bad happened", MyError::Bad.to_string()); 55 | /// assert_eq!( 56 | /// "This is a very serious error!!!", 57 | /// MyError::Terrible.to_string() 58 | /// ); 59 | /// ``` 60 | /// 61 | /// For an error with a single case you can generate a struct: 62 | /// ``` 63 | /// use custom_error::custom_error; 64 | /// 65 | /// custom_error! { pub MyError{} = "Something bad happened" } 66 | /// assert_eq!("Something bad happened", MyError {}.to_string()); 67 | /// ``` 68 | /// 69 | /// ### Custom error with parameters 70 | /// ``` 71 | /// use custom_error::custom_error; 72 | /// 73 | /// custom_error!{SantaError 74 | /// BadChild{name:String, foolishness:u8} = "{name} has been bad {foolishness} times this year", 75 | /// TooFar = "The location you indicated is too far from the north pole", 76 | /// InvalidReindeer{legs:u8} = "The reindeer has {legs} legs" 77 | /// } 78 | /// 79 | /// assert_eq!( 80 | /// "Thomas has been bad 108 times this year", 81 | /// SantaError::BadChild{ 82 | /// name: "Thomas".into(), 83 | /// foolishness: 108 84 | /// }.to_string()); 85 | /// 86 | /// assert_eq!( 87 | /// "The location you indicated is too far from the north pole", 88 | /// SantaError::TooFar.to_string() 89 | /// ); 90 | /// 91 | /// assert_eq!( 92 | /// "The reindeer has 8 legs", 93 | /// SantaError::InvalidReindeer{legs:8}.to_string() 94 | /// ); 95 | /// ``` 96 | /// 97 | /// ### Automatic conversion from other error types 98 | /// 99 | /// You can add a special field named `source` to your error types. 100 | /// 101 | /// Use this field to include the lower-level source of the error. 102 | /// It will be used in the error 103 | /// [`source()`](https://doc.rust-lang.org/std/error/trait.Error.html#method.source) 104 | /// method, and automatic conversion from the source error type to your custom error type will be possible 105 | /// (your error type will implement `From`). 106 | /// 107 | /// #### limitations 108 | /// * You cannot have several error cases that contain a single *source* field of the same type: 109 | /// `custom_error!(E A{source:X} B{source:Y})` is allowed, but 110 | /// `custom_error!(E A{source:X} B{source:X})` is forbidden. 111 | /// * If the source field is not the only one, then the automatic conversion 112 | /// will not be implemented. 113 | /// * The type of source must be valid for the `'static` lifetime (because of the type signature of 114 | /// the [`source()`](https://doc.rust-lang.org/std/error/trait.Error.html#method.source) method). 115 | /// You can still have a field with a non-static type that you will not name `source`, 116 | /// and manually implement the error conversion from this type to your error type. 117 | /// 118 | /// ``` 119 | /// # #[cfg(feature = "std")] { 120 | /// use std::{fs::File, io, io::Read, result::Result}; 121 | /// use custom_error::custom_error; 122 | /// 123 | /// custom_error! {MyError 124 | /// IO{source: io::Error} = "input/output error", 125 | /// Unknown = "unknown error" 126 | /// } 127 | /// 128 | /// fn read_file(filename: &str) -> Result { 129 | /// let mut res = String::new(); 130 | /// File::open(filename)?.read_to_string(&mut res)?; 131 | /// Ok(res) 132 | /// } 133 | /// 134 | /// assert_eq!( 135 | /// "input/output error", 136 | /// read_file("/i'm not a file/").unwrap_err().to_string() 137 | /// ) 138 | /// # } 139 | /// ``` 140 | /// 141 | /// ### Custom formatting function for error messages 142 | /// 143 | /// If the format string syntax is not enough to express your complex error formatting needs, 144 | /// you can use custom code to generate your error description. 145 | /// 146 | /// ``` 147 | /// use custom_error::custom_error; 148 | /// 149 | /// static LANG: &'static str = "FR"; 150 | /// 151 | /// # fn localize(_:&str, _:&str) -> &'static str { "Un problème est survenu" } 152 | /// 153 | /// custom_error! { pub MyError 154 | /// Problem = @{ localize(LANG, "A problem occurred") }, 155 | /// } 156 | /// 157 | /// assert_eq!("Un problème est survenu", MyError::Problem.to_string()); 158 | /// ``` 159 | /// 160 | /// ``` 161 | /// # #[cfg(feature = "std")] { 162 | /// use std::io::Error; 163 | /// use std::io::ErrorKind::*; 164 | /// use custom_error::custom_error; 165 | /// 166 | /// custom_error! { pub MyError 167 | /// Io{source: Error} = @{ 168 | /// match source.kind() { 169 | /// NotFound => "The file does not exist", 170 | /// TimedOut => "The operation timed out", 171 | /// _ => "unknown error", 172 | /// } 173 | /// }, 174 | /// } 175 | /// 176 | /// assert_eq!( 177 | /// "The operation timed out", 178 | /// MyError::Io { 179 | /// source: TimedOut.into() 180 | /// } 181 | /// .to_string() 182 | /// ); 183 | /// # } 184 | /// ``` 185 | /// 186 | /// ### Derive traits for your errors 187 | /// You can add custom [attributes](https://doc.rust-lang.org/rust-by-example/attribute.html) 188 | /// at the beginning of the macro invocation. This allows you to derive traits for your error: 189 | /// 190 | /// ``` 191 | /// use custom_error::custom_error; 192 | /// 193 | /// custom_error! { 194 | /// #[derive(PartialEq,PartialOrd)] 195 | /// ErrLevel Small = "Don't worry", Serious = "Aaargh!!!" 196 | /// } 197 | /// assert_ne!(ErrLevel::Small, ErrLevel::Serious); 198 | /// assert!(ErrLevel::Small < ErrLevel::Serious); 199 | /// ``` 200 | #[macro_export] 201 | macro_rules! custom_error { 202 | ( 203 | $( #[$meta_attribute:meta] )* // Attributes, like #[derive(SomeTrait)] 204 | $visibility:vis // `pub` marker 205 | $errtype:ident // Name of the error type to generate 206 | $( < $( 207 | $type_param:tt // Optional type parameters for generic error types 208 | ),* 209 | > )? 210 | $( 211 | $( #[$field_meta:meta] )* // Meta-attributes for the variant, such as a doc comment 212 | $field:ident // Name of an error variant 213 | $( { $( 214 | $( #[$attr_meta:meta] )* // Meta-attributes for the attribute of the error variant 215 | $attr_name:ident // Name of an attribute of the error variant 216 | : 217 | $attr_type:ty // type of the attribute 218 | ),* } )? 219 | = 220 | $( @{ $($msg_fun:tt)* } )? 221 | $($msg:expr)? // The human-readable error message 222 | ),* 223 | $(,)* // Trailing comma 224 | ) => { 225 | $( #[$meta_attribute] )* 226 | #[derive(Debug)] 227 | $visibility enum $errtype $( < $($type_param),* > )* { 228 | $( 229 | $( #[$field_meta] )* 230 | $field 231 | $( { $( $( #[$attr_meta] )* $attr_name : $attr_type ),* } )* 232 | ),* 233 | } 234 | 235 | $crate::add_type_bounds! { 236 | ( $($($type_param),*)* ) 237 | (core::fmt::Debug + core::fmt::Display) 238 | { impl <} {> $crate::Error 239 | for $errtype $( < $($type_param),* > )* 240 | { 241 | fn source(&self) -> Option<&(dyn $crate::Error + 'static)> 242 | { 243 | #[allow(unused_variables, unreachable_code)] 244 | match self {$( 245 | $errtype::$field $( { $( $attr_name ),* } )* => { 246 | $( $( 247 | $crate::return_if_source!($attr_name, $attr_name) 248 | );* ;)* 249 | None 250 | } 251 | ),*} 252 | } 253 | } 254 | }} 255 | 256 | $crate::impl_error_conversion!{ 257 | ( $errtype $(< $($type_param),* >)* ) 258 | $([ 259 | $field, 260 | $($( 261 | $attr_name, 262 | $attr_name, 263 | $attr_type 264 | ),*),* 265 | ])* 266 | } 267 | 268 | $crate::add_type_bounds! { 269 | ( $($($type_param),*)* ) 270 | (alloc::string::ToString) 271 | { impl <} {> core::fmt::Display 272 | for $errtype $( < $($type_param),* > )* 273 | { 274 | fn fmt(&self, formatter: &mut core::fmt::Formatter) 275 | -> core::fmt::Result 276 | { 277 | match self {$( 278 | $errtype::$field $( { $( $attr_name ),* } )* => { 279 | $(write!(formatter, "{}", ($($msg_fun)*) )?;)* 280 | $crate::display_message!(formatter, $($($attr_name),*),* | $($msg)*); 281 | Ok(()) 282 | } 283 | ),*} 284 | } 285 | } 286 | }} 287 | }; 288 | 289 | // Simple struct error 290 | ( 291 | $( #[$meta_attribute:meta] )* // Attributes, like #[derive(SomeTrait)] 292 | $visibility:vis // `pub` marker 293 | $errtype:ident // Name of the error type to generate 294 | $( < $( 295 | $type_param:tt // Optional type parameters for generic error types 296 | ),* 297 | > )? 298 | { $( 299 | $( #[$field_meta:meta] )* // Field meta attributes, such as doc comments 300 | $field_name:ident // Name of an attribute of the error variant 301 | : 302 | $field_type:ty // type of the attribute 303 | ),* } 304 | = 305 | $( @{ $($msg_fun:tt)* } )? 306 | $($msg:expr)? // The human-readable error message 307 | $(,)* // Trailing comma 308 | ) => { 309 | $( #[$meta_attribute] )* 310 | #[derive(Debug)] 311 | $visibility struct $errtype $( < $($type_param),* > )* { 312 | $( 313 | $( #[$field_meta] )* 314 | pub $field_name : $field_type 315 | ),* 316 | } 317 | 318 | $crate::add_type_bounds! { 319 | ( $($($type_param),*)* ) 320 | (core::fmt::Debug + core::fmt::Display) 321 | { impl <} {> $crate::Error 322 | for $errtype $( < $($type_param),* > )* 323 | { 324 | #[allow(unused_variables, unreachable_code)] 325 | fn source(&self) -> Option<&(dyn $crate::Error + 'static)> 326 | { 327 | #[allow(unused_variables, unreachable_code)] 328 | match self { 329 | $errtype { $( $field_name ),* } => { 330 | $({ 331 | $crate::return_if_source!($field_name, $field_name) 332 | });* 333 | None 334 | } 335 | } 336 | } 337 | } 338 | }} 339 | 340 | $crate::impl_error_conversion_for_struct!{ 341 | $errtype $(< $($type_param),* >)*, 342 | $( $field_name: $field_type ),* 343 | } 344 | 345 | $crate::add_type_bounds! { 346 | ( $($($type_param),*)* ) 347 | (alloc::string::ToString) 348 | { impl <} {> core::fmt::Display 349 | for $errtype $( < $($type_param),* > )* 350 | { 351 | fn fmt(&self, formatter: &mut core::fmt::Formatter) 352 | -> core::fmt::Result 353 | { 354 | // make fields accessible with variables, so that we can 355 | // use them in custom error msg blocks without self 356 | $( 357 | let $field_name = &self.$field_name; 358 | )* 359 | $(write!(formatter, "{}", ($($msg_fun)*) )?;)* 360 | $crate::display_message!(formatter, $($field_name),* | $($msg)*); 361 | Ok(()) 362 | } 363 | } 364 | }} 365 | }; 366 | } 367 | 368 | #[doc(hidden)] 369 | #[macro_export] 370 | macro_rules! return_if_source { 371 | // Return the source if the attribute is called 'source' 372 | (source, $attr_name:ident) => {{ 373 | // Borrow is needed in order to support boxed errors 374 | // see: https://github.com/lovasoa/custom_error/issues/20 375 | return Some(core::borrow::Borrow::borrow($attr_name)); 376 | }}; 377 | // If the attribute has a different name, return nothing 378 | ($_attr_name:ident, $_repeat:ident ) => { 379 | () 380 | }; 381 | } 382 | 383 | #[doc(hidden)] 384 | #[macro_export] 385 | macro_rules! impl_error_conversion { 386 | ( ( $($prefix:tt)* ) [ $($field_data:tt)* ] $($rest:tt)* ) => { 387 | $crate::impl_error_conversion!{$($prefix)*, $($field_data)*} 388 | $crate::impl_error_conversion!{ ($($prefix)*) $($rest)*} 389 | }; 390 | ( ( $($prefix:tt)* ) ) => {}; 391 | // implement From only when there is a single attribute and it is named 'source' 392 | ( 393 | $errtype:ident $( < $($type_param:tt),* > )*, 394 | $field:ident, 395 | source, 396 | $source:ident, 397 | $source_type:ty 398 | ) => { 399 | impl $( < $($type_param),* > )* From<$source_type> 400 | for $errtype $( < $($type_param),* > )* { 401 | fn from(source: $source_type) -> Self { 402 | $errtype::$field { source } 403 | } 404 | } 405 | }; 406 | ( 407 | $_errtype:ident $( < $($_errtype_type_param:tt),* > )*, 408 | $_field:ident, 409 | $( 410 | $_:ident, 411 | $_repeated:ident, 412 | $_type:ty 413 | ),* 414 | ) => {}; // If the list of fields is not a single field named 'source', do nothing 415 | } 416 | 417 | #[doc(hidden)] 418 | #[macro_export] 419 | macro_rules! impl_error_conversion_for_struct { 420 | ( ( $($prefix:tt)* ) ) => {}; 421 | // implement From only when there is a single field and it is named 'source' 422 | ( 423 | $errtype:ident, $( < $($type_param:tt),* > )* 424 | source: $source_type:ty 425 | ) => { 426 | impl $( < $($type_param),* > )* From<$source_type> 427 | for $errtype $( < $($type_param),* > )* { 428 | fn from(source: $source_type) -> Self { $errtype { source } } 429 | } 430 | }; 431 | ( 432 | $_errtype:ident $( < $($_errtype_type_param:tt),* > )*, 433 | $( $_field:ident: $_type:ty ),* 434 | ) => {}; // If the list of fields is not a single field named 'source', do nothing 435 | } 436 | 437 | #[doc(hidden)] 438 | #[macro_export] 439 | macro_rules! display_message { 440 | ($formatter:expr, $($attr:ident),* | $msg:expr) => { 441 | write!( 442 | $formatter, 443 | concat!($msg $(, "{", stringify!($attr), ":.0}" )*) 444 | $( , $attr = $attr.to_string() )* 445 | )?; 446 | }; 447 | ($formatter:expr, $($attr:ident),* | ) => {}; 448 | } 449 | 450 | /* This macro, given a list of generic parameters and type 451 | bounds, adds the type bounds to all generic type parameters 452 | (and leaves the generic lifetime parameters unchanged) */ 453 | #[doc(hidden)] 454 | #[macro_export] 455 | macro_rules! add_type_bounds { 456 | ( 457 | ( $typ:ident $(, $rest:tt)* ) // type parameter 458 | ( $($bounds:tt)* ) 459 | { $($prefix:tt)* } 460 | { $($suffix:tt)* } 461 | ) => { 462 | add_type_bounds!{ 463 | ( $(, $rest)* ) 464 | ( $($bounds)* ) 465 | { $($prefix)* $typ : $($bounds)*} 466 | { $($suffix)* } 467 | } 468 | }; 469 | ( 470 | ( $lifetime:tt $(, $rest:tt)* ) // lifetime parameter 471 | ( $($bounds:tt)* ) 472 | { $($prefix:tt)* } 473 | { $($suffix:tt)* } 474 | ) => { 475 | add_type_bounds!{ 476 | ( $(, $rest)* ) 477 | ( $($bounds)* ) 478 | { $($prefix)* $lifetime } 479 | { $($suffix)* } 480 | } 481 | }; 482 | ( 483 | ( , $($rest:tt)* ) // add the comma to the prefix 484 | ( $($bounds:tt)* ) 485 | { $($prefix:tt)* } 486 | { $($suffix:tt)* } 487 | ) => { 488 | add_type_bounds!{ 489 | ( $($rest)* ) 490 | ( $($bounds)* ) 491 | { $($prefix)* , } 492 | { $($suffix)* } 493 | } 494 | }; 495 | ( 496 | ( ) // no more generic params to consume 497 | ( $($bounds:tt)* ) 498 | { $($prefix:tt)* } 499 | { $($suffix:tt)* } 500 | ) => { 501 | $($prefix)* $($suffix)* 502 | } 503 | } 504 | 505 | #[cfg(all(test, feature = "std"))] 506 | mod tests { 507 | use std::error::Error; 508 | use std::str::FromStr; 509 | 510 | #[test] 511 | fn single_error_case() { 512 | custom_error!(MyError Bad="bad"); 513 | assert_eq!("bad", MyError::Bad.to_string()); 514 | } 515 | 516 | #[test] 517 | fn single_error_struct_case() { 518 | custom_error!(MyError {} = "bad"); 519 | assert_eq!("bad", MyError {}.to_string()); 520 | } 521 | 522 | #[test] 523 | #[allow(dead_code)] 524 | fn three_error_cases() { 525 | custom_error!(MyError NotPerfect=":/", Bad="bad", Awfull="arghhh"); 526 | assert_eq!("arghhh", MyError::Awfull.to_string()) 527 | } 528 | 529 | #[test] 530 | fn with_error_data() { 531 | custom_error!(MyError 532 | Bad = "bad", 533 | Catastrophic{broken_things:u8} = "{broken_things} things are broken" 534 | ); 535 | assert_eq!("bad", MyError::Bad.to_string()); 536 | assert_eq!( 537 | "9 things are broken", 538 | MyError::Catastrophic { broken_things: 9 }.to_string() 539 | ); 540 | } 541 | 542 | #[test] 543 | fn with_doc_comments_for_variants() { 544 | custom_error! {MyError 545 | /// A bad error 546 | Bad="bad", 547 | /// Terrible error 548 | Terrible="terrible" 549 | } 550 | assert!(MyError::Bad.source().is_none()); 551 | assert!(MyError::Terrible.source().is_none()); 552 | } 553 | 554 | #[test] 555 | #[allow(dead_code)] 556 | fn with_doc_comments_for_fields() { 557 | custom_error! {MyError 558 | Bad { 559 | /// Name of the bad field 560 | bad: &'static str 561 | } = "bad {bad}", 562 | Terrible { 563 | /// Name of the terrible field 564 | terrible: &'static str 565 | } = "bad {terrible}", 566 | } 567 | assert!(MyError::Bad { bad: "heh" }.source().is_none()); 568 | } 569 | 570 | #[test] 571 | fn enum_with_field_lifetime() { 572 | custom_error!(MyError 573 | Problem{description: &'static str} = "{description}" 574 | ); 575 | assert_eq!("bad", MyError::Problem { description: "bad" }.to_string()); 576 | } 577 | 578 | #[test] 579 | fn struct_with_field_lifetime() { 580 | custom_error!(MyError {description: &'static str} = "{}"); 581 | assert_eq!("bad", MyError { description: "bad" }.to_string()); 582 | } 583 | 584 | #[test] 585 | fn enum_with_derive() { 586 | custom_error! { 587 | #[derive(PartialEq, PartialOrd)] 588 | #[derive(Clone)] 589 | MyError A = "A", B{b:u8} = "B({b})", C = "C" 590 | }; 591 | // PartialEq 592 | assert_eq!(MyError::A, MyError::A); 593 | assert_eq!(MyError::B { b: 1 }, MyError::B { b: 1 }); 594 | assert_ne!(MyError::B { b: 0 }, MyError::B { b: 1 }); 595 | assert_ne!(MyError::A, MyError::B { b: 1 }); 596 | // PartialOrd 597 | assert!(MyError::A < MyError::B { b: 1 }); 598 | assert!(MyError::B { b: 1 } < MyError::B { b: 2 }); 599 | assert!(MyError::B { b: 2 } < MyError::C); 600 | // Clone 601 | assert_eq!(MyError::A.clone(), MyError::A); 602 | } 603 | 604 | #[test] 605 | fn enum_with_pub_derive_and_doc_comment() { 606 | custom_error! { 607 | ///Doc comment 608 | #[derive(PartialEq, PartialOrd)] 609 | pub MyError A = "A" 610 | }; 611 | assert_eq!(MyError::A, MyError::A); 612 | } 613 | 614 | #[test] 615 | fn enum_with_box_dyn_source() { 616 | // See https://github.com/lovasoa/custom_error/issues/20 617 | custom_error! {pub MyError 618 | Dynamic { source: Box } = "dynamic", 619 | } 620 | let err = u8::from_str("x").unwrap_err(); 621 | assert_eq!( 622 | err.to_string(), 623 | MyError::Dynamic { 624 | source: Box::new(err) 625 | } 626 | .source() 627 | .unwrap() 628 | .to_string() 629 | ); 630 | } 631 | 632 | #[test] 633 | fn struct_with_box_dyn_source() { 634 | custom_error! {pub MyError 635 | { source: Box } = "dynamic", 636 | } 637 | let err = u8::from_str("x").unwrap_err(); 638 | assert_eq!( 639 | err.to_string(), 640 | MyError { 641 | source: Box::new(err) 642 | } 643 | .source() 644 | .unwrap() 645 | .to_string() 646 | ); 647 | } 648 | 649 | #[test] 650 | fn struct_with_public_field() { 651 | mod inner { 652 | custom_error! {pub MyError {x: &'static str} = "{}"} 653 | } 654 | assert_eq!("hello", inner::MyError { x: "hello" }.to_string()); 655 | } 656 | 657 | #[test] 658 | fn struct_with_field_documentation() { 659 | custom_error! { 660 | pub MyError { 661 | /// This is a doc comment 662 | x: &'static str 663 | } = "{}" 664 | } 665 | assert_eq!("hello", MyError { x: "hello" }.to_string()); 666 | } 667 | 668 | #[test] 669 | fn struct_with_error_data() { 670 | custom_error!(MyError { broken_things: u8 } = "{broken_things} things are broken"); 671 | assert_eq!( 672 | "9 things are broken", 673 | MyError { broken_things: 9 }.to_string() 674 | ); 675 | } 676 | 677 | #[test] 678 | fn struct_with_derive() { 679 | custom_error!( 680 | #[derive(PartialEq, PartialOrd, Clone, Default)] 681 | MyError { x: u8 } = ":(" 682 | ); 683 | assert_eq!(MyError { x: 9 }, MyError { x: 9 }); // Has PartialEq 684 | assert_eq!(MyError { x: 0 }.clone(), MyError { x: 0 }); // Has Clone 685 | assert_eq!(MyError::default(), MyError { x: 0 }); // Has Default 686 | assert!(MyError { x: 0 } < MyError { x: 1 }); // Has PartialOrd 687 | } 688 | 689 | #[test] 690 | fn with_multiple_error_data() { 691 | custom_error!(E X{a:u8, b:u8, c:u8} = "{c} {b} {a}"); 692 | 693 | assert_eq!("3 2 1", E::X { a: 1, b: 2, c: 3 }.to_string()); 694 | } 695 | 696 | #[test] 697 | fn struct_with_multiple_error_data() { 698 | custom_error!( 699 | E { 700 | a: u8, 701 | b: u8, 702 | c: u8 703 | } = "{c} {b} {a}" 704 | ); 705 | 706 | assert_eq!("3 2 1", E { a: 1, b: 2, c: 3 }.to_string()); 707 | } 708 | 709 | #[test] 710 | fn source() { 711 | use std::{error::Error, io}; 712 | custom_error!(E A{source: io::Error}=""); 713 | let source: io::Error = io::ErrorKind::InvalidData.into(); 714 | assert_eq!( 715 | source.to_string(), 716 | E::A { source }.source().unwrap().to_string() 717 | ); 718 | } 719 | 720 | #[test] 721 | fn struct_source() { 722 | use std::{error::Error, io}; 723 | custom_error!(E { source: io::Error } = ""); 724 | let source: io::Error = io::ErrorKind::InvalidData.into(); 725 | assert_eq!( 726 | source.to_string(), 727 | E { source }.source().unwrap().to_string() 728 | ); 729 | } 730 | 731 | #[test] 732 | fn from_source() { 733 | use std::io; 734 | custom_error!(E A{source: io::Error}="bella vita"); 735 | let source = io::Error::from(io::ErrorKind::InvalidData); 736 | assert_eq!("bella vita", E::from(source).to_string()); 737 | } 738 | 739 | #[test] 740 | fn struct_from_source() { 741 | use std::io; 742 | custom_error!(E { source: io::Error } = "bella vita"); 743 | let source = io::Error::from(io::ErrorKind::InvalidData); 744 | assert_eq!("bella vita", E::from(source).to_string()); 745 | } 746 | 747 | #[test] 748 | #[allow(dead_code)] 749 | fn with_source_and_others() { 750 | use std::{error::Error, io}; 751 | custom_error!(MyError Zero="", One{x:u8}="", Two{x:u8, source:io::Error}="{x}"); 752 | fn source() -> io::Error { 753 | io::ErrorKind::AlreadyExists.into() 754 | } 755 | let my_err = MyError::Two { 756 | x: 42, 757 | source: source(), 758 | }; 759 | assert_eq!("42", my_err.to_string()); 760 | assert_eq!(source().to_string(), my_err.source().unwrap().to_string()); 761 | } 762 | 763 | #[test] 764 | #[allow(dead_code)] 765 | fn struct_with_source_and_others() { 766 | use std::{error::Error, io}; 767 | custom_error!( 768 | MyError { 769 | x: u8, 770 | source: io::Error 771 | } = "{x}" 772 | ); 773 | fn source() -> io::Error { 774 | io::ErrorKind::AlreadyExists.into() 775 | } 776 | let my_err = MyError { 777 | x: 42, 778 | source: source(), 779 | }; 780 | assert_eq!("42", my_err.to_string()); 781 | assert_eq!(source().to_string(), my_err.source().unwrap().to_string()); 782 | } 783 | 784 | #[test] 785 | fn pub_error() { 786 | mod my_mod { 787 | custom_error! {pub MyError Case1="case1"} 788 | } 789 | assert_eq!("case1", my_mod::MyError::Case1.to_string()); 790 | } 791 | 792 | #[test] 793 | fn pub_error_struct() { 794 | mod my_mod { 795 | custom_error! {pub MyError{} = "case1"} 796 | } 797 | assert_eq!("case1", my_mod::MyError {}.to_string()); 798 | } 799 | 800 | #[test] 801 | fn pub_error_struct_fields() { 802 | mod my_mod { 803 | custom_error! {pub MyError{x:u8} = "x={x}"} 804 | } 805 | assert_eq!("x=9", my_mod::MyError { x: 9 }.to_string()); 806 | } 807 | 808 | #[test] 809 | fn generic_error() { 810 | custom_error! {MyError E1{x:X,y:Y}="x={x} y={y}", E2="e2"} 811 | assert_eq!("x=42 y=42", MyError::E1 { x: 42u8, y: 42u8 }.to_string()); 812 | assert_eq!("e2", MyError::E2::.to_string()); 813 | } 814 | 815 | #[test] 816 | fn generic_error_struct() { 817 | custom_error! {MyError{x:X,y:Y}="x={x} y={y}"} 818 | assert_eq!("x=42 y=42", MyError { x: 42u8, y: 42u8 }.to_string()); 819 | } 820 | 821 | #[test] 822 | fn single_error_case_with_braces() { 823 | custom_error! {MyError Bad="bad"} 824 | assert_eq!("bad", MyError::Bad.to_string()); 825 | } 826 | 827 | #[test] 828 | fn single_error_struct_case_with_braces() { 829 | custom_error! {MyError{} ="bad"} 830 | assert_eq!("bad", MyError {}.to_string()) 831 | } 832 | 833 | #[test] 834 | fn trailing_comma() { 835 | custom_error! {MyError1 A="a",} 836 | custom_error! {MyError2 A="a", B="b",} 837 | 838 | assert_eq!("a", MyError1::A.to_string()); 839 | assert_eq!("a", MyError2::A.to_string()); 840 | assert_eq!("b", MyError2::B.to_string()); 841 | } 842 | 843 | #[test] 844 | fn with_custom_formatting() { 845 | custom_error! {MyError 846 | Complex{a:u8, b:u8} = @{ 847 | if a+b == 0 { 848 | "zero".to_string() 849 | } else { 850 | (a+b).to_string() 851 | } 852 | }, 853 | Simple = "simple" 854 | } 855 | 856 | assert_eq!("zero", MyError::Complex { a: 0, b: 0 }.to_string()); 857 | assert_eq!("3", MyError::Complex { a: 2, b: 1 }.to_string()); 858 | assert_eq!("simple", MyError::Simple.to_string()); 859 | } 860 | 861 | #[test] 862 | fn struct_with_custom_formatting() { 863 | custom_error! {MyError{a:u8, b:u8} = @{ 864 | if a+b == 0 { 865 | "zero".to_string() 866 | } else { 867 | (a+b).to_string() 868 | } 869 | } 870 | } 871 | 872 | assert_eq!("zero", MyError { a: 0, b: 0 }.to_string()); 873 | assert_eq!("3", MyError { a: 2, b: 1 }.to_string()); 874 | } 875 | 876 | #[test] 877 | fn custom_format_source() { 878 | use std::io; 879 | 880 | custom_error! {MyError 881 | Io{source:io::Error} = @{format!("IO Error occurred: {:?}", source.kind())} 882 | } 883 | 884 | assert_eq!( 885 | "IO Error occurred: Interrupted", 886 | MyError::Io { 887 | source: io::ErrorKind::Interrupted.into() 888 | } 889 | .to_string() 890 | ) 891 | } 892 | 893 | #[test] 894 | fn struct_custom_format_source() { 895 | use std::io; 896 | 897 | custom_error! {MyError{source:io::Error} = @{format!("IO Error occurred: {:?}", source.kind())} } 898 | 899 | assert_eq!( 900 | "IO Error occurred: Interrupted", 901 | MyError { 902 | source: io::ErrorKind::Interrupted.into() 903 | } 904 | .to_string() 905 | ) 906 | } 907 | 908 | #[test] 909 | fn lifetime_source_param() { 910 | #[derive(Debug)] 911 | struct SourceError<'my_lifetime> { 912 | x: &'my_lifetime str, 913 | } 914 | impl<'a> std::fmt::Display for SourceError<'a> { 915 | fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result { 916 | Ok(()) 917 | } 918 | } 919 | impl<'a> std::error::Error for SourceError<'a> {} 920 | 921 | custom_error! { MyError<'source_lifetime> 922 | Sourced { lifetimed : SourceError<'source_lifetime> } = @{ lifetimed.x }, 923 | Other { source: std::fmt::Error } = "other error" 924 | } 925 | 926 | let sourced = MyError::Sourced { 927 | lifetimed: SourceError { 928 | x: "I am the source", 929 | }, 930 | }; 931 | assert_eq!("I am the source", sourced.to_string()); 932 | let other_err: MyError = std::fmt::Error.into(); 933 | assert_eq!("other error", other_err.to_string()); 934 | } 935 | 936 | #[test] 937 | fn struct_lifetime_source_param() { 938 | #[derive(Debug)] 939 | struct SourceError<'my_lifetime> { 940 | x: &'my_lifetime str, 941 | } 942 | impl<'a> std::fmt::Display for SourceError<'a> { 943 | fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result { 944 | Ok(()) 945 | } 946 | } 947 | impl<'a> std::error::Error for SourceError<'a> {} 948 | 949 | custom_error! { MyError<'source_lifetime>{ 950 | lifetimed : SourceError<'source_lifetime> 951 | } = @{ lifetimed.x },} 952 | 953 | let sourced = MyError { 954 | lifetimed: SourceError { 955 | x: "I am the source", 956 | }, 957 | }; 958 | assert_eq!("I am the source", sourced.to_string()); 959 | } 960 | 961 | #[test] 962 | fn lifetime_param_and_type_param() { 963 | #[derive(Debug)] 964 | struct MyType<'a, T> { 965 | data: &'a str, 966 | _y: T, 967 | } 968 | custom_error! { MyError<'a,T> 969 | X { d: MyType<'a,T> } = @{ format!("error x: {}", d.data) }, 970 | Y { d: T } = "error y" 971 | } 972 | let err = MyError::X { 973 | d: MyType { 974 | data: "hello", 975 | _y: 42i8, 976 | }, 977 | }; 978 | assert_eq!("error x: hello", err.to_string()); 979 | let err_y = MyError::Y { 980 | d: String::from("my string"), 981 | }; 982 | assert_eq!("error y", err_y.to_string()); 983 | } 984 | 985 | #[test] 986 | fn struct_lifetime_param_and_type_param() { 987 | #[derive(Debug)] 988 | struct MyType<'a, T> { 989 | data: &'a str, 990 | _y: T, 991 | } 992 | custom_error! { MyError<'a,T> { 993 | d: MyType<'a,T> 994 | } = @{ format!("error x: {}", d.data) } } 995 | let err = MyError { 996 | d: MyType { 997 | data: "hello", 998 | _y: 42i8, 999 | }, 1000 | }; 1001 | assert_eq!("error x: hello", err.to_string()); 1002 | } 1003 | } 1004 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | #[macro_use] 3 | extern crate custom_error; 4 | 5 | use std::{fs::File, io, io::ErrorKind, io::Read, num::ParseIntError, result::Result}; 6 | 7 | custom_error! { 8 | OpenFileError 9 | NotFound{filename: String} = "Tried to open '{filename}', but it doesn't exist", 10 | Other = "An unknown I/O error occured.", 11 | } 12 | 13 | /// Opens a file with a verbose error on failure 14 | fn open_file_verbose(filename: &str) -> Result { 15 | File::open(filename).map_err(|e| match e.kind() { 16 | ErrorKind::NotFound => OpenFileError::NotFound { 17 | filename: filename.to_string(), 18 | }, 19 | _ => OpenFileError::Other, 20 | }) 21 | } 22 | 23 | custom_error! {FileParseError 24 | Open{source: OpenFileError} = @{ source }, // Chained custom error 25 | Io{source: io::Error} = "I/O error", 26 | Format{source: ParseIntError} = "the file does not contain a valid integer", 27 | TooLarge{value:u8} = "the number in the file ({value}) is too large" 28 | } 29 | 30 | fn parse_hex_file(filename: &str) -> Result { 31 | let mut contents = String::new(); 32 | // The '?' notation can convert from generic errors to our custom error type 33 | open_file_verbose(filename)?.read_to_string(&mut contents)?; 34 | let value = u8::from_str_radix(&contents, 16)?; 35 | if value > 42 { 36 | Err(FileParseError::TooLarge { value }) 37 | } else { 38 | Ok(value) 39 | } 40 | } 41 | 42 | #[test] 43 | fn main() { 44 | let parse_result = parse_hex_file("/i'm not a file/"); 45 | assert_eq!( 46 | "Tried to open '/i'm not a file/', but it doesn't exist", 47 | parse_result.unwrap_err().to_string() 48 | ); 49 | } 50 | --------------------------------------------------------------------------------