Skip to main content

impl_more/
from_str.rs

1/// Implement [`FromStr`] by forwarding to a field's implementation.
2///
3/// The first argument is the struct to create the impl for and the second is the field type whose
4/// [`FromStr`] implementation is used.
5///
6/// # Examples
7/// With a newtype struct:
8/// ```
9/// use impl_more::forward_from_str;
10///
11/// #[derive(Debug, PartialEq)]
12/// struct Port(u16);
13/// forward_from_str!(Port => u16);
14///
15/// assert_eq!("8080".parse(), Ok(Port(8080)));
16/// ```
17///
18/// With a named field struct and type parameters:
19/// ```
20/// use impl_more::forward_from_str;
21///
22/// #[derive(Debug, PartialEq)]
23/// struct Value<T> { inner: T }
24/// forward_from_str!(<T> in Value<T> => inner: T);
25///
26/// assert_eq!("true".parse(), Ok(Value { inner: true }));
27/// ```
28///
29/// [`FromStr`]: core::str::FromStr
30#[macro_export]
31macro_rules! forward_from_str {
32    (<$($generic:ident),+> in $this:ty => $inner:ty $(,)?) => {
33        impl <$($generic),+> ::core::str::FromStr for $this
34        where
35            $inner: ::core::str::FromStr,
36        {
37            type Err = <$inner as ::core::str::FromStr>::Err;
38
39            fn from_str(value: &str) -> ::core::result::Result<Self, Self::Err> {
40                <$inner as ::core::str::FromStr>::from_str(value).map(Self)
41            }
42        }
43    };
44
45    (<$($generic:ident),+> in $this:ty => $field:ident : $inner:ty $(,)?) => {
46        impl <$($generic),+> ::core::str::FromStr for $this
47        where
48            $inner: ::core::str::FromStr,
49        {
50            type Err = <$inner as ::core::str::FromStr>::Err;
51
52            fn from_str(value: &str) -> ::core::result::Result<Self, Self::Err> {
53                <$inner as ::core::str::FromStr>::from_str(value)
54                    .map(|$field| Self { $field })
55            }
56        }
57    };
58
59    ($this:ty => $inner:ty $(,)?) => {
60        impl ::core::str::FromStr for $this {
61            type Err = <$inner as ::core::str::FromStr>::Err;
62
63            fn from_str(value: &str) -> ::core::result::Result<Self, Self::Err> {
64                <$inner as ::core::str::FromStr>::from_str(value).map(Self)
65            }
66        }
67    };
68
69    ($this:ty => $field:ident : $inner:ty $(,)?) => {
70        impl ::core::str::FromStr for $this {
71            type Err = <$inner as ::core::str::FromStr>::Err;
72
73            fn from_str(value: &str) -> ::core::result::Result<Self, Self::Err> {
74                <$inner as ::core::str::FromStr>::from_str(value)
75                    .map(|$field| Self { $field })
76            }
77        }
78    };
79}
80
81#[cfg(test)]
82mod tests {
83    use core::{num::ParseIntError, str::FromStr as _};
84
85    #[derive(Debug, PartialEq)]
86    struct Newtype(u16);
87    forward_from_str!(Newtype => u16);
88
89    #[derive(Debug, PartialEq)]
90    struct Named {
91        inner: bool,
92    }
93    forward_from_str!(Named => inner: bool);
94
95    #[derive(Debug, PartialEq)]
96    struct Generic<T>(T);
97    forward_from_str!(<T> in Generic<T> => T);
98
99    #[derive(Debug, PartialEq)]
100    struct GenericNamed<T> {
101        inner: T,
102    }
103    forward_from_str!(<T> in GenericNamed<T> => inner: T);
104
105    static_assertions::assert_impl_all!(Newtype: core::str::FromStr<Err = ParseIntError>);
106    static_assertions::assert_impl_all!(Named: core::str::FromStr<Err = core::str::ParseBoolError>);
107    static_assertions::assert_impl_all!(Generic<u16>: core::str::FromStr<Err = ParseIntError>);
108    static_assertions::assert_impl_all!(
109        GenericNamed<bool>: core::str::FromStr<Err = core::str::ParseBoolError>
110    );
111
112    #[test]
113    fn forwards_newtype() {
114        assert_eq!(Newtype::from_str("8080"), Ok(Newtype(8080)));
115        assert!(Newtype::from_str("invalid").is_err());
116    }
117
118    #[test]
119    fn forwards_named_field() {
120        assert_eq!(Named::from_str("true"), Ok(Named { inner: true }));
121        assert!(Named::from_str("invalid").is_err());
122    }
123
124    #[test]
125    fn forwards_generic_newtype() {
126        assert_eq!(Generic::<u16>::from_str("8080"), Ok(Generic(8080)));
127    }
128
129    #[test]
130    fn forwards_generic_named_field() {
131        assert_eq!(
132            GenericNamed::<bool>::from_str("true"),
133            Ok(GenericNamed { inner: true })
134        );
135    }
136}