Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use dedicated error type for FromStr (#221) #235

Merged
merged 2 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- The `TryFrom`, `Add`, `Sub`, `BitAnd`, `BitOr`, `BitXor`, `Not` and `Neg`
derives now return a dedicated error type instead of a `&'static str` on
error.
- The `FromStr` derive now uses a dedicated `FromStrError` error type instead
of generating unique one each time.
- The `Display` derive (and other `fmt`-like ones) now uses
`#[display("...", (<expr>),*)]` syntax instead of
`#[display(fmt = "...", ("<expr>"),*)]`, and `#[display(bound(<bound>))]`
Expand Down
4 changes: 2 additions & 2 deletions impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ unicode-xid = { version = "0.2.2", optional = true }
rustc_version = { version = "0.4", optional = true }

[dev-dependencies]
derive_more = { path = "..", features = ["add", "not", "try_into"] }
derive_more = { path = "..", features = ["add", "from_str", "not", "try_into"] }
itertools = "0.10.5"

[badges]
Expand All @@ -54,7 +54,7 @@ deref_mut = []
display = ["syn/extra-traits", "dep:unicode-xid"]
error = ["syn/extra-traits"]
from = ["syn/extra-traits"]
from_str = ["dep:convert_case"]
from_str = []
index = []
index_mut = []
into = ["syn/extra-traits"]
Expand Down
19 changes: 4 additions & 15 deletions impl/doc/from_str.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ to the variant. If using a case insensitive match would give a unique variant
insensitive matching will be used, otherwise it will fall back to exact string
matching.

Since the string may not match any vairants an error type is needed so one
will be generated of the format `Parse{}Error`.
Since the string may not match any variants an error type is needed, so the
`derive_more::FromStrError` will be used for that purpose.

e.g. Given the following enum:

Expand All @@ -121,25 +121,14 @@ Code like this will be generated:
# Baz,
# }
#
#[derive(Clone, Debug, Eq, PartialEq)]
struct ParseEnumNoFieldsError;

impl std::fmt::Display for ParseEnumNoFieldsError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.write_str("invalid enum no fields")
}
}

impl std::error::Error for ParseEnumNoFieldsError {}

impl ::core::str::FromStr for EnumNoFields {
type Err = ParseEnumNoFieldsError;
type Err = ::derive_more::FromStrError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(match src.to_lowercase().as_str() {
"foo" => EnumNoFields::Foo,
"bar" => EnumNoFields::Bar,
"baz" => EnumNoFields::Baz,
_ => return Err(ParseEnumNoFieldsError{}),
_ => return Err(::derive_more::FromStrError::new("EnumNoFields")),
})
}
}
Expand Down
27 changes: 5 additions & 22 deletions impl/src/from_str.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::utils::{DeriveType, HashMap};
use crate::utils::{SingleFieldData, State};
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use quote::quote;
use syn::{parse::Result, DeriveInput};

/// Provides the hook to expand `#[derive(FromStr)]` into an implementation of `FromStr`
Expand Down Expand Up @@ -74,11 +73,7 @@ fn enum_from(
}

let input_type = &input.ident;
let visibility = &input.vis;

let err_name = format_ident!("Parse{input_type}Error");
let err_message =
format!("invalid {}", input_type.to_string().to_case(Case::Lower));
let input_type_name = input_type.to_string();

let mut cases = vec![];

Expand All @@ -103,26 +98,14 @@ fn enum_from(
let trait_path = state.trait_path;

quote! {
impl #trait_path for #input_type {
type Err = ::derive_more::FromStrError;

#[derive(Debug, Clone, PartialEq, Eq)]
#visibility struct #err_name;

impl std::fmt::Display for #err_name {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.write_str(#err_message)
}
}

impl std::error::Error for #err_name {}

impl #trait_path for #input_type
{
type Err = #err_name;
#[inline]
fn from_str(src: &str) -> ::core::result::Result<Self, Self::Err> {
Ok(match src.to_lowercase().as_str() {
#(#cases)*
_ => return Err(#err_name{}),
_ => return Err(::derive_more::FromStrError::new(#input_type_name)),
})
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ pub use self::convert::TryIntoError;

#[cfg(any(feature = "add", feature = "not"))]
pub mod ops;

#[cfg(feature = "from_str")]
mod r#str;
#[cfg(feature = "from_str")]
pub use self::r#str::FromStrError;
25 changes: 25 additions & 0 deletions src/str.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use core::fmt;

/// Error of parsing an enum value its string representation.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct FromStrError {
type_name: &'static str,
}

impl FromStrError {
#[doc(hidden)]
#[must_use]
#[inline]
pub const fn new(type_name: &'static str) -> Self {
Self { type_name }
}
}

impl fmt::Display for FromStrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Invalid `{}` string representation", self.type_name)
}
}

#[cfg(feature = "std")]
impl std::error::Error for FromStrError {}
12 changes: 4 additions & 8 deletions tests/from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,8 @@ fn enum_test() {
assert_eq!("FOO".parse::<EnumNoFields>().unwrap(), EnumNoFields::Foo);
assert_eq!("foo".parse::<EnumNoFields>().unwrap(), EnumNoFields::Foo);
assert_eq!(
"other".parse::<EnumNoFields>().unwrap_err(),
ParseEnumNoFieldsError {}
);
assert_eq!(
ParseEnumNoFieldsError {}.to_string(),
"invalid enum no fields"
"other".parse::<EnumNoFields>().unwrap_err().to_string(),
"Invalid `EnumNoFields` string representation",
);
}

Expand All @@ -38,7 +34,7 @@ fn enum_test_case_sensitive() {
assert_eq!("Baz".parse::<EnumNoFields>().unwrap(), EnumNoFields::Baz);
assert_eq!("BaZ".parse::<EnumNoFields>().unwrap(), EnumNoFields::BaZ);
assert_eq!(
"baz".parse::<EnumNoFields>().unwrap_err(),
ParseEnumNoFieldsError {}
"baz".parse::<EnumNoFields>().unwrap_err().to_string(),
"Invalid `EnumNoFields` string representation",
);
}