custom_string/
lib.rs

1#[macro_export]
2macro_rules! custom_string {
3    (
4        $(#[$meta:meta])*,
5        $owned_struct_name:ident,
6        $ref_struct_name:ident,
7        $with_trait_name:ident,
8        $field_name:ident,
9        $validate_fn:expr
10    ) => {
11
12        $(#[$meta])*
13        #[derive(Clone, Ord, PartialOrd, Eq, Hash, Debug)]
14        pub struct $owned_struct_name {
15            value: String,
16        }
17
18        $(#[$meta])*
19        #[derive(Copy, Clone, Ord, PartialOrd, Eq, Hash, Debug)]
20        pub struct $ref_struct_name<'a> {
21            value: &'a str,
22        }
23
24        impl<S: AsRef<str>> PartialEq<S> for $owned_struct_name {
25            fn eq(&self, other: &S) -> bool {
26                self.value() == other.as_ref()
27            }
28        }
29
30        impl<'a, S: AsRef<str>> PartialEq<S> for $ref_struct_name<'a> {
31            fn eq(&self, other: &S) -> bool {
32                self.value() == other.as_ref()
33            }
34        }
35
36        impl $owned_struct_name {
37            //! Validation
38
39            /// Validates the `value`.
40            ///
41            /// Returns `Ok(value)`.
42            /// Returns `Err(error_message)` if the `value` is invalid.
43            pub fn validate(value: &str) -> Result<&str, &'static str> {
44                match $validate_fn(value) {
45                    Ok(()) => Ok(value),
46                    Err(e) => Err(e),
47                }
48            }
49
50            /// Checks if the `value` is valid.
51            pub fn is_valid(value: &str) -> bool {
52                Self::validate(value).is_ok()
53            }
54        }
55
56        impl<'a> $ref_struct_name<'a> {
57            //! Validation
58
59            /// Validates the `value`.
60            ///
61            /// Returns `Ok(value)`.
62            /// Returns `Err(error_message)` if the `value` is invalid.
63            pub fn validate(value: &str) -> Result<&str, &'static str> {
64                $owned_struct_name::validate(value)
65            }
66
67            /// Checks if the `value` is valid.
68            pub fn is_valid(value: &str) -> bool {
69                Self::validate(value).is_ok()
70            }
71        }
72
73        impl $owned_struct_name {
74            //! Construction
75
76            #[doc = concat!("Creates a new `", stringify!($owned_struct_name), "` from the `value`.")]
77            #[doc = ""]
78            #[doc = "# Safety"]
79            #[doc = "The `value` must be valid."]
80            pub unsafe fn new_unchecked<S>(value: S) -> Self
81            where
82                S: Into<String>,
83            {
84                let value: String = value.into();
85
86                debug_assert!(Self::is_valid(value.as_str()));
87
88                Self { value }
89            }
90
91            #[doc = concat!("Creates a new `", stringify!($owned_struct_name), "` from the `value`.")]
92            pub fn new<S>(value: S) -> Result<Self, &'static str>
93            where
94                S: AsRef<str> + Into<String>,
95            {
96                Ok(unsafe { Self::new_unchecked(Self::validate(value.as_ref())?) })
97            }
98        }
99
100        impl<'a> $ref_struct_name<'a> {
101            //! Construction
102
103            #[doc = concat!("Creates a new `", stringify!($ref_struct_name), "` from the `value`.")]
104            #[doc = ""]
105            #[doc = "# Safety"]
106            #[doc = "The `value` must be valid."]
107            pub unsafe fn new_unchecked(value: &'a str) -> Self {
108                debug_assert!(Self::is_valid(value));
109
110                Self { value }
111            }
112
113            #[doc = concat!("Creates a new `", stringify!($ref_struct_name), "` from the `value`.")]
114            pub fn new(value: &'a str) -> Result<Self, &'static str> {
115                Ok(unsafe { Self::new_unchecked(Self::validate(value)?) })
116            }
117        }
118
119        impl $owned_struct_name {
120            //! Properties
121
122            /// Gets the value.
123            pub fn value(&self) -> &str {
124                self.value.as_str()
125            }
126
127            /// Gets the length of the value. (in bytes)
128            pub fn len(&self) -> usize {
129                self.value.len()
130            }
131
132            /// Checks if the value is empty.
133            pub fn is_empty(&self) -> bool {
134                self.value.is_empty()
135            }
136        }
137
138        impl<'a> $ref_struct_name<'a> {
139            //! Properties
140
141            /// Gets the value.
142            pub fn value(&self) -> &str {
143                self.value
144            }
145
146            /// Gets the length of the value. (in bytes)
147            pub fn len(&self) -> usize {
148                self.value.len()
149            }
150
151            /// Checks if the value is empty.
152            pub fn is_empty(&self) -> bool {
153                self.value.is_empty()
154            }
155        }
156
157        impl $owned_struct_name {
158            //! Conversions
159
160            /// Converts the owned type to a reference type.
161            pub fn to_ref<'a>(&'a self) -> $ref_struct_name<'a> {
162                unsafe { $ref_struct_name::new_unchecked(self.value.as_str()) }
163            }
164        }
165
166        impl<'a> $ref_struct_name<'a> {
167            //! Conversions
168
169            /// Converts the reference type to an owned type.
170            pub fn to_owned(self) -> $owned_struct_name {
171                unsafe { $owned_struct_name::new_unchecked(self.value.to_string()) }
172            }
173        }
174
175        impl<'a> From<$ref_struct_name<'a>> for $owned_struct_name {
176            fn from(reference: $ref_struct_name<'a>) -> Self {
177                reference.to_owned()
178            }
179        }
180
181        impl From<$owned_struct_name> for String {
182            fn from(value: $owned_struct_name) -> Self {
183                value.value
184            }
185        }
186
187        impl<'a> From<$ref_struct_name<'a>> for String {
188            fn from(value: $ref_struct_name<'a>) -> Self {
189                value.to_string()
190            }
191        }
192
193        impl AsRef<str> for $owned_struct_name {
194            fn as_ref(&self) -> &str {
195                self.value.as_ref()
196            }
197        }
198
199        impl<'a> AsRef<str> for $ref_struct_name<'a> {
200            fn as_ref(&self) -> &str {
201                self.value
202            }
203        }
204
205        impl std::borrow::Borrow<str> for $owned_struct_name {
206            fn borrow(&self) -> &str {
207                self.value.borrow()
208            }
209        }
210
211        impl<'a> std::borrow::Borrow<str> for $ref_struct_name<'a> {
212            fn borrow(&self) -> &str {
213                self.value
214            }
215        }
216
217        impl std::fmt::Display for $owned_struct_name {
218            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219                write!(f, "{}", self.value)
220            }
221        }
222
223        impl<'a> std::fmt::Display for $ref_struct_name<'a> {
224            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225                write!(f, "{}", self.value)
226            }
227        }
228
229        #[doc = concat!("An element with a `", stringify!($owned_struct_name), "`.")]
230        pub trait $with_trait_name {
231            #[doc = concat!("Gets the `", stringify!($field_name), "`.")]
232            fn $field_name(&self) -> $ref_struct_name<'_>;
233        }
234
235        impl $with_trait_name for $owned_struct_name {
236            fn $field_name(&self) -> $ref_struct_name<'_> {
237                $ref_struct_name { value: self.value.as_str() }
238            }
239        }
240
241        impl $with_trait_name for $ref_struct_name<'_> {
242            fn $field_name(&self) -> $ref_struct_name<'_> {
243                *self
244            }
245        }
246    };
247}
248
249#[cfg(test)]
250#[allow(dead_code)]
251mod tests {
252    custom_string!(#[doc = "A lowercase string."], Lower, LowerRef, WithLower, lower, |s: &str| if !s
253        .as_bytes()
254        .iter()
255        .all(|c| c.is_ascii_lowercase())
256    {
257        Err("not lowercase")
258    } else {
259        Ok(())
260    });
261
262    #[test]
263    fn equals() {
264        let one: Lower = Lower::new("one").unwrap();
265        let two: Lower = Lower::new("two").unwrap();
266
267        assert_eq!(one, "one");
268        assert_eq!(one, one);
269        assert_ne!(one, "two");
270        assert_ne!(one, two);
271    }
272
273    #[test]
274    fn validation() {
275        assert!(Lower::is_valid("one"));
276        assert!(!Lower::is_valid("ONE"));
277
278        assert!(Lower::validate("one").is_ok());
279        assert_eq!(Lower::validate("ONE").err().unwrap(), "not lowercase");
280    }
281}