From 04b8ee1bf48b8ad8ebabeeecbc2b256ebfc36fc7 Mon Sep 17 00:00:00 2001 From: sttk Date: Wed, 4 Dec 2024 22:18:46 +0900 Subject: [PATCH] new: added Err --- Cargo.toml | 1 + src/errs.rs | 701 ++++++++++++++++++ src/lib.rs | 6 + src/main.rs | 3 - .../lifetime_of_reason_in_dropped_errs.rs | 21 + .../lifetime_of_reason_in_dropped_errs.stderr | 12 + .../lifetime_of_returned_reason_in_errs.rs | 32 + ...lifetime_of_returned_reason_in_errs.stderr | 8 + tests/errs_test.rs | 121 +++ 9 files changed, 902 insertions(+), 3 deletions(-) create mode 100644 src/errs.rs create mode 100644 src/lib.rs delete mode 100644 src/main.rs create mode 100644 tests/compile_errors/lifetime_of_reason_in_dropped_errs.rs create mode 100644 tests/compile_errors/lifetime_of_reason_in_dropped_errs.stderr create mode 100644 tests/compile_errors/lifetime_of_returned_reason_in_errs.rs create mode 100644 tests/compile_errors/lifetime_of_returned_reason_in_errs.stderr create mode 100644 tests/errs_test.rs diff --git a/Cargo.toml b/Cargo.toml index 4566438..c81ae29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ categories = [] name = "sabi" [dependencies] +trybuild = "1.0.101" diff --git a/src/errs.rs b/src/errs.rs new file mode 100644 index 0000000..e717d00 --- /dev/null +++ b/src/errs.rs @@ -0,0 +1,701 @@ +// Copyright (C) 2024 Takayuki Sato. All Rights Reserved. +// This program is free software under MIT License. +// See the file LICENSE in this distribution for more details. + +use std::any; +use std::error; +use std::fmt; +use std::ptr; + +/// The error type used within the Sabi framework. +/// +/// It holds a value of any type that represents the reason for the error. +/// By the reason, it is possible to identify the error or retrieve the detailed information about +/// the error. +pub struct Err { + reason_container: *const ReasonContainer, +} + +impl Err { + /// Creates a new error with the value reprenents the reason. + /// + /// ```rust + /// use sabi::Err; + /// + /// #[derive(Debug)] + /// enum Reasons { + /// IllegalState { state: String }, + /// } + /// + /// let err = Err::new(Reasons::IllegalState { state: "bad state".to_string() }); + /// ``` + pub fn new(reason: R) -> Self + where + R: fmt::Debug + Send + Sync + 'static, + { + let boxed = Box::new(ReasonContainer::::new(reason)); + let ptr: ptr::NonNull = + unsafe { ptr::NonNull::new_unchecked(Box::into_raw(boxed)).cast::() }; + Self { + reason_container: ptr.as_ptr(), + } + } + + /// Checks if `R` is the type of the reason held by this error object. + /// + /// ```rust + /// use sabi::Err; + /// + /// #[derive(Debug)] + /// enum Reasons { + /// IllegalState { state: String }, + /// } + /// + /// let err = Err::new(Reasons::IllegalState { state: "bad state".to_string() }); + /// assert!(err.is_reason::()); + /// assert!(err.is_reason::() == false); + /// ``` + pub fn is_reason(&self) -> bool + where + R: fmt::Debug + Send + Sync + 'static, + { + let type_id = any::TypeId::of::(); + let is_fn = unsafe { (*self.reason_container).is_fn }; + is_fn(type_id) + } + + /// Gets the reason value if the type of the reason is `R`. + /// + /// ```rust + /// use sabi::Err; + /// + /// #[derive(Debug)] + /// enum Reasons { + /// IllegalState { state: String }, + /// } + /// + /// let err = Err::new(Reasons::IllegalState { state: "bad state".to_string() }); + /// match err.reason::() { + /// Ok(r) => match r { + /// Reasons::IllegalState { state } => println!("state = {state}"), + /// _ => { /* ... */ } + /// } + /// Err(err) => match err.reason::() { + /// Ok(s) => println!("string reason = {s}"), + /// Err(_err) => { /* ... */ } + /// } + /// } + /// ``` + pub fn reason(&self) -> Result<&R, &Self> + where + R: fmt::Debug + Send + Sync + 'static, + { + let type_id = any::TypeId::of::(); + let is_fn = unsafe { (*self.reason_container).is_fn }; + if is_fn(type_id) { + let typed_ptr = self.reason_container as *const ReasonContainer; + return Ok(unsafe { &((*typed_ptr).reason) }); + } + + Err(self) + } + + /// Checks the type of the reason held by this object, and if it matches, executes the provided + /// function. + /// + /// ```rust + /// use sabi::Err; + /// + /// #[derive(Debug)] + /// enum Reasons { + /// IllegalState { state: String }, + /// } + /// + /// let err = Err::new(Reasons::IllegalState { state: "bad state".to_string() }); + /// err.match_reason::(|r| match r { + /// Reasons::IllegalState { state } => println!("state = {state}"), + /// _ => { /* ... */ } + /// }) + /// .match_reason::(|s| { + /// println!("string reason = {s}"); + /// }); + /// ``` + pub fn match_reason(&self, func: fn(&R)) -> &Self + where + R: fmt::Debug + Send + Sync + 'static, + { + let type_id = any::TypeId::of::(); + let is_fn = unsafe { (*self.reason_container).is_fn }; + if is_fn(type_id) { + let typed_ptr = self.reason_container as *const ReasonContainer; + func(unsafe { &((*typed_ptr).reason) }); + } + + self + } +} + +impl Drop for Err { + fn drop(&mut self) { + let drop_fn = unsafe { (*self.reason_container).drop_fn }; + drop_fn(self.reason_container); + } +} + +impl fmt::Debug for Err { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let debug_fn = unsafe { (*self.reason_container).debug_fn }; + debug_fn(self.reason_container, f) + } +} + +impl fmt::Display for Err { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_fn = unsafe { (*self.reason_container).display_fn }; + display_fn(self.reason_container, f) + } +} + +impl error::Error for Err { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + None + } +} + +#[repr(C)] +struct ReasonContainer +where + R: fmt::Debug + Send + Sync + 'static, +{ + drop_fn: fn(*const ReasonContainer), + debug_fn: fn(*const ReasonContainer, f: &mut fmt::Formatter<'_>) -> fmt::Result, + display_fn: fn(*const ReasonContainer, f: &mut fmt::Formatter<'_>) -> fmt::Result, + is_fn: fn(any::TypeId) -> bool, + reason: R, +} + +impl ReasonContainer +where + R: fmt::Debug + Send + Sync + 'static, +{ + fn new(reason: R) -> Self { + Self { + drop_fn: drop_reason_container::, + debug_fn: debug_reason::, + display_fn: display_reason::, + is_fn: is_reason::, + reason, + } + } +} + +fn drop_reason_container(ptr: *const ReasonContainer) +where + R: fmt::Debug + Send + Sync + 'static, +{ + let typed_ptr = ptr as *mut ReasonContainer; + unsafe { + drop(Box::from_raw(typed_ptr)); + } +} + +fn debug_reason(ptr: *const ReasonContainer, f: &mut fmt::Formatter<'_>) -> fmt::Result +where + R: fmt::Debug + Send + Sync + 'static, +{ + let typed_ptr = ptr as *mut ReasonContainer; + write!( + f, + "{} {{ reason: {} {:?} }}", + any::type_name::(), + any::type_name::(), + unsafe { &(*typed_ptr).reason } + ) +} + +fn display_reason(ptr: *const ReasonContainer, f: &mut fmt::Formatter<'_>) -> fmt::Result +where + R: fmt::Debug + Send + Sync + 'static, +{ + let typed_ptr = ptr as *mut ReasonContainer; + write!(f, "{:?}", unsafe { &(*typed_ptr).reason }) +} + +fn is_reason(type_id: any::TypeId) -> bool +where + R: fmt::Debug + Send + Sync + 'static, +{ + any::TypeId::of::() == type_id +} + +#[cfg(test)] +mod tests_of_err { + use super::*; + use std::sync::{LazyLock, Mutex}; + + struct Logger { + log_vec: Vec, + } + impl Logger { + fn new() -> Self { + Self { + log_vec: Vec::::new(), + } + } + fn log(&mut self, s: &str) { + self.log_vec.push(s.to_string()); + } + fn assert_logs(&self, logs: &[&str]) { + assert_eq!(self.log_vec.len(), logs.len()); + for i in 0..self.log_vec.len() { + assert_eq!(self.log_vec[i], logs[i]); + } + } + } + + mod reason_is_enum_with_no_data { + use super::*; + + static LOGGER: LazyLock> = LazyLock::new(|| Mutex::new(Logger::new())); + + #[allow(dead_code)] + #[derive(Debug)] + enum Enum0 { + Reason0, + Other, + } + impl Drop for Enum0 { + fn drop(&mut self) { + LOGGER.lock().unwrap().log("drop Enum0"); + } + } + + fn create_err() -> Result<(), Err> { + let err = Err::new(Enum0::Reason0); + LOGGER.lock().unwrap().log("created Enum0"); + Err(err) + } + + fn consume_err() { + let err = create_err().unwrap_err(); + + assert!(err.is_reason::()); + assert!(!err.is_reason::()); + + match err.reason::() { + Ok(_) => panic!(), + Err(err) => match err.reason::() { + Ok(r) => match r { + Enum0::Reason0 => {} + _ => panic!(), + }, + Err(_) => panic!(), + }, + } + + err.match_reason::(|_s| { + panic!(); + }) + .match_reason::(|r| match r { + Enum0::Reason0 => {} + _ => panic!(), + }); + + assert_eq!(format!("{err:?}"), "sabi::errs::Err { reason: sabi::errs::tests_of_err::reason_is_enum_with_no_data::Enum0 Reason0 }"); + assert_eq!(format!("{err}"), "Reason0"); + + LOGGER.lock().unwrap().log("consumed Enum0"); + } + + #[test] + fn test_err_by_reaason0() { + consume_err(); + LOGGER.lock().unwrap().log("end"); + + LOGGER.lock().unwrap().assert_logs(&[ + "created Enum0", + "consumed Enum0", + "drop Enum0", + "end", + ]); + } + } + + mod reason_is_enum_with_one_value { + use super::*; + + static LOGGER: LazyLock> = LazyLock::new(|| Mutex::new(Logger::new())); + + #[allow(dead_code)] + #[derive(Debug)] + enum Enum1 { + Reason1(String), + Other, + } + impl Drop for Enum1 { + fn drop(&mut self) { + LOGGER.lock().unwrap().log("drop Enum1"); + } + } + + fn create_err() -> Result<(), Err> { + let err = Err::new(Enum1::Reason1("hello".to_string())); + LOGGER.lock().unwrap().log("created Enum1"); + Err(err) + } + + fn consume_err() { + let err = create_err().unwrap_err(); + + assert!(err.is_reason::()); + assert!(!err.is_reason::()); + + match err.reason::() { + Ok(_) => panic!(), + Err(err) => match err.reason::() { + Ok(r) => match r { + Enum1::Reason1(s) => assert_eq!(s, "hello"), + _ => panic!(), + }, + Err(_) => panic!(), + }, + } + + err.match_reason::(|_s| { + panic!(); + }) + .match_reason::(|r| match r { + Enum1::Reason1(s) => assert_eq!(s, "hello"), + _ => panic!(), + }); + + assert_eq!(format!("{err:?}"), "sabi::errs::Err { reason: sabi::errs::tests_of_err::reason_is_enum_with_one_value::Enum1 Reason1(\"hello\") }"); + assert_eq!(format!("{err}"), "Reason1(\"hello\")"); + + LOGGER.lock().unwrap().log("consumed Enum1"); + } + + #[test] + fn test_err_by_reaason1() { + consume_err(); + LOGGER.lock().unwrap().log("end"); + + LOGGER.lock().unwrap().assert_logs(&[ + "created Enum1", + "consumed Enum1", + "drop Enum1", + "end", + ]); + } + } + + mod reason_is_enum_with_two_values { + use super::*; + + static LOGGER: LazyLock> = LazyLock::new(|| Mutex::new(Logger::new())); + + #[allow(dead_code)] + #[derive(Debug)] + enum Enum2 { + Reason2(String, i32), + Other, + } + impl Drop for Enum2 { + fn drop(&mut self) { + LOGGER.lock().unwrap().log("drop Enum2"); + } + } + + fn create_err() -> Result<(), Err> { + let err = Err::new(Enum2::Reason2("hello".to_string(), 123)); + LOGGER.lock().unwrap().log("created Enum2"); + Err(err) + } + + fn consume_err() { + let err = create_err().unwrap_err(); + + assert!(err.is_reason::()); + assert!(!err.is_reason::()); + + match err.reason::() { + Ok(_) => panic!(), + Err(err) => match err.reason::() { + Ok(r) => match r { + Enum2::Reason2(s, n) => { + assert_eq!(s, "hello"); + assert_eq!(*n, 123); + } + _ => panic!(), + }, + Err(_) => panic!(), + }, + } + + err.match_reason::(|_s| { + panic!(); + }) + .match_reason::(|r| match r { + Enum2::Reason2(s, n) => { + assert_eq!(s, "hello"); + assert_eq!(*n, 123); + } + _ => panic!(), + }); + + assert_eq!(format!("{err:?}"), "sabi::errs::Err { reason: sabi::errs::tests_of_err::reason_is_enum_with_two_values::Enum2 Reason2(\"hello\", 123) }"); + assert_eq!(format!("{err}"), "Reason2(\"hello\", 123)"); + + LOGGER.lock().unwrap().log("consumed Enum2"); + } + + #[test] + fn test_err_by_reaason2() { + consume_err(); + LOGGER.lock().unwrap().log("end"); + + LOGGER.lock().unwrap().assert_logs(&[ + "created Enum2", + "consumed Enum2", + "drop Enum2", + "end", + ]); + } + } + + mod reason_is_enum_with_named_fields { + use super::*; + + static LOGGER: LazyLock> = LazyLock::new(|| Mutex::new(Logger::new())); + + #[allow(dead_code)] + #[derive(Debug)] + enum Enum3 { + Reason3 { x: i32, y: i32 }, + Other, + } + impl Drop for Enum3 { + fn drop(&mut self) { + LOGGER.lock().unwrap().log("drop Enum3"); + } + } + + fn create_err() -> Result<(), Err> { + let err = Err::new(Enum3::Reason3 { x: 123, y: 456 }); + LOGGER.lock().unwrap().log("created Enum3"); + Err(err) + } + + fn consume_err() { + let err = create_err().unwrap_err(); + + assert!(err.is_reason::()); + assert!(!err.is_reason::()); + + match err.reason::() { + Ok(_) => panic!(), + Err(err) => match err.reason::() { + Ok(r) => match r { + Enum3::Reason3 { x, y } => { + assert_eq!(*x, 123); + assert_eq!(*y, 456); + } + _ => panic!(), + }, + Err(_) => panic!(), + }, + } + + err.match_reason::(|_s| { + panic!(); + }) + .match_reason::(|r| match r { + Enum3::Reason3 { x, y } => { + assert_eq!(*x, 123); + assert_eq!(*y, 456); + } + _ => panic!(), + }); + + assert_eq!(format!("{err:?}"), "sabi::errs::Err { reason: sabi::errs::tests_of_err::reason_is_enum_with_named_fields::Enum3 Reason3 { x: 123, y: 456 } }"); + assert_eq!(format!("{err}"), "Reason3 { x: 123, y: 456 }"); + + LOGGER.lock().unwrap().log("consumed Enum3"); + } + + #[test] + fn test_err_by_reaason3() { + consume_err(); + LOGGER.lock().unwrap().log("end"); + + LOGGER.lock().unwrap().assert_logs(&[ + "created Enum3", + "consumed Enum3", + "drop Enum3", + "end", + ]); + } + } + + mod reason_is_number { + use super::*; + + fn create_err() -> Result<(), Err> { + let err = Err::new(123_456); + Err(err) + } + + fn consume_err() { + let err = create_err().unwrap_err(); + + assert!(err.is_reason::()); + assert!(!err.is_reason::()); + + match err.reason::() { + Ok(_) => panic!(), + Err(err) => match err.reason::() { + Ok(n) => assert_eq!(*n, 123_456), + Err(_) => panic!(), + }, + } + + err.match_reason::(|_s| { + panic!(); + }) + .match_reason::(|n| { + assert_eq!(*n, 123_456); + }); + + assert_eq!(format!("{err:?}"), "sabi::errs::Err { reason: i32 123456 }"); + assert_eq!(format!("{err}"), "123456"); + } + + #[test] + fn test_err_by_reaason4() { + consume_err(); + } + } + + mod reason_is_string { + use super::*; + + fn create_err() -> Result<(), Err> { + let err = Err::new("Hello, world!".to_string()); + Err(err) + } + + fn consume_err() { + let err = create_err().unwrap_err(); + + assert!(!err.is_reason::()); + assert!(err.is_reason::()); + + match err.reason::() { + Ok(s) => assert_eq!(s, "Hello, world!"), + Err(err) => match err.reason::() { + Ok(_n) => panic!(), + Err(_) => panic!(), + }, + } + + err.match_reason::(|s| { + assert_eq!(s, "Hello, world!"); + }) + .match_reason::(|_n| { + panic!(); + }); + + assert_eq!( + format!("{err:?}"), + "sabi::errs::Err { reason: alloc::string::String \"Hello, world!\" }" + ); + assert_eq!(format!("{err}"), "\"Hello, world!\""); + } + + #[test] + fn test_err_by_reaason5() { + consume_err(); + } + } + + mod reason_is_struct { + use super::*; + + static LOGGER: LazyLock> = LazyLock::new(|| Mutex::new(Logger::new())); + + #[allow(dead_code)] + #[derive(Debug)] + struct Struct6 { + s: String, + b: bool, + n: i64, + } + impl Drop for Struct6 { + fn drop(&mut self) { + LOGGER.lock().unwrap().log("drop Struct6"); + } + } + + fn create_err() -> Result<(), Err> { + let err = Err::new(Struct6 { + s: "hello".to_string(), + b: true, + n: 987, + }); + LOGGER.lock().unwrap().log("created Struct6"); + Err(err) + } + + fn consume_err() { + let err = create_err().unwrap_err(); + + assert!(err.is_reason::()); + assert!(!err.is_reason::()); + + match err.reason::() { + Ok(_) => panic!(), + Err(err) => match err.reason::() { + Ok(r) => { + assert_eq!(r.s, "hello"); + assert_eq!(r.b, true); + assert_eq!(r.n, 987); + } + Err(_) => panic!(), + }, + } + + err.match_reason::(|_s| { + panic!(); + }) + .match_reason::(|r| { + assert_eq!(r.s, "hello"); + assert_eq!(r.b, true); + assert_eq!(r.n, 987); + }); + + assert_eq!( + format!("{err:?}"), + "sabi::errs::Err { reason: sabi::errs::tests_of_err::reason_is_struct::Struct6 Struct6 { s: \"hello\", b: true, n: 987 } }" + ); + assert_eq!( + format!("{err}"), + "Struct6 { s: \"hello\", b: true, n: 987 }" + ); + + LOGGER.lock().unwrap().log("consumed Struct6"); + } + + #[test] + fn test_err_by_reaason6() { + consume_err(); + LOGGER.lock().unwrap().log("end"); + + LOGGER.lock().unwrap().assert_logs(&[ + "created Struct6", + "consumed Struct6", + "drop Struct6", + "end", + ]); + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1e7e086 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +// Copyright (C) 2024 Takayuki Sato. All Rights Reserved. +// This program is free software under MIT License. +// See the file LICENSE in this distribution for more details. + +mod errs; +pub use errs::Err; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/tests/compile_errors/lifetime_of_reason_in_dropped_errs.rs b/tests/compile_errors/lifetime_of_reason_in_dropped_errs.rs new file mode 100644 index 0000000..0662899 --- /dev/null +++ b/tests/compile_errors/lifetime_of_reason_in_dropped_errs.rs @@ -0,0 +1,21 @@ +use sabi::Err; + +#[derive(Debug)] +enum Reasons { + MyErr { msg: String, flag: bool }, +} + +fn return_err() -> Result<(), Err> { + let err = Err::new(Reasons::MyErr { msg: "hello".to_string(), flag: true }); + Err(err) +} + +fn consume_err(_err: Err) {} + +fn main() { + let err = return_err().unwrap_err(); + let reason = err.reason::().unwrap(); + println!("{:?}", reason); + consume_err(err); + println!("{:?}", reason); +} diff --git a/tests/compile_errors/lifetime_of_reason_in_dropped_errs.stderr b/tests/compile_errors/lifetime_of_reason_in_dropped_errs.stderr new file mode 100644 index 0000000..7ce3422 --- /dev/null +++ b/tests/compile_errors/lifetime_of_reason_in_dropped_errs.stderr @@ -0,0 +1,12 @@ +error[E0505]: cannot move out of `err` because it is borrowed + --> tests/compile_errors/lifetime_of_reason_in_dropped_errs.rs:19:15 + | +16 | let err = return_err().unwrap_err(); + | --- binding `err` declared here +17 | let reason = err.reason::().unwrap(); + | --- borrow of `err` occurs here +18 | println!("{:?}", reason); +19 | consume_err(err); + | ^^^ move out of `err` occurs here +20 | println!("{:?}", reason); + | ------ borrow later used here diff --git a/tests/compile_errors/lifetime_of_returned_reason_in_errs.rs b/tests/compile_errors/lifetime_of_returned_reason_in_errs.rs new file mode 100644 index 0000000..9ef3e96 --- /dev/null +++ b/tests/compile_errors/lifetime_of_returned_reason_in_errs.rs @@ -0,0 +1,32 @@ +use sabi::Err; + +#[derive(Debug)] +enum Reasons { + MyErr { msg: String, flag: bool }, +} + +fn return_reason() -> Result<(), Reasons> { + Err(Reasons::MyErr { msg: "hello".to_string(), flag: true }) +} + +fn forward_reason() -> Result<(), Reasons> { + return_reason() +} + +fn return_err() -> Result<(), Err> { + let err = Err::new(Reasons::MyErr { msg: "hello".to_string(), flag: true }); + Err(err) +} + +fn forward_reason_in_err() -> Result<&'static Reasons, &'static Err> { + let err = return_err().unwrap_err(); + err.reason::() +} + +fn main() { + let reason = forward_reason().unwrap_err(); + println!("{:?}", reason); + + let reason = forward_reason_in_err().unwrap_err(); + println!("{:?}", reason); +} diff --git a/tests/compile_errors/lifetime_of_returned_reason_in_errs.stderr b/tests/compile_errors/lifetime_of_returned_reason_in_errs.stderr new file mode 100644 index 0000000..4ea5726 --- /dev/null +++ b/tests/compile_errors/lifetime_of_returned_reason_in_errs.stderr @@ -0,0 +1,8 @@ +error[E0515]: cannot return value referencing local variable `err` + --> tests/compile_errors/lifetime_of_returned_reason_in_errs.rs:23:3 + | +23 | err.reason::() + | ---^^^^^^^^^^^^^^^^^^^^ + | | + | returns a value referencing data owned by the current function + | `err` is borrowed here diff --git a/tests/errs_test.rs b/tests/errs_test.rs new file mode 100644 index 0000000..fccf0bc --- /dev/null +++ b/tests/errs_test.rs @@ -0,0 +1,121 @@ +#[cfg(test)] +mod integration_tests_of_err { + use sabi; + + #[derive(Debug)] + enum IoErrs { + FileNotFound { path: String }, + NoPermission { path: String, r#mod: (u8, u8, u8) }, + } + + fn find_file() -> Result<(), sabi::Err> { + let err = sabi::Err::new(IoErrs::FileNotFound { + path: "/aaa/bbb/ccc".to_string(), + }); + Err(err) + } + + fn write_file() -> Result<(), sabi::Err> { + let err = sabi::Err::new(IoErrs::NoPermission { + path: "/aaa/bbb/ccc".to_string(), + r#mod: (6, 6, 6), + }); + Err(err) + } + + #[test] + fn should_create_err_and_identify_reason() { + match find_file() { + Ok(_) => panic!(), + Err(err) => match err.reason::() { + Ok(r) => match r { + IoErrs::FileNotFound { path } => { + assert_eq!(path, "/aaa/bbb/ccc"); + } + IoErrs::NoPermission { path: _, r#mod: _ } => panic!(), + }, + _ => panic!(), + }, + } + + match write_file() { + Ok(_) => panic!(), + Err(err) => match err.reason::() { + Ok(r) => match r { + IoErrs::FileNotFound { path: _ } => panic!(), + IoErrs::NoPermission { path, r#mod } => { + assert_eq!(path, "/aaa/bbb/ccc"); + assert_eq!(*r#mod, (6, 6, 6)); + } + }, + _ => panic!(), + }, + } + } + + #[test] + fn should_match_reason() { + match find_file() { + Ok(_) => panic!(), + Err(err) => err.match_reason::(|r| match r { + IoErrs::FileNotFound { path } => { + assert_eq!(path, "/aaa/bbb/ccc"); + } + IoErrs::NoPermission { path: _, r#mod: _ } => panic!(), + }), + }; + + match write_file() { + Ok(_) => panic!(), + Err(err) => err.match_reason::(|r| match r { + IoErrs::FileNotFound { path: _ } => panic!(), + IoErrs::NoPermission { path, r#mod } => { + assert_eq!(path, "/aaa/bbb/ccc"); + assert_eq!(*r#mod, (6, 6, 6)); + } + }), + }; + } + + #[test] + fn should_check_type_of_reason() { + let err = find_file().unwrap_err(); + assert!(err.is_reason::()); + assert!(!err.is_reason::()); + + let err = write_file().unwrap_err(); + assert!(err.is_reason::()); + assert!(!err.is_reason::()); + } + + #[test] + fn should_output_err_in_debug_format() { + let err = find_file().unwrap_err(); + //println!("{err:?}"); + assert_eq!(format!("{err:?}"), "sabi::errs::Err { reason: errs_test::integration_tests_of_err::IoErrs FileNotFound { path: \"/aaa/bbb/ccc\" } }"); + + let err = write_file().unwrap_err(); + //println!("{err:?}"); + assert_eq!(format!("{err:?}"), "sabi::errs::Err { reason: errs_test::integration_tests_of_err::IoErrs NoPermission { path: \"/aaa/bbb/ccc\", mod: (6, 6, 6) } }"); + } + + #[test] + fn should_output_err_in_display_format() { + let err = find_file().unwrap_err(); + //println!("{err}"); + assert_eq!(format!("{err}"), "FileNotFound { path: \"/aaa/bbb/ccc\" }"); + + let err = write_file().unwrap_err(); + //println!("{err}"); + assert_eq!( + format!("{err}"), + "NoPermission { path: \"/aaa/bbb/ccc\", mod: (6, 6, 6) }" + ); + } +} + +#[test] +fn compile_error_check() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile_errors/*_errs.rs"); +}