custom_error-1.9.2/.cargo_vcs_info.json0000644000000001120000000000000135610ustar { "git": { "sha1": "7ba39a74ac106a516db32eb8c8c13a5d7d97ee7f" } } custom_error-1.9.2/.github/workflows/rust.yml000064400000000000000000000011720000000000000174530ustar 00000000000000name: Rust on: push: branches: [ master ] pull_request: branches: [ master ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest strategy: matrix: rust: [1.36.0, stable, beta] steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} default: true override: true - name: test uses: actions-rs/cargo@v1 with: command: test - name: test nostd uses: actions-rs/cargo@v1 with: command: test args: --no-default-features custom_error-1.9.2/.gitignore000064400000000000000000000001010000000000000143150ustar 00000000000000/target **/*.rs.bk Cargo.lock custom_error.iml .idea/ launch.jsoncustom_error-1.9.2/Cargo.toml0000644000000021520000000000000115650ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "custom_error" version = "1.9.2" authors = ["lovasoa"] description = "Define custom errors without boilerplate using the custom_error! macro." homepage = "https://github.com/lovasoa/custom_error" documentation = "https://docs.rs/custom_error" readme = "README.md" keywords = ["error", "failure", "macro"] categories = ["rust-patterns", "development-tools", "encoding"] license = "BSD-2-Clause" repository = "https://github.com/lovasoa/custom_error" [dependencies] [features] default = ["std"] std = [] unstable = [] [badges.maintenance] status = "passively-maintained" custom_error-1.9.2/Cargo.toml.orig000064400000000000000000000014050000000000000152240ustar 00000000000000[package] name = "custom_error" description = "Define custom errors without boilerplate using the custom_error! macro." version = "1.9.2" authors = ["lovasoa"] license = "BSD-2-Clause" homepage = "https://github.com/lovasoa/custom_error" repository = "https://github.com/lovasoa/custom_error" readme = "README.md" categories = ["rust-patterns", "development-tools", "encoding"] keywords = ["error", "failure", "macro"] documentation = "https://docs.rs/custom_error" edition='2018' [dependencies] [badges] maintenance = { status = "passively-maintained" } [features] default = ["std"] # Disable std for use in no_std context. std = [] # Enable allocator_api and try_reserve, to define Error trait # for unstable `AllocError` and `TryReserveError` in no-std unstable = [] custom_error-1.9.2/LICENSE000064400000000000000000000024460000000000000133500ustar 00000000000000BSD 2-Clause License Copyright (c) 2018, Ophir LOJKINE All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. custom_error-1.9.2/README.md000064400000000000000000000142340000000000000136200ustar 00000000000000# Rust custom error [![Rust](https://github.com/lovasoa/custom_error/actions/workflows/rust.yml/badge.svg)](https://github.com/lovasoa/custom_error/actions/workflows/rust.yml) [![Crates.io](https://img.shields.io/crates/v/custom_error)](https://crates.io/crates/custom_error) [![docs.rs](https://img.shields.io/docsrs/custom_error)](https://docs.rs/custom_error) This crate contains a macro that should make it easier to define custom errors without having to write a lot of boilerplate code. The `custom_error!` macro included in this crate takes a type name and a list of possible errors and generates a rust enumeration for all the cases, together with the required `impl` blocks implementing `std::error::Error` and `std::fmt::Display`. If you only have a single case for an error you can also generate a struct instead of an enum. You can now write: ```rust extern crate custom_error; use custom_error::custom_error; // Note the use of braces rather than parentheses. custom_error!{MyError Unknown{code:u8} = "unknown error with code {code}.", Err41 = "Sit by a lake" } ``` instead of ```rust #[derive(Debug)] enum MyError { Unknown { code: u8 }, Err41, } impl std::error::Error for MyError {} impl std::fmt::Display for MyError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { MyError::Unknown { code } => write!(f, "unknown error with code {}." , code), MyError::Err41 => write!(f, "Sit by a lake") } } } ``` If you only have a single error case you can also generate a struct: ```rust extern crate custom_error; use custom_error::custom_error; custom_error!{MyError{code:u8} = "error with code {code}."} ``` ## Simple error To define a simple error, you only have to indicate three things: * the name of your custom error type, * the name of the different error cases, * a human-readable description for each case. ```rust extern crate custom_error; use custom_error::custom_error; custom_error!{MyError Bad = "Something bad happened", Terrible = "This is a very serious error!!!" } ``` ## Custom error with parameters You can store data inside your errors. In order to do so, indicate the name and types of the fields to want to store in curly braces after an error type. ```rust extern crate custom_error; use custom_error::custom_error; custom_error!{SantaError BadChild{name:String, foolishness:u8} = "{name} has been bad {foolishness} times this year", TooFar = "The location you indicated is too far from the north pole", InvalidReindeer{legs:u8} = "The reindeer has {legs} legs" } assert_eq!( "Thomas has been bad 108 times this year", SantaError::BadChild{name: "Thomas".into(), foolishness: 108}.to_string()); ``` The error messages can reference your parameters using braces (`{parameter_name}`). If you need some custom logic to display your parameters, you can use [advanced custom error messages](#advanced-custom-error-messages). ## Wrapping other error types If the cause of your error is another lower-level error, you can indicate that by adding a special `source` field to one of your error cases. Thus, you can make your custom error wrap other error types, and the conversion from these foreign error types will be implemented automatically. ```rust #[macro_use] extern crate custom_error; use std::{io, io::Read, fs::File, result::Result, num::ParseIntError}; custom_error! {FileParseError Io{source: io::Error} = "unable to read from the file", Format{source: ParseIntError} = "the file does not contain a valid integer", TooLarge{value:u8} = "the number in the file ({value}) is too large" } fn parse_hex_file(filename: &str) -> Result { let mut contents = String::new(); // The '?' notation can convert from generic errors to our custom error type File::open(filename)?.read_to_string(&mut contents)?; let value = u8::from_str_radix(&contents, 16)?; if value > 42 { Err(FileParseError::TooLarge { value }) } else { Ok(value) } } fn main() { let parse_result = parse_hex_file("/i'm not a file/"); assert_eq!("unable to read from the file", parse_result.unwrap_err().to_string()); } ``` ## Visibility You can make an error type public by adding the `pub` keyword at the beginning of the declaration. ```rust custom_error!{pub MyError A="error a", B="error b"} ``` ## Attributes You can derive traits for your error types by adding attributes to the beginning of your macro invocation. ```rust custom_error!{#[derive(PartialEq,PartialOrd)] MyError A="error a", B="error b"} assert!(MyError::A < MyError::B); ``` Since [doc comments](https://doc.rust-lang.org/beta/rustdoc/print.html#the-doc-attribute) are just syntax sugar for `#[doc = "..."]`, you can use them too: ```rust custom_error!{ /// This is the documentation for my error type pub MyError A="error a", B="error b" } ``` ## Advanced custom error messages If you want to use error messages that you cannot express with simple formatting strings, you can generate your error messages with custom code. In the following example, we use this feature to display a different error message based on the cause of the underlying IO error. ```rust custom_error!{ pub MyError Io{source: Error} = @{ match source.kind() { NotFound => "The file does not exist", TimedOut => "The operation timed out", _ => "unknown error", } }, Unknown = "unknown error" } ``` ## nostd This crate supports [`no-std`](https://docs.rust-embedded.org/book/intro/no-std.html): it can be built without the rust standard library. To use the no-std version, disable the std feature in this crate in your `Cargo.toml` : ```toml [dependencies] custom_error = { version = "1", default-features = false } # nostd compatible ``` ### unstable There is also an `unstable` feature that implements the nostd error trait on `AllocError` and `TryReserveError` in no-std. ## Minimum supported rust version 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) custom_error-1.9.2/src/error.rs000064400000000000000000000034420000000000000146260ustar 00000000000000//! The Error trait currently resides in std and is not available for use in no_std. //! //! We provide our own Error trait in this case and implement it for all errors in //! `core` and `alloc`. //! //! Error might go back be in libcore again: //! https://doc.rust-lang.org/nightly/src/std/error.rs.html#5 //! //! Once this happens, this file will become obsolete and can be removed. use core::any::TypeId; use core::fmt::{Debug, Display}; /// A copy of the Error trait definition from libstd (for now). pub trait Error: Debug + Display { /// Human-readable error description fn description(&self) -> &str { "description() is deprecated; use Display" } /// The lower-level source of this error, if any. fn cause(&self) -> Option<&dyn Error> { self.source() } /// The lower-level source of this error, if any. fn source(&self) -> Option<&(dyn Error + 'static)> { None } /// type id fn type_id(&self) -> TypeId where Self: 'static, { TypeId::of::() } } impl Error for core::alloc::LayoutErr {} impl Error for core::array::TryFromSliceError {} impl Error for core::cell::BorrowError {} impl Error for core::cell::BorrowMutError {} impl Error for core::char::CharTryFromError {} impl Error for core::char::DecodeUtf16Error {} impl Error for core::char::ParseCharError {} impl Error for core::fmt::Error {} impl Error for core::num::ParseFloatError {} impl Error for core::num::ParseIntError {} impl Error for core::num::TryFromIntError {} impl Error for core::str::ParseBoolError {} impl Error for core::str::Utf8Error {} impl Error for alloc::string::ParseError {} #[cfg(feature = "unstable")] impl Error for core::alloc::AllocError {} #[cfg(feature = "unstable")] impl Error for alloc::collections::TryReserveError {} custom_error-1.9.2/src/lib.rs000064400000000000000000000727000000000000000142460ustar 00000000000000#![forbid(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "unstable", feature(allocator_api, try_reserve))] //! # Rust custom error //! This crate contains a [`custom_error!`](custom_error) macro that should make it easier to define custom errors //! without having to write a lot of boilerplate code. //! //! ## Crate features //! //! ### nostd //! //! This crate supports [`no-std`](https://docs.rust-embedded.org/book/intro/no-std.html): it can be built without the rust standard library. //! To use the no-std version, disable the std feature in this crate in your `Cargo.toml` : //! //! ```toml //! [dependencies] //! custom_error = { version = "1", default-features = false } # nostd compatible //! ``` //! //! ### unstable //! //! There is also an `unstable` feature //! that implements the nostd error trait on `AllocError` and `TryReserveError` in no-std. extern crate alloc; /// We define our own Error trait for no_std. #[cfg(not(feature = "std"))] mod error; /// Use the Error trait from `std` by default. #[cfg(feature = "std")] pub use std::error::Error; /// Use our own Error trait for `no_std`. #[cfg(not(feature = "std"))] pub use error::Error; /// Constructs a custom error type. /// /// # Examples /// /// ### Simple error /// /// For an error with multiple cases you can generate an enum: /// ``` /// use custom_error::custom_error; /// /// custom_error! { pub MyError /// Bad = "Something bad happened", /// Terrible = "This is a very serious error!!!" /// } /// assert_eq!("Something bad happened", MyError::Bad.to_string()); /// assert_eq!( /// "This is a very serious error!!!", /// MyError::Terrible.to_string() /// ); /// ``` /// /// For an error with a single case you can generate a struct: /// ``` /// use custom_error::custom_error; /// /// custom_error! { pub MyError{} = "Something bad happened" } /// assert_eq!("Something bad happened", MyError {}.to_string()); /// ``` /// /// ### Custom error with parameters /// ``` /// use custom_error::custom_error; /// /// custom_error!{SantaError /// BadChild{name:String, foolishness:u8} = "{name} has been bad {foolishness} times this year", /// TooFar = "The location you indicated is too far from the north pole", /// InvalidReindeer{legs:u8} = "The reindeer has {legs} legs" /// } /// /// assert_eq!( /// "Thomas has been bad 108 times this year", /// SantaError::BadChild{ /// name: "Thomas".into(), /// foolishness: 108 /// }.to_string()); /// /// assert_eq!( /// "The location you indicated is too far from the north pole", /// SantaError::TooFar.to_string() /// ); /// /// assert_eq!( /// "The reindeer has 8 legs", /// SantaError::InvalidReindeer{legs:8}.to_string() /// ); /// ``` /// /// ### Automatic conversion from other error types /// /// You can add a special field named `source` to your error types. /// /// Use this field to include the lower-level source of the error. /// It will be used in the error /// [`source()`](https://doc.rust-lang.org/std/error/trait.Error.html#method.source) /// method, and automatic conversion from the source error type to your custom error type will be possible /// (your error type will implement `From`). /// /// #### limitations /// * You cannot have several error cases that contain a single *source* field of the same type: /// `custom_error!(E A{source:X} B{source:Y})` is allowed, but /// `custom_error!(E A{source:X} B{source:X})` is forbidden. /// * If the source field is not the only one, then the automatic conversion /// will not be implemented. /// * The type of source must be valid for the `'static` lifetime (because of the type signature of /// the [`source()`](https://doc.rust-lang.org/std/error/trait.Error.html#method.source) method). /// You can still have a field with a non-static type that you will not name `source`, /// and manually implement the error conversion from this type to your error type. /// /// ``` /// # #[cfg(feature = "std")] { /// use std::{fs::File, io, io::Read, result::Result}; /// use custom_error::custom_error; /// /// custom_error! {MyError /// IO{source: io::Error} = "input/output error", /// Unknown = "unknown error" /// } /// /// fn read_file(filename: &str) -> Result { /// let mut res = String::new(); /// File::open(filename)?.read_to_string(&mut res)?; /// Ok(res) /// } /// /// assert_eq!( /// "input/output error", /// read_file("/i'm not a file/").unwrap_err().to_string() /// ) /// # } /// ``` /// /// ### Custom formatting function for error messages /// /// If the format string syntax is not enough to express your complex error formatting needs, /// you can use custom code to generate your error description. /// /// ``` /// use custom_error::custom_error; /// /// static LANG: &'static str = "FR"; /// /// # fn localize(_:&str, _:&str) -> &'static str { "Un problème est survenu" } /// /// custom_error! { pub MyError /// Problem = @{ localize(LANG, "A problem occurred") }, /// } /// /// assert_eq!("Un problème est survenu", MyError::Problem.to_string()); /// ``` /// /// ``` /// # #[cfg(feature = "std")] { /// use std::io::Error; /// use std::io::ErrorKind::*; /// use custom_error::custom_error; /// /// custom_error! { pub MyError /// Io{source: Error} = @{ /// match source.kind() { /// NotFound => "The file does not exist", /// TimedOut => "The operation timed out", /// _ => "unknown error", /// } /// }, /// } /// /// assert_eq!( /// "The operation timed out", /// MyError::Io { /// source: TimedOut.into() /// } /// .to_string() /// ); /// # } /// ``` /// /// ### Derive traits for your errors /// You can add custom [attributes](https://doc.rust-lang.org/rust-by-example/attribute.html) /// at the beginning of the macro invocation. This allows you to derive traits for your error: /// /// ``` /// use custom_error::custom_error; /// /// custom_error! { /// #[derive(PartialEq,PartialOrd)] /// ErrLevel Small = "Don't worry", Serious = "Aaargh!!!" /// } /// assert_ne!(ErrLevel::Small, ErrLevel::Serious); /// assert!(ErrLevel::Small < ErrLevel::Serious); /// ``` #[macro_export] macro_rules! custom_error { ( $( #[$meta_attribute:meta] )* // Attributes, like #[derive(SomeTrait)] $visibility:vis // `pub` marker $errtype:ident // Name of the error type to generate $( < $( $type_param:tt // Optional type parameters for generic error types ),* > )? $( $( #[$field_meta:meta] )* // Meta-attributes for the variant, such as a doc comment $field:ident // Name of an error variant $( { $( $( #[$attr_meta:meta] )* // Meta-attributes for the attribute of the error variant $attr_name:ident // Name of an attribute of the error variant : $attr_type:ty // type of the attribute ),* } )? = $( @{ $($msg_fun:tt)* } )? $($msg:expr)? // The human-readable error message ),* $(,)* // Trailing comma ) => { $( #[$meta_attribute] )* #[derive(Debug)] $visibility enum $errtype $( < $($type_param),* > )* { $( $( #[$field_meta] )* $field $( { $( $( #[$attr_meta] )* $attr_name : $attr_type ),* } )* ),* } $crate::add_type_bounds! { ( $($($type_param),*)* ) (core::fmt::Debug + core::fmt::Display) { impl <} {> $crate::Error for $errtype $( < $($type_param),* > )* { fn source(&self) -> Option<&(dyn $crate::Error + 'static)> { #[allow(unused_variables, unreachable_code)] match self {$( $errtype::$field $( { $( $attr_name ),* } )* => { $( $( $crate::return_if_source!($attr_name, $attr_name) );* ;)* None } ),*} } } }} $crate::impl_error_conversion!{ ( $errtype $(< $($type_param),* >)* ) $([ $field, $($( $attr_name, $attr_name, $attr_type ),*),* ])* } $crate::add_type_bounds! { ( $($($type_param),*)* ) (alloc::string::ToString) { impl <} {> core::fmt::Display for $errtype $( < $($type_param),* > )* { fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { match self {$( $errtype::$field $( { $( $attr_name ),* } )* => { $(write!(formatter, "{}", ($($msg_fun)*) )?;)* $crate::display_message!(formatter, $($($attr_name),*),* | $($msg)*); Ok(()) } ),*} } } }} }; // Simple struct error ( $( #[$meta_attribute:meta] )* // Attributes, like #[derive(SomeTrait)] $visibility:vis // `pub` marker $errtype:ident // Name of the error type to generate $( < $( $type_param:tt // Optional type parameters for generic error types ),* > )? { $( $( #[$field_meta:meta] )* // Field meta attributes, such as doc comments $field_name:ident // Name of an attribute of the error variant : $field_type:ty // type of the attribute ),* } = $( @{ $($msg_fun:tt)* } )? $($msg:expr)? // The human-readable error message $(,)* // Trailing comma ) => { $( #[$meta_attribute] )* #[derive(Debug)] $visibility struct $errtype $( < $($type_param),* > )* { $( $( #[$field_meta] )* pub $field_name : $field_type ),* } $crate::add_type_bounds! { ( $($($type_param),*)* ) (core::fmt::Debug + core::fmt::Display) { impl <} {> $crate::Error for $errtype $( < $($type_param),* > )* { #[allow(unused_variables, unreachable_code)] fn source(&self) -> Option<&(dyn $crate::Error + 'static)> { #[allow(unused_variables, unreachable_code)] match self { $errtype { $( $field_name ),* } => { $({ $crate::return_if_source!($field_name, $field_name) });* None } } } } }} $crate::impl_error_conversion_for_struct!{ $errtype $(< $($type_param),* >)*, $( $field_name: $field_type ),* } $crate::add_type_bounds! { ( $($($type_param),*)* ) (alloc::string::ToString) { impl <} {> core::fmt::Display for $errtype $( < $($type_param),* > )* { fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { // make fields accessible with variables, so that we can // use them in custom error msg blocks without self $( let $field_name = &self.$field_name; )* $(write!(formatter, "{}", ($($msg_fun)*) )?;)* $crate::display_message!(formatter, $($field_name),* | $($msg)*); Ok(()) } } }} }; } #[doc(hidden)] #[macro_export] macro_rules! return_if_source { // Return the source if the attribute is called 'source' (source, $attr_name:ident) => {{ // Borrow is needed in order to support boxed errors // see: https://github.com/lovasoa/custom_error/issues/20 return Some(core::borrow::Borrow::borrow($attr_name)); }}; // If the attribute has a different name, return nothing ($_attr_name:ident, $_repeat:ident ) => { () }; } #[doc(hidden)] #[macro_export] macro_rules! impl_error_conversion { ( ( $($prefix:tt)* ) [ $($field_data:tt)* ] $($rest:tt)* ) => { $crate::impl_error_conversion!{$($prefix)*, $($field_data)*} $crate::impl_error_conversion!{ ($($prefix)*) $($rest)*} }; ( ( $($prefix:tt)* ) ) => {}; // implement From only when there is a single attribute and it is named 'source' ( $errtype:ident $( < $($type_param:tt),* > )*, $field:ident, source, $source:ident, $source_type:ty ) => { impl $( < $($type_param),* > )* From<$source_type> for $errtype $( < $($type_param),* > )* { fn from(source: $source_type) -> Self { $errtype::$field { source } } } }; ( $_errtype:ident $( < $($_errtype_type_param:tt),* > )*, $_field:ident, $( $_:ident, $_repeated:ident, $_type:ty ),* ) => {}; // If the list of fields is not a single field named 'source', do nothing } #[doc(hidden)] #[macro_export] macro_rules! impl_error_conversion_for_struct { ( ( $($prefix:tt)* ) ) => {}; // implement From only when there is a single field and it is named 'source' ( $errtype:ident, $( < $($type_param:tt),* > )* source: $source_type:ty ) => { impl $( < $($type_param),* > )* From<$source_type> for $errtype $( < $($type_param),* > )* { fn from(source: $source_type) -> Self { $errtype { source } } } }; ( $_errtype:ident $( < $($_errtype_type_param:tt),* > )*, $( $_field:ident: $_type:ty ),* ) => {}; // If the list of fields is not a single field named 'source', do nothing } #[doc(hidden)] #[macro_export] macro_rules! display_message { ($formatter:expr, $($attr:ident),* | $msg:expr) => { write!( $formatter, concat!($msg $(, "{", stringify!($attr), ":.0}" )*) $( , $attr = $attr.to_string() )* )?; }; ($formatter:expr, $($attr:ident),* | ) => {}; } /* This macro, given a list of generic parameters and type bounds, adds the type bounds to all generic type parameters (and leaves the generic lifetime parameters unchanged) */ #[doc(hidden)] #[macro_export] macro_rules! add_type_bounds { ( ( $typ:ident $(, $rest:tt)* ) // type parameter ( $($bounds:tt)* ) { $($prefix:tt)* } { $($suffix:tt)* } ) => { add_type_bounds!{ ( $(, $rest)* ) ( $($bounds)* ) { $($prefix)* $typ : $($bounds)*} { $($suffix)* } } }; ( ( $lifetime:tt $(, $rest:tt)* ) // lifetime parameter ( $($bounds:tt)* ) { $($prefix:tt)* } { $($suffix:tt)* } ) => { add_type_bounds!{ ( $(, $rest)* ) ( $($bounds)* ) { $($prefix)* $lifetime } { $($suffix)* } } }; ( ( , $($rest:tt)* ) // add the comma to the prefix ( $($bounds:tt)* ) { $($prefix:tt)* } { $($suffix:tt)* } ) => { add_type_bounds!{ ( $($rest)* ) ( $($bounds)* ) { $($prefix)* , } { $($suffix)* } } }; ( ( ) // no more generic params to consume ( $($bounds:tt)* ) { $($prefix:tt)* } { $($suffix:tt)* } ) => { $($prefix)* $($suffix)* } } #[cfg(all(test, feature = "std"))] mod tests { use std::error::Error; use std::str::FromStr; #[test] fn single_error_case() { custom_error!(MyError Bad="bad"); assert_eq!("bad", MyError::Bad.to_string()); } #[test] fn single_error_struct_case() { custom_error!(MyError {} = "bad"); assert_eq!("bad", MyError {}.to_string()); } #[test] #[allow(dead_code)] fn three_error_cases() { custom_error!(MyError NotPerfect=":/", Bad="bad", Awfull="arghhh"); assert_eq!("arghhh", MyError::Awfull.to_string()) } #[test] fn with_error_data() { custom_error!(MyError Bad = "bad", Catastrophic{broken_things:u8} = "{broken_things} things are broken" ); assert_eq!("bad", MyError::Bad.to_string()); assert_eq!( "9 things are broken", MyError::Catastrophic { broken_things: 9 }.to_string() ); } #[test] fn with_doc_comments_for_variants() { custom_error! {MyError /// A bad error Bad="bad", /// Terrible error Terrible="terrible" } assert!(MyError::Bad.source().is_none()); assert!(MyError::Terrible.source().is_none()); } #[test] #[allow(dead_code)] fn with_doc_comments_for_fields() { custom_error! {MyError Bad { /// Name of the bad field bad: &'static str } = "bad {bad}", Terrible { /// Name of the terrible field terrible: &'static str } = "bad {terrible}", } assert!(MyError::Bad { bad: "heh" }.source().is_none()); } #[test] fn enum_with_field_lifetime() { custom_error!(MyError Problem{description: &'static str} = "{description}" ); assert_eq!("bad", MyError::Problem { description: "bad" }.to_string()); } #[test] fn struct_with_field_lifetime() { custom_error!(MyError {description: &'static str} = "{}"); assert_eq!("bad", MyError { description: "bad" }.to_string()); } #[test] fn enum_with_derive() { custom_error! { #[derive(PartialEq, PartialOrd)] #[derive(Clone)] MyError A = "A", B{b:u8} = "B({b})", C = "C" }; // PartialEq assert_eq!(MyError::A, MyError::A); assert_eq!(MyError::B { b: 1 }, MyError::B { b: 1 }); assert_ne!(MyError::B { b: 0 }, MyError::B { b: 1 }); assert_ne!(MyError::A, MyError::B { b: 1 }); // PartialOrd assert!(MyError::A < MyError::B { b: 1 }); assert!(MyError::B { b: 1 } < MyError::B { b: 2 }); assert!(MyError::B { b: 2 } < MyError::C); // Clone assert_eq!(MyError::A.clone(), MyError::A); } #[test] fn enum_with_pub_derive_and_doc_comment() { custom_error! { ///Doc comment #[derive(PartialEq, PartialOrd)] pub MyError A = "A" }; assert_eq!(MyError::A, MyError::A); } #[test] fn enum_with_box_dyn_source() { // See https://github.com/lovasoa/custom_error/issues/20 custom_error! {pub MyError Dynamic { source: Box } = "dynamic", } let err = u8::from_str("x").unwrap_err(); assert_eq!( err.to_string(), MyError::Dynamic { source: Box::new(err) } .source() .unwrap() .to_string() ); } #[test] fn struct_with_box_dyn_source() { custom_error! {pub MyError { source: Box } = "dynamic", } let err = u8::from_str("x").unwrap_err(); assert_eq!( err.to_string(), MyError { source: Box::new(err) } .source() .unwrap() .to_string() ); } #[test] fn struct_with_public_field() { mod inner { custom_error! {pub MyError {x: &'static str} = "{}"} } assert_eq!("hello", inner::MyError { x: "hello" }.to_string()); } #[test] fn struct_with_field_documentation() { custom_error! { pub MyError { /// This is a doc comment x: &'static str } = "{}" } assert_eq!("hello", MyError { x: "hello" }.to_string()); } #[test] fn struct_with_error_data() { custom_error!(MyError { broken_things: u8 } = "{broken_things} things are broken"); assert_eq!( "9 things are broken", MyError { broken_things: 9 }.to_string() ); } #[test] fn struct_with_derive() { custom_error!( #[derive(PartialEq, PartialOrd, Clone, Default)] MyError { x: u8 } = ":(" ); assert_eq!(MyError { x: 9 }, MyError { x: 9 }); // Has PartialEq assert_eq!(MyError { x: 0 }.clone(), MyError { x: 0 }); // Has Clone assert_eq!(MyError::default(), MyError { x: 0 }); // Has Default assert!(MyError { x: 0 } < MyError { x: 1 }); // Has PartialOrd } #[test] fn with_multiple_error_data() { custom_error!(E X{a:u8, b:u8, c:u8} = "{c} {b} {a}"); assert_eq!("3 2 1", E::X { a: 1, b: 2, c: 3 }.to_string()); } #[test] fn struct_with_multiple_error_data() { custom_error!( E { a: u8, b: u8, c: u8 } = "{c} {b} {a}" ); assert_eq!("3 2 1", E { a: 1, b: 2, c: 3 }.to_string()); } #[test] fn source() { use std::{error::Error, io}; custom_error!(E A{source: io::Error}=""); let source: io::Error = io::ErrorKind::InvalidData.into(); assert_eq!( source.to_string(), E::A { source }.source().unwrap().to_string() ); } #[test] fn struct_source() { use std::{error::Error, io}; custom_error!(E { source: io::Error } = ""); let source: io::Error = io::ErrorKind::InvalidData.into(); assert_eq!( source.to_string(), E { source }.source().unwrap().to_string() ); } #[test] fn from_source() { use std::io; custom_error!(E A{source: io::Error}="bella vita"); let source = io::Error::from(io::ErrorKind::InvalidData); assert_eq!("bella vita", E::from(source).to_string()); } #[test] fn struct_from_source() { use std::io; custom_error!(E { source: io::Error } = "bella vita"); let source = io::Error::from(io::ErrorKind::InvalidData); assert_eq!("bella vita", E::from(source).to_string()); } #[test] #[allow(dead_code)] fn with_source_and_others() { use std::{error::Error, io}; custom_error!(MyError Zero="", One{x:u8}="", Two{x:u8, source:io::Error}="{x}"); fn source() -> io::Error { io::ErrorKind::AlreadyExists.into() } let my_err = MyError::Two { x: 42, source: source(), }; assert_eq!("42", my_err.to_string()); assert_eq!(source().to_string(), my_err.source().unwrap().to_string()); } #[test] #[allow(dead_code)] fn struct_with_source_and_others() { use std::{error::Error, io}; custom_error!( MyError { x: u8, source: io::Error } = "{x}" ); fn source() -> io::Error { io::ErrorKind::AlreadyExists.into() } let my_err = MyError { x: 42, source: source(), }; assert_eq!("42", my_err.to_string()); assert_eq!(source().to_string(), my_err.source().unwrap().to_string()); } #[test] fn pub_error() { mod my_mod { custom_error! {pub MyError Case1="case1"} } assert_eq!("case1", my_mod::MyError::Case1.to_string()); } #[test] fn pub_error_struct() { mod my_mod { custom_error! {pub MyError{} = "case1"} } assert_eq!("case1", my_mod::MyError {}.to_string()); } #[test] fn pub_error_struct_fields() { mod my_mod { custom_error! {pub MyError{x:u8} = "x={x}"} } assert_eq!("x=9", my_mod::MyError { x: 9 }.to_string()); } #[test] fn generic_error() { custom_error! {MyError E1{x:X,y:Y}="x={x} y={y}", E2="e2"} assert_eq!("x=42 y=42", MyError::E1 { x: 42u8, y: 42u8 }.to_string()); assert_eq!("e2", MyError::E2::.to_string()); } #[test] fn generic_error_struct() { custom_error! {MyError{x:X,y:Y}="x={x} y={y}"} assert_eq!("x=42 y=42", MyError { x: 42u8, y: 42u8 }.to_string()); } #[test] fn single_error_case_with_braces() { custom_error! {MyError Bad="bad"} assert_eq!("bad", MyError::Bad.to_string()); } #[test] fn single_error_struct_case_with_braces() { custom_error! {MyError{} ="bad"} assert_eq!("bad", MyError {}.to_string()) } #[test] fn trailing_comma() { custom_error! {MyError1 A="a",} custom_error! {MyError2 A="a", B="b",} assert_eq!("a", MyError1::A.to_string()); assert_eq!("a", MyError2::A.to_string()); assert_eq!("b", MyError2::B.to_string()); } #[test] fn with_custom_formatting() { custom_error! {MyError Complex{a:u8, b:u8} = @{ if a+b == 0 { "zero".to_string() } else { (a+b).to_string() } }, Simple = "simple" } assert_eq!("zero", MyError::Complex { a: 0, b: 0 }.to_string()); assert_eq!("3", MyError::Complex { a: 2, b: 1 }.to_string()); assert_eq!("simple", MyError::Simple.to_string()); } #[test] fn struct_with_custom_formatting() { custom_error! {MyError{a:u8, b:u8} = @{ if a+b == 0 { "zero".to_string() } else { (a+b).to_string() } } } assert_eq!("zero", MyError { a: 0, b: 0 }.to_string()); assert_eq!("3", MyError { a: 2, b: 1 }.to_string()); } #[test] fn custom_format_source() { use std::io; custom_error! {MyError Io{source:io::Error} = @{format!("IO Error occurred: {:?}", source.kind())} } assert_eq!( "IO Error occurred: Interrupted", MyError::Io { source: io::ErrorKind::Interrupted.into() } .to_string() ) } #[test] fn struct_custom_format_source() { use std::io; custom_error! {MyError{source:io::Error} = @{format!("IO Error occurred: {:?}", source.kind())} } assert_eq!( "IO Error occurred: Interrupted", MyError { source: io::ErrorKind::Interrupted.into() } .to_string() ) } #[test] fn lifetime_source_param() { #[derive(Debug)] struct SourceError<'my_lifetime> { x: &'my_lifetime str, } impl<'a> std::fmt::Display for SourceError<'a> { fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) } } impl<'a> std::error::Error for SourceError<'a> {} custom_error! { MyError<'source_lifetime> Sourced { lifetimed : SourceError<'source_lifetime> } = @{ lifetimed.x }, Other { source: std::fmt::Error } = "other error" } let sourced = MyError::Sourced { lifetimed: SourceError { x: "I am the source", }, }; assert_eq!("I am the source", sourced.to_string()); let other_err: MyError = std::fmt::Error.into(); assert_eq!("other error", other_err.to_string()); } #[test] fn struct_lifetime_source_param() { #[derive(Debug)] struct SourceError<'my_lifetime> { x: &'my_lifetime str, } impl<'a> std::fmt::Display for SourceError<'a> { fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) } } impl<'a> std::error::Error for SourceError<'a> {} custom_error! { MyError<'source_lifetime>{ lifetimed : SourceError<'source_lifetime> } = @{ lifetimed.x },} let sourced = MyError { lifetimed: SourceError { x: "I am the source", }, }; assert_eq!("I am the source", sourced.to_string()); } #[test] fn lifetime_param_and_type_param() { #[derive(Debug)] struct MyType<'a, T> { data: &'a str, _y: T, } custom_error! { MyError<'a,T> X { d: MyType<'a,T> } = @{ format!("error x: {}", d.data) }, Y { d: T } = "error y" } let err = MyError::X { d: MyType { data: "hello", _y: 42i8, }, }; assert_eq!("error x: hello", err.to_string()); let err_y = MyError::Y { d: String::from("my string"), }; assert_eq!("error y", err_y.to_string()); } #[test] fn struct_lifetime_param_and_type_param() { #[derive(Debug)] struct MyType<'a, T> { data: &'a str, _y: T, } custom_error! { MyError<'a,T> { d: MyType<'a,T> } = @{ format!("error x: {}", d.data) } } let err = MyError { d: MyType { data: "hello", _y: 42i8, }, }; assert_eq!("error x: hello", err.to_string()); } } custom_error-1.9.2/tests/tests.rs000064400000000000000000000031310000000000000152050ustar 00000000000000#![cfg(feature = "std")] #[macro_use] extern crate custom_error; use std::{fs::File, io, io::ErrorKind, io::Read, num::ParseIntError, result::Result}; custom_error! { OpenFileError NotFound{filename: String} = "Tried to open '{filename}', but it doesn't exist", Other = "An unknown I/O error occured.", } /// Opens a file with a verbose error on failure fn open_file_verbose(filename: &str) -> Result { File::open(filename).map_err(|e| match e.kind() { ErrorKind::NotFound => OpenFileError::NotFound { filename: filename.to_string(), }, _ => OpenFileError::Other, }) } custom_error! {FileParseError Open{source: OpenFileError} = @{ source }, // Chained custom error Io{source: io::Error} = "I/O error", Format{source: ParseIntError} = "the file does not contain a valid integer", TooLarge{value:u8} = "the number in the file ({value}) is too large" } fn parse_hex_file(filename: &str) -> Result { let mut contents = String::new(); // The '?' notation can convert from generic errors to our custom error type open_file_verbose(filename)?.read_to_string(&mut contents)?; let value = u8::from_str_radix(&contents, 16)?; if value > 42 { Err(FileParseError::TooLarge { value }) } else { Ok(value) } } #[test] fn main() { let parse_result = parse_hex_file("/i'm not a file/"); assert_eq!( "Tried to open '/i'm not a file/', but it doesn't exist", parse_result.unwrap_err().to_string() ); }