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