stringly_conversions/
lib.rs

1// Rust language amplification library providing multiple generic trait
2// implementations, type wrappers, derive macros and other language enhancements
3//
4// Written in 2019-2020 by
5//     Martin Habovstiak <martin.habovstiak@gmail.com>
6//
7// To the extent possible under law, the author(s) have dedicated all
8// copyright and related and neighboring rights to this software to
9// the public domain worldwide. This software is distributed without
10// any warranty.
11//
12// You should have received a copy of the MIT License
13// along with this software.
14// If not, see <https://opensource.org/licenses/MIT>.
15
16//! A crate helping to convert to/from various representations of strings.
17//!
18//! This crate is `no_std` with an optional feature to enable `alloc`.
19
20#![no_std]
21
22#[cfg(feature = "alloc")]
23pub extern crate alloc;
24
25pub extern crate paste;
26
27// We republish all supported external crates for access from the macros
28#[cfg(feature = "serde_str_helpers")]
29pub extern crate serde_str_helpers;
30
31/// impls TryFrom<T> where T: Deref<Target=str> in terms of FromStr.
32///
33/// If your type implements `FromStr` then it could also implement `TryFrom<T>`
34/// where `T` are various stringly types like `&str`, `String`, `Cow<'a,
35/// str>`... Implementing these conversions as a blanket impl is impossible due
36/// to the conflict with `T: Into<Self>`. Implementing them manually is tedious.
37/// This macro will help you. However, take a look at
38/// `impl_into_stringly_standard` which will help you even more!
39///
40/// This needs to be a macro instead of blanket imple in order to resolve the
41/// conflict with T: Into<Self>
42#[macro_export]
43macro_rules! impl_try_from_stringly {
44    ($to:ty $(, $from:ty)+ $(,)?) => {
45        $(
46            impl ::core::convert::TryFrom<$from> for $to {
47                type Error = <$to as ::core::str::FromStr>::Err;
48                #[inline]
49                fn try_from(value: $from) -> Result<Self, Self::Error> {
50                    <$to as core::str::FromStr>::from_str(&value)
51                }
52            }
53        )*
54    };
55
56    (@std, $to:ty $(, $from:ty)+ $(,)?) => {
57        $(
58            #[cfg(feature = "std")]
59            impl std::convert::TryFrom<$from> for $to {
60                type Error = <$to as ::core::str::FromStr>::Err;
61                #[inline]
62                fn try_from(value: $from) -> Result<Self, Self::Error> {
63                    <$to>::from_str(&value)
64                }
65            }
66        )*
67    }
68}
69
70/// Calls impl_try_from_stringly!() with a set of standard stringly types.
71///
72/// A module is generated internally and its name is derived from the type name.
73/// This obviously fails when the type name is generic or contains other
74/// non-ident characters. In such case supply the macro with a second argument
75/// which is some unique identifier.
76///
77/// The currently supported types are:
78///
79/// * `&str` - also supported in `no_std`
80/// * `String`
81/// * `Cow<'_, str>,`
82/// * `Box<str>,`
83/// * `Box<Cow<'_, str>>,`
84/// * `Rc<str>,`
85/// * `Rc<String>,`
86/// * `Rc<Cow<'_, str>>,`
87/// * `Arc<str>,`
88/// * `Arc<String>,`
89/// * `Arc<Cow<'_, str>>,`
90///
91/// Types from external crates:
92/// * `serde_str_helpers::DeserBorrowStr`
93#[macro_export]
94macro_rules! impl_try_from_stringly_standard {
95    ($type:ty) => {
96        $crate::impl_try_from_stringly_standard!($type, $type);
97    };
98    ($type:ty, $module_suffix:ty) => {
99        $crate::paste::paste! {
100            #[allow(non_snake_case)]
101            mod [<__try_from_stringly_standard_ $module_suffix >] {
102                use super::*;
103                #[cfg(feature = "alloc")]
104                use alloc::string::String;
105                #[cfg(feature = "alloc")]
106                use alloc::boxed::Box;
107                #[cfg(feature = "alloc")]
108                use alloc::borrow::Cow;
109                #[cfg(feature = "alloc")]
110                use alloc::rc::Rc;
111                #[cfg(feature = "alloc")]
112                use alloc::sync::Arc;
113
114                impl_try_from_stringly! { $type,
115                    &str,
116                }
117
118                #[cfg(feature = "alloc")]
119                impl_try_from_stringly! { $type,
120                    String,
121                    Cow<'_, str>,
122                    Box<str>,
123                    Box<Cow<'_, str>>,
124                    Rc<str>,
125                    Rc<String>,
126                    Rc<Cow<'_, str>>,
127                    Arc<str>,
128                    Arc<String>,
129                    Arc<Cow<'_, str>>,
130                }
131
132                #[cfg(feature = "serde_str_helpers")]
133                impl_try_from_stringly!($type, $crate::serde_str_helpers::DeserBorrowStr<'_>);
134            }
135        }
136    };
137}
138
139/// Impls From<T> for Stringly where String: Into<Stringly>, T: Display
140#[macro_export]
141macro_rules! impl_into_stringly {
142    ($from:ty $(, $into:ty)+ $(,)?) => {
143        $(
144            impl From<$from> for $into {
145                fn from(value: $from) -> Self {
146                    $crate::alloc::string::ToString::to_string(&value).into()
147                }
148            }
149        )+
150    }
151}
152
153/// Implements `impl_into_stringly` for `$type` and traits with `$type`
154///
155/// A module is generated internally and its name is derived from the type name.
156/// This obviously fails when the type name is generic or contains other
157/// non-ident characters. In such case supply the macro with a second argument
158/// which is some unique identifier.
159///
160/// The currently supported types are:
161///
162/// * `String`
163/// * `Cow<'_, str>,`
164/// * `Box<str>,`
165/// * `Box<Cow<'_, str>>,`
166/// * `Rc<str>,`
167/// * `Rc<String>,`
168/// * `Rc<Cow<'_, str>>,`
169/// * `Arc<str>,`
170/// * `Arc<String>,`
171/// * `Arc<Cow<'_, str>>,`
172#[macro_export]
173macro_rules! impl_into_stringly_standard {
174    ($type:ty) => {
175        $crate::impl_into_stringly_standard!($type, $type);
176    };
177    ($type:ty, $module_suffix:ty) => {
178        $crate::paste::paste! {
179            #[allow(non_snake_case)]
180            mod [< __into_stringly_standard_ $type >] {
181                #[allow(unused)]
182                use super::*;
183                #[cfg(feature = "alloc")]
184                use alloc::borrow::Cow;
185                #[cfg(feature = "alloc")]
186                use alloc::rc::Rc;
187                #[cfg(feature = "alloc")]
188                use alloc::sync::Arc;
189
190                #[cfg(feature = "alloc")]
191                impl_into_stringly! { $type,
192                    String,
193                    Cow<'_, str>,
194                    Box<str>,
195                    Rc<str>,
196                    Rc<String>,
197                    Arc<str>,
198                    Arc<String>,
199                }
200            }
201        }
202    };
203}
204
205#[cfg(test)]
206mod tests {
207    use core::convert::TryFrom;
208    use core::fmt;
209
210    #[cfg(feature = "alloc")]
211    use alloc::borrow::Cow;
212    #[cfg(feature = "alloc")]
213    use alloc::boxed::Box;
214    #[cfg(feature = "alloc")]
215    use alloc::rc::Rc;
216    #[cfg(feature = "alloc")]
217    use alloc::string::String;
218    #[cfg(feature = "alloc")]
219    use alloc::sync::Arc;
220
221    struct Number(u32);
222
223    impl_try_from_stringly_standard!(Number);
224    impl_into_stringly_standard!(Number);
225
226    impl core::str::FromStr for Number {
227        type Err = core::num::ParseIntError;
228
229        fn from_str(s: &str) -> Result<Self, Self::Err> {
230            s.parse().map(Number)
231        }
232    }
233
234    impl fmt::Display for Number {
235        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236            write!(f, "{}", self.0)
237        }
238    }
239
240    // Intended for testing clashes, the code itself is irrelevant.
241    struct Foo<T>(T);
242
243    impl_try_from_stringly_standard!(Foo<u32>, Foo__u32);
244
245    impl core::str::FromStr for Foo<u32> {
246        type Err = core::num::ParseIntError;
247
248        fn from_str(s: &str) -> Result<Self, Self::Err> {
249            s.parse().map(Foo)
250        }
251    }
252
253    #[test]
254    fn parse_str() {
255        assert_eq!(Number::try_from("42").unwrap().0, 42);
256    }
257
258    #[cfg(feature = "alloc")]
259    #[test]
260    fn parse_alloc() {
261        assert_eq!(Number::try_from(String::from("42")).unwrap().0, 42);
262        assert_eq!(Number::try_from(<Cow<'_, str>>::from("42")).unwrap().0, 42);
263        assert_eq!(Number::try_from(<Box<str>>::from("42")).unwrap().0, 42);
264        assert_eq!(Number::try_from(<Rc<str>>::from("42")).unwrap().0, 42);
265        assert_eq!(Number::try_from(Rc::new(String::from("42"))).unwrap().0, 42);
266        assert_eq!(Number::try_from(<Arc<str>>::from("42")).unwrap().0, 42);
267        assert_eq!(
268            Number::try_from(Arc::new(String::from("42"))).unwrap().0,
269            42
270        );
271    }
272
273    #[cfg(all(feature = "serde_str_helpers", feature = "alloc"))]
274    #[test]
275    fn test_serde_str_helpers() {
276        assert_eq!(
277            Number::try_from(serde_str_helpers::DeserBorrowStr::from(
278                <Cow<'_, str>>::from("42")
279            ))
280            .unwrap()
281            .0,
282            42
283        );
284    }
285
286    #[cfg(feature = "alloc")]
287    #[test]
288    fn display() {
289        assert_eq!(&*<String>::from(Number(42)), "42");
290        assert_eq!(&*<Cow<'_, str>>::from(Number(42)), "42");
291        assert_eq!(&*<Box<str>>::from(Number(42)), "42");
292        assert_eq!(&*<Rc<str>>::from(Number(42)), "42");
293        assert_eq!(&*<Rc<String>>::from(Number(42)), "42");
294        assert_eq!(&*<Arc<str>>::from(Number(42)), "42");
295        assert_eq!(&*<Arc<String>>::from(Number(42)), "42");
296    }
297}