Skip to content

Commit

Permalink
Use dedicated error type for FromStr (#221) (#235)
Browse files Browse the repository at this point in the history
Part of #221
Resolves #221

## Synopsis

#221 (comment)

## Solution

Adds `FromStrError` type and uses it in `#[derive(FromStr)]` expansion
for enums instead of generating an error type in-place.
  • Loading branch information
tyranron authored Jan 26, 2023
1 parent aa4d5eb commit 9014b18
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 47 deletions.
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",
);
}

0 comments on commit 9014b18

Please sign in to comment.