Skip to content

Commit

Permalink
feat: ErrorSetMarker and Coerce trait
Browse files Browse the repository at this point in the history
  • Loading branch information
mcmah309 committed Jul 19, 2024
1 parent de70f84 commit ee39fd2
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 135 deletions.
109 changes: 21 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


A concise way to define errors and ergonomically coerce a subset into a superset with with just `.into()` or `?`.
Managing errors is no longer a tedious process.

`error_set` was inspired by zig's [error set](https://ziglang.org/documentation/master/#Error-Set-Type)
and works functionally the same.
Expand Down Expand Up @@ -394,110 +395,42 @@ As mentioned, any above subset can be converted into a superset with `.into()` o
<summary>Base Functionality In Action</summary>

```rust
fn main() {
let book_section_parsing_error = BookSectionParsingError::MissingName;
let book_parsing_error: BookParsingError = book_section_parsing_error.coerce(); // `.coerce()` == `.into()`
assert!(matches!(book_parsing_error, BookParsingError::MissingName));
let media_error: MediaError = book_parsing_error.coerce(); // `.coerce()` == `.into()`
assert!(matches!(media_error, MediaError::MissingName));

let io_error =std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
let result_download_error: Result<(), DownloadError> = Err(io_error).coerce(); // `.coerce()` == `.map_err(Into::into)`
let result_media_error: Result<(), MediaError> = result_download_error.coerce(); // `.coerce()` == `.map_err(Into::into)`
assert!(matches!(result_media_error, Err(MediaError::IoError(_))));
}
```
</details>

<details>
Here we can easily define all the different error states a function could exit with. Note this example is verbose as not all error states have downstream handlers that care about the error type, but imagine it so.
<summary>More Intricate Example</summary>
use error_set::error_set;

```rust
error_set::error_set! {
error_set! {
MediaError = {
IoError(std::io::Error)
} || BookParsingError || DownloadError || UploadError;
} || BookParsingError || DownloadError || ParseUploadError;
BookParsingError = {
MissingContent,
BookAccess(std::io::Error),
MissingBookDescription,
CouldNotReadBook(std::io::Error),
} || BookSectionParsingError;
BookSectionParsingError = {
MissingName,
NoContents,
};
DownloadError = {
InvalidUrl,
CouldNotSaveBook(std::io::Error),
};
UploadError = {
ParseUploadError = {
MaximumUploadSizeReached,
TimedOut,
AuthenticationFailed,
};
}

fn parse_book(file_path: &str) -> Result<String, BookParsingError> {
let mut file = File::open(file_path).coerce::<BookParsingError>()?;
let mut content = String::new();
file.read_to_string(&mut content).coerce::<BookParsingError>()?;
if content.is_empty() {
Err(BookParsingError::MissingContent)
} else {
Ok(content)
}
}

fn download_book(url: &str, save_path: &str) -> Result<(), DownloadError> {
if url.is_empty() {
Err(DownloadError::InvalidUrl)
} else {
let simulated_book_content = "This is a downloaded book content.";
let mut file = File::create(save_path).coerce::<DownloadError>()?;
file.write_all(simulated_book_content.as_bytes()).coerce::<DownloadError>()?;
Ok(())
}
}

fn upload_content(content: &str) -> Result<(), UploadError> {
let auth = true;
if !auth { // Simulate auth
return Err(UploadError::AuthenticationFailed);
}
let time_out = false;
if !time_out { // Simulate timeout uploading
return Err(UploadError::TimedOut);
}
if content.len() > 1024 { // Simulate an upload size limit
Err(UploadError::MaximumUploadSizeReached)
} else {
println!("Book uploaded successfully.");
Ok(())
}
}

fn process_book(download_path: &str, download_url: &str) -> Result<String, MediaError> {
download_book(download_url, download_path).coerce::<MediaError>()?;
let content = parse_book(download_path).coerce::<MediaError>()?;
const MAX_RETRIES: u8 = 3;
let mut current_retries = 0;
match upload_content(&content) {
Err(UploadError::TimedOut) => {
while current_retries < MAX_RETRIES {
current_retries += 1;
if let Ok(_) = upload_content(&content) {
break;
}
}
}
Err(e) => return Err(e.coerce()),
_ => (),
}
fs::remove_file(download_path).coerce::<MediaError>()?;
Ok(content)
}

fn main() {
match process_book("downloaded_book.txt", "http://example.com/book") {
Ok(content) => println!("Book processed successfully: {}", content),
Err(e) => eprintln!("An error occurred: {:?}", e),
}
let book_section_parsing_error: BookSectionParsingError = BookSectionParsingError::MissingName;
let book_parsing_error: BookParsingError = book_section_parsing_error.into();
assert!(matches!(book_parsing_error, BookParsingError::MissingName));
let media_error: MediaError = book_parsing_error.into();
assert!(matches!(media_error, MediaError::MissingName));

let io_error = std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
let result_download_error: Result<(), DownloadError> = Err(io_error).coerce(); // `.coerce()` == `.map_err(Into::into)`
let result_media_error: Result<(), MediaError> = result_download_error.coerce(); // `.coerce()` == `.map_err(Into::into)`
assert!(matches!(result_media_error, Err(MediaError::IoError(_))));
}
```
</details>
2 changes: 0 additions & 2 deletions impl/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ fn add_enum(error_enum_node: &ErrorEnumGraphNode, token_stream: &mut TokenStream
pub enum #enum_name {
#error_variant_tokens
}

impl error_set::ErrorSetMarker for #enum_name {}
});
}

Expand Down
22 changes: 2 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,14 @@

pub use error_set_impl::*;

pub trait Coerce<T> {
fn coerce(self) -> T;
}

pub trait CoerceResult<T, E1> {
fn coerce<E2: From<E1> + std::error::Error>(self) -> Result<T,E2>;
fn coerce<E2: From<E1>>(self) -> Result<T,E2>;
}

impl<T, E1> CoerceResult<T,E1> for Result<T, E1>
where
E1: std::error::Error,
{
#[inline(always)]
fn coerce<E2: From<E1> + std::error::Error>(self) -> Result<T, E2> {
fn coerce<E2: From<E1>>(self) -> Result<T, E2> {
self.map_err(Into::<E2>::into)
}
}
pub trait ErrorSetMarker {}

impl<E1, E2> Coerce<E2> for E1
where
E2: From<E1> + std::error::Error,
E1: std::error::Error + ErrorSetMarker,
{
#[inline(always)]
fn coerce(self) -> E2 {
self.into()
}
}
46 changes: 21 additions & 25 deletions tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ pub mod error_sources_of_same_name {
};
}


#[test]
fn test() {
let x = X::IoError(std::io::Error::new(
Expand Down Expand Up @@ -133,7 +132,7 @@ pub mod error_sources_of_different_names {

#[cfg(test)]
pub mod readme_example {
use error_set::error_set;
use error_set::{error_set, CoerceResult};

error_set! {
MediaError = {
Expand Down Expand Up @@ -169,17 +168,15 @@ pub mod readme_example {

#[test]
fn test() {
let book_section_parsing_error = BookSectionParsingError::MissingName;
let book_section_parsing_error: BookSectionParsingError =
BookSectionParsingError::MissingName;
let book_parsing_error: BookParsingError = book_section_parsing_error.into();
assert!(matches!(
book_parsing_error,
BookParsingError::MissingName
));
assert!(matches!(book_parsing_error, BookParsingError::MissingName));
let media_error: MediaError = book_parsing_error.into();
assert!(matches!(media_error, MediaError::MissingName));

let io_error =std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
let result_download_error: Result<(), DownloadError> = Err(io_error).map_err(Into::into);
let io_error = std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
let result_download_error: Result<(), DownloadError> = Err(io_error).coerce();
let result_media_error: Result<(), MediaError> = result_download_error.map_err(Into::into);
assert!(matches!(result_media_error, Err(MediaError::IoError(_))));
}
Expand Down Expand Up @@ -216,14 +213,11 @@ pub mod readme_example_aggregation {
fn test() {
let book_section_parsing_error = BookSectionParsingError::MissingName;
let book_parsing_error: BookParsingError = book_section_parsing_error.into();
assert!(matches!(
book_parsing_error,
BookParsingError::MissingName
));
assert!(matches!(book_parsing_error, BookParsingError::MissingName));
let media_error: MediaError = book_parsing_error.into();
assert!(matches!(media_error, MediaError::MissingName));

let io_error =std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
let io_error = std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
let result_download_error: Result<(), DownloadError> = Err(io_error).map_err(Into::into);
let result_media_error: Result<(), MediaError> = result_download_error.map_err(Into::into);
assert!(matches!(result_media_error, Err(MediaError::IoError(_))));
Expand Down Expand Up @@ -254,23 +248,23 @@ pub mod coerce_macro {
};
}

fn setx_result() -> Result<(),SetX> {
fn setx_result() -> Result<(), SetX> {
Err(SetX::A)
}
fn setx() -> SetX {
SetX::A
}

fn setx_result_to_sety_result_coerce_return() -> Result<(),SetY> {
fn setx_result_to_sety_result_coerce_return() -> Result<(), SetY> {
let _ok = coerce!(setx_result() => {
Ok(ok) => ok,
Err(SetX::X) => (), // handle
{ Err(SetX) => return Err(SetY) }
});
Ok(())
}
fn setx_result_to_sety_result_coerce() -> Result<(),SetY> {
let result: Result<(),SetY> = coerce!(setx_result() => {
fn setx_result_to_sety_result_coerce() -> Result<(), SetY> {
let result: Result<(), SetY> = coerce!(setx_result() => {
Ok(_) => Err(SetY::D),
Err(SetX::X) => Err(SetY::F), // handle
{ Err(SetX) => Err(SetY) }
Expand Down Expand Up @@ -326,7 +320,10 @@ pub mod coerce_macro {

#[test]
fn test() {
assert_eq!(setx_result_to_sety_result_coerce_return().unwrap_err(), SetY::A);
assert_eq!(
setx_result_to_sety_result_coerce_return().unwrap_err(),
SetY::A
);
assert_eq!(setx_result_to_sety_result_coerce().unwrap_err(), SetY::A);
assert_eq!(setx_to_sety_coerce(), SetY::A);
assert_eq!(setx_to_sety_coerce_return(), SetY::A);
Expand All @@ -336,7 +333,7 @@ pub mod coerce_macro {
}

pub mod documentation {
use error_set::{error_set, Coerce, CoerceResult};
use error_set::{error_set, CoerceResult};

error_set! {
/// This is a MediaError doc
Expand Down Expand Up @@ -375,15 +372,15 @@ pub mod documentation {
#[test]
fn test() {
let book_section_parsing_error = BookSectionParsingError::MissingNameArg;
let book_parsing_error: BookParsingError = book_section_parsing_error.coerce();
let book_parsing_error: BookParsingError = book_section_parsing_error.into();
assert!(matches!(
book_parsing_error,
BookParsingError::MissingNameArg
));
let media_error: MediaError = book_parsing_error.coerce();
let media_error: MediaError = book_parsing_error.into();
assert!(matches!(media_error, MediaError::MissingNameArg));

let io_error =std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
let io_error = std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
let result_download_error: Result<(), DownloadError> = Err(io_error).coerce();
let result_media_error: Result<(), MediaError> = result_download_error.coerce();
assert!(matches!(result_media_error, Err(MediaError::IoError(_))));
Expand All @@ -399,7 +396,6 @@ pub mod should_not_compile_tests {
t.compile_fail("tests/trybuild/depends_on_itself.rs");
}


#[test]
fn multiple_same_sources() {
let t = trybuild::TestCases::new();
Expand All @@ -417,4 +413,4 @@ pub mod should_not_compile_tests {
let t = trybuild::TestCases::new();
t.compile_fail("tests/trybuild/recursive_dependency.rs");
}
}
}

0 comments on commit ee39fd2

Please sign in to comment.