Skip to main content

custom_string/
custom_string.rs

1#[macro_export]
2macro_rules! custom_string {
3    (
4        $(#[$meta:meta])*,
5        $owned_struct_name:ident,
6        $validate_fn:expr
7    ) => {
8        paste::paste! {
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 [<$owned_struct_name Ref>]<'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 [<$owned_struct_name Ref>]<'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)` if the `value` is invalid.
41            pub fn validate(value: &str) -> Result<&str, $crate::ValidationError> {
42                match $validate_fn(value) {
43                    Ok(()) => Ok(value),
44                    Err(e) => Err($crate::ValidationError::new(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> [<$owned_struct_name Ref>]<'a> {
55            //! Validation
56
57            /// Validates the `value`.
58            ///
59            /// Returns `Ok(value)`.
60            /// Returns `Err(error)` if the `value` is invalid.
61            pub fn validate(value: &str) -> Result<&str, $crate::ValidationError> {
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, $crate::ValidationError>
91            where
92                S: AsRef<str> + Into<String>,
93            {
94                Self::validate(value.as_ref())?;
95                Ok(unsafe { Self::new_unchecked(value) })
96            }
97        }
98
99        impl<'a> [<$owned_struct_name Ref>]<'a> {
100            //! Construction
101
102            #[doc = concat!("Creates a new `", stringify!([<$owned_struct_name Ref>]), "` from the `value`.")]
103            #[doc = ""]
104            #[doc = "# Safety"]
105            #[doc = "The `value` must be valid."]
106            pub unsafe fn new_unchecked(value: &'a str) -> Self {
107                debug_assert!(Self::is_valid(value));
108
109                Self { value }
110            }
111
112            #[doc = concat!("Creates a new `", stringify!([<$owned_struct_name Ref>]), "` from the `value`.")]
113            pub fn new(value: &'a str) -> Result<Self, $crate::ValidationError> {
114                Ok(unsafe { Self::new_unchecked(Self::validate(value)?) })
115            }
116        }
117
118        impl $owned_struct_name {
119            //! Properties
120
121            /// Gets the value.
122            pub fn value(&self) -> &str {
123                self.value.as_str()
124            }
125
126            /// Gets the length of the value. (in bytes)
127            pub fn len(&self) -> usize {
128                self.value.len()
129            }
130
131            /// Checks if the value is empty.
132            pub fn is_empty(&self) -> bool {
133                self.value.is_empty()
134            }
135        }
136
137        impl<'a> [<$owned_struct_name Ref>]<'a> {
138            //! Properties
139
140            /// Gets the value.
141            pub fn value(&self) -> &str {
142                self.value
143            }
144
145            /// Gets the length of the value. (in bytes)
146            pub fn len(&self) -> usize {
147                self.value.len()
148            }
149
150            /// Checks if the value is empty.
151            pub fn is_empty(&self) -> bool {
152                self.value.is_empty()
153            }
154        }
155
156        impl $owned_struct_name {
157            //! Conversions
158
159            /// Converts the owned type to a reference type.
160            pub fn to_ref(&self) -> [<$owned_struct_name Ref>]<'_> {
161                unsafe { [<$owned_struct_name Ref>]::new_unchecked(self.value.as_str()) }
162            }
163        }
164
165        impl<'a> [<$owned_struct_name Ref>]<'a> {
166            //! Conversions
167
168            /// Converts the reference type to an owned type.
169            pub fn to_owned(self) -> $owned_struct_name {
170                unsafe { $owned_struct_name::new_unchecked(self.value.to_string()) }
171            }
172        }
173
174        impl<'a> From<[<$owned_struct_name Ref>]<'a>> for $owned_struct_name {
175            fn from(reference: [<$owned_struct_name Ref>]<'a>) -> Self {
176                reference.to_owned()
177            }
178        }
179
180        impl From<$owned_struct_name> for String {
181            fn from(value: $owned_struct_name) -> Self {
182                value.value
183            }
184        }
185
186        impl<'a> From<[<$owned_struct_name Ref>]<'a>> for String {
187            fn from(value: [<$owned_struct_name Ref>]<'a>) -> Self {
188                value.to_string()
189            }
190        }
191
192        impl TryFrom<String> for $owned_struct_name {
193            type Error = $crate::ValidationError;
194
195            fn try_from(value: String) -> Result<Self, Self::Error> {
196                Self::new(value)
197            }
198        }
199
200        impl<'a> TryFrom<&'a str> for $owned_struct_name {
201            type Error = $crate::ValidationError;
202
203            fn try_from(value: &'a str) -> Result<Self, Self::Error> {
204                Self::new(value)
205            }
206        }
207
208        impl<'a> TryFrom<&'a str> for [<$owned_struct_name Ref>]<'a> {
209            type Error = $crate::ValidationError;
210
211            fn try_from(value: &'a str) -> Result<Self, Self::Error> {
212                Self::new(value)
213            }
214        }
215
216        impl AsRef<str> for $owned_struct_name {
217            fn as_ref(&self) -> &str {
218                self.value.as_str()
219            }
220        }
221
222        impl<'a> AsRef<str> for [<$owned_struct_name Ref>]<'a> {
223            fn as_ref(&self) -> &str {
224                self.value
225            }
226        }
227
228        impl std::borrow::Borrow<str> for $owned_struct_name {
229            fn borrow(&self) -> &str {
230                self.value.as_str()
231            }
232        }
233
234        impl<'a> std::borrow::Borrow<str> for [<$owned_struct_name Ref>]<'a> {
235            fn borrow(&self) -> &str {
236                self.value
237            }
238        }
239
240        impl std::ops::Deref for $owned_struct_name {
241            type Target = str;
242
243            fn deref(&self) -> &str {
244                self.value.as_str()
245            }
246        }
247
248        impl<'a> std::ops::Deref for [<$owned_struct_name Ref>]<'a> {
249            type Target = str;
250
251            fn deref(&self) -> &str {
252                self.value
253            }
254        }
255
256        impl std::fmt::Display for $owned_struct_name {
257            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258                write!(f, "{}", self.value)
259            }
260        }
261
262        impl<'a> std::fmt::Display for [<$owned_struct_name Ref>]<'a> {
263            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264                write!(f, "{}", self.value)
265            }
266        }
267
268        impl std::str::FromStr for $owned_struct_name {
269            type Err = $crate::ValidationError;
270
271            fn from_str(s: &str) -> Result<Self, Self::Err> {
272                Self::new(s)
273            }
274        }
275
276        impl<'a> PartialOrd<[<$owned_struct_name Ref>]<'a>> for $owned_struct_name {
277            fn partial_cmp(&self, other: &[<$owned_struct_name Ref>]<'a>) -> Option<std::cmp::Ordering> {
278                self.value().partial_cmp(other.value())
279            }
280        }
281
282        impl<'a> PartialOrd<$owned_struct_name> for [<$owned_struct_name Ref>]<'a> {
283            fn partial_cmp(&self, other: &$owned_struct_name) -> Option<std::cmp::Ordering> {
284                self.value().partial_cmp(other.value())
285            }
286        }
287
288        #[cfg(feature = "serde")]
289        impl serde::Serialize for $owned_struct_name {
290            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
291            where
292                S: serde::Serializer,
293            {
294                self.value.serialize(serializer)
295            }
296        }
297
298        #[cfg(feature = "serde")]
299        impl<'a> serde::Serialize for [<$owned_struct_name Ref>]<'a> {
300            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
301            where
302                S: serde::Serializer,
303            {
304                self.value.serialize(serializer)
305            }
306        }
307
308        #[cfg(feature = "serde")]
309        impl<'de> serde::Deserialize<'de> for $owned_struct_name {
310            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
311            where
312                D: serde::Deserializer<'de>,
313            {
314                let value: String = String::deserialize(deserializer)?;
315                Self::new(value).map_err(serde::de::Error::custom)
316            }
317        }
318
319        }
320    };
321}
322
323#[cfg(test)]
324#[allow(dead_code)]
325mod tests {
326    use std::collections::HashMap;
327
328    custom_string!(
329        #[doc = "A lowercase string."],
330        Lower,
331        |s: &str| if !s.as_bytes().iter().all(|c| c.is_ascii_lowercase()) {
332            Err("not lowercase")
333        } else {
334            Ok(())
335        }
336    );
337
338    #[test]
339    fn validation() {
340        assert!(Lower::is_valid("abc"));
341        assert!(!Lower::is_valid("ABC"));
342        assert!(!Lower::is_valid("aBc"));
343
344        let result: Result<&str, _> = Lower::validate("abc");
345        assert!(result.is_ok());
346
347        let error: crate::ValidationError = Lower::validate("ABC").unwrap_err();
348        assert_eq!(error.message(), "not lowercase");
349        assert_eq!(error.to_string(), "not lowercase");
350    }
351
352    #[test]
353    fn validation_ref() {
354        assert!(LowerRef::is_valid("abc"));
355        assert!(!LowerRef::is_valid("ABC"));
356
357        let error: crate::ValidationError = LowerRef::validate("ABC").unwrap_err();
358        assert_eq!(error.message(), "not lowercase");
359    }
360
361    #[test]
362    fn construction() {
363        let owned: Lower = Lower::new("abc").unwrap();
364        assert_eq!(owned.value(), "abc");
365
366        let error: crate::ValidationError = Lower::new("ABC").unwrap_err();
367        assert_eq!(error.message(), "not lowercase");
368
369        let owned_from_string: Lower = Lower::new(String::from("abc")).unwrap();
370        assert_eq!(owned_from_string.value(), "abc");
371    }
372
373    #[test]
374    fn construction_ref() {
375        let reference: LowerRef = LowerRef::new("abc").unwrap();
376        assert_eq!(reference.value(), "abc");
377
378        let error: crate::ValidationError = LowerRef::new("ABC").unwrap_err();
379        assert_eq!(error.message(), "not lowercase");
380    }
381
382    #[test]
383    fn properties() {
384        let owned: Lower = Lower::new("abc").unwrap();
385        assert_eq!(owned.value(), "abc");
386        assert_eq!(owned.len(), 3);
387        assert!(!owned.is_empty());
388
389        let reference: LowerRef = LowerRef::new("abc").unwrap();
390        assert_eq!(reference.value(), "abc");
391        assert_eq!(reference.len(), 3);
392        assert!(!reference.is_empty());
393    }
394
395    #[test]
396    fn equality() {
397        let one: Lower = Lower::new("one").unwrap();
398        let two: Lower = Lower::new("two").unwrap();
399
400        assert_eq!(one, "one");
401        assert_eq!(one, one);
402        assert_ne!(one, "two");
403        assert_ne!(one, two);
404
405        let one_ref: LowerRef = LowerRef::new("one").unwrap();
406        let two_ref: LowerRef = LowerRef::new("two").unwrap();
407
408        assert_eq!(one_ref, "one");
409        assert_eq!(one_ref, one_ref);
410        assert_ne!(one_ref, "two");
411        assert_ne!(one_ref, two_ref);
412    }
413
414    #[test]
415    fn ordering() {
416        let a: Lower = Lower::new("a").unwrap();
417        let b: Lower = Lower::new("b").unwrap();
418        assert!(a < b);
419
420        let a_ref: LowerRef = LowerRef::new("a").unwrap();
421        let b_ref: LowerRef = LowerRef::new("b").unwrap();
422        assert!(a_ref < b_ref);
423    }
424
425    #[test]
426    fn display() {
427        let owned: Lower = Lower::new("abc").unwrap();
428        assert_eq!(format!("{}", owned), "abc");
429
430        let reference: LowerRef = LowerRef::new("abc").unwrap();
431        assert_eq!(format!("{}", reference), "abc");
432    }
433
434    #[test]
435    fn deref() {
436        let owned: Lower = Lower::new("abc").unwrap();
437        let s: &str = &owned;
438        assert_eq!(s, "abc");
439        assert!(owned.starts_with("ab"));
440
441        let reference: LowerRef = LowerRef::new("abc").unwrap();
442        let s: &str = &reference;
443        assert_eq!(s, "abc");
444        assert!(reference.starts_with("ab"));
445    }
446
447    #[test]
448    fn conversions_owned_ref() {
449        let owned: Lower = Lower::new("abc").unwrap();
450        let reference: LowerRef = owned.to_ref();
451        assert_eq!(reference.value(), "abc");
452
453        let back_to_owned: Lower = reference.to_owned();
454        assert_eq!(back_to_owned.value(), "abc");
455
456        let from_ref: Lower = Lower::from(reference);
457        assert_eq!(from_ref.value(), "abc");
458    }
459
460    #[test]
461    fn conversions_to_string() {
462        let owned: Lower = Lower::new("abc").unwrap();
463        let s: String = String::from(owned);
464        assert_eq!(s, "abc");
465
466        let reference: LowerRef = LowerRef::new("abc").unwrap();
467        let s: String = String::from(reference);
468        assert_eq!(s, "abc");
469    }
470
471    #[test]
472    fn try_from() {
473        let owned: Lower = Lower::try_from("abc").unwrap();
474        assert_eq!(owned.value(), "abc");
475
476        let owned_from_string: Lower = Lower::try_from(String::from("abc")).unwrap();
477        assert_eq!(owned_from_string.value(), "abc");
478
479        let reference: LowerRef = LowerRef::try_from("abc").unwrap();
480        assert_eq!(reference.value(), "abc");
481
482        let error: crate::ValidationError = Lower::try_from("ABC").unwrap_err();
483        assert_eq!(error.message(), "not lowercase");
484    }
485
486    #[test]
487    fn as_ref_and_borrow() {
488        use std::borrow::Borrow;
489
490        let owned: Lower = Lower::new("abc").unwrap();
491        let s: &str = owned.as_ref();
492        assert_eq!(s, "abc");
493        let s: &str = owned.borrow();
494        assert_eq!(s, "abc");
495
496        let reference: LowerRef = LowerRef::new("abc").unwrap();
497        let s: &str = reference.as_ref();
498        assert_eq!(s, "abc");
499        let s: &str = reference.borrow();
500        assert_eq!(s, "abc");
501    }
502
503    #[test]
504    fn hash_map_lookup() {
505        let mut map: HashMap<Lower, i32> = HashMap::new();
506        let key: Lower = Lower::new("abc").unwrap();
507        map.insert(key, 42);
508
509        assert_eq!(map.get("abc"), Some(&42));
510        assert_eq!(map.get("xyz"), None);
511    }
512
513    #[test]
514    fn clone() {
515        let owned: Lower = Lower::new("abc").unwrap();
516        let cloned: Lower = owned.clone();
517        assert_eq!(owned, cloned);
518
519        let reference: LowerRef = LowerRef::new("abc").unwrap();
520        let copied: LowerRef = reference;
521        assert_eq!(reference, copied);
522    }
523
524    #[test]
525    fn cross_type_equality() {
526        let owned: Lower = Lower::new("abc").unwrap();
527        let reference: LowerRef = LowerRef::new("abc").unwrap();
528
529        assert_eq!(owned, reference);
530        assert_eq!(reference, owned);
531
532        let other_ref: LowerRef = LowerRef::new("xyz").unwrap();
533        assert_ne!(owned, other_ref);
534        assert_ne!(other_ref, owned);
535    }
536
537    #[test]
538    fn cross_type_ordering() {
539        let owned_a: Lower = Lower::new("a").unwrap();
540        let ref_b: LowerRef = LowerRef::new("b").unwrap();
541
542        assert!(owned_a < ref_b);
543        assert!(ref_b > owned_a);
544
545        let owned_b: Lower = Lower::new("b").unwrap();
546        let ref_a: LowerRef = LowerRef::new("a").unwrap();
547
548        assert!(owned_b > ref_a);
549        assert!(ref_a < owned_b);
550    }
551
552    #[test]
553    fn from_str() {
554        let owned: Lower = "abc".parse().unwrap();
555        assert_eq!(owned.value(), "abc");
556
557        let error: crate::ValidationError = "ABC".parse::<Lower>().unwrap_err();
558        assert_eq!(error.message(), "not lowercase");
559    }
560
561    #[cfg(feature = "serde")]
562    mod serde_tests {
563        use super::*;
564
565        #[test]
566        fn serialize_owned() {
567            let owned: Lower = Lower::new("abc").unwrap();
568            let json: String = serde_json::to_string(&owned).unwrap();
569            assert_eq!(json, "\"abc\"");
570        }
571
572        #[test]
573        fn serialize_ref() {
574            let reference: LowerRef = LowerRef::new("abc").unwrap();
575            let json: String = serde_json::to_string(&reference).unwrap();
576            assert_eq!(json, "\"abc\"");
577        }
578
579        #[test]
580        fn deserialize_valid() {
581            let owned: Lower = serde_json::from_str("\"abc\"").unwrap();
582            assert_eq!(owned.value(), "abc");
583        }
584
585        #[test]
586        fn deserialize_invalid() {
587            let result: Result<Lower, _> = serde_json::from_str("\"ABC\"");
588            assert!(result.is_err());
589        }
590
591        #[test]
592        fn round_trip() {
593            let original: Lower = Lower::new("abc").unwrap();
594            let json: String = serde_json::to_string(&original).unwrap();
595            let deserialized: Lower = serde_json::from_str(&json).unwrap();
596            assert_eq!(original, deserialized);
597        }
598    }
599
600    #[test]
601    fn validation_error() {
602        let error: crate::ValidationError = crate::ValidationError::new("test error");
603        assert_eq!(error.message(), "test error");
604        assert_eq!(error.to_string(), "test error");
605        assert_eq!(error, crate::ValidationError::new("test error"));
606        assert_ne!(error, crate::ValidationError::new("other error"));
607    }
608}