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