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        $crate::__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        #[doc = concat!("An element with a `", stringify!($owned_struct_name), "`.")]
289        pub trait [<With $owned_struct_name>] {
290            #[doc = concat!("Gets the `", stringify!($owned_struct_name), "`.")]
291            fn [<$owned_struct_name:snake>](&self) -> [<$owned_struct_name Ref>]<'_>;
292        }
293
294        impl [<With $owned_struct_name>] for $owned_struct_name {
295            fn [<$owned_struct_name:snake>](&self) -> [<$owned_struct_name Ref>]<'_> {
296                self.to_ref()
297            }
298        }
299
300        impl<'a> [<With $owned_struct_name>] for [<$owned_struct_name Ref>]<'a> {
301            fn [<$owned_struct_name:snake>](&self) -> [<$owned_struct_name Ref>]<'_> {
302                *self
303            }
304        }
305
306        $crate::__custom_string_serde_impl!($owned_struct_name, [<$owned_struct_name Ref>]);
307
308        }
309    };
310}
311
312#[cfg(feature = "serde")]
313#[doc(hidden)]
314#[macro_export]
315macro_rules! __custom_string_serde_impl {
316    ($owned_struct_name:ident, $ref_struct_name:ident) => {
317        impl $crate::__serde::Serialize for $owned_struct_name {
318            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
319            where
320                S: $crate::__serde::Serializer,
321            {
322                $crate::__serde::Serialize::serialize(&self.value, serializer)
323            }
324        }
325
326        impl<'a> $crate::__serde::Serialize for $ref_struct_name<'a> {
327            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
328            where
329                S: $crate::__serde::Serializer,
330            {
331                $crate::__serde::Serialize::serialize(&self.value, serializer)
332            }
333        }
334
335        impl<'de> $crate::__serde::Deserialize<'de> for $owned_struct_name {
336            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
337            where
338                D: $crate::__serde::Deserializer<'de>,
339            {
340                let value: String =
341                    <String as $crate::__serde::Deserialize>::deserialize(deserializer)?;
342                Self::new(value).map_err(<D::Error as $crate::__serde::de::Error>::custom)
343            }
344        }
345    };
346}
347
348#[cfg(not(feature = "serde"))]
349#[doc(hidden)]
350#[macro_export]
351macro_rules! __custom_string_serde_impl {
352    ($owned_struct_name:ident, $ref_struct_name:ident) => {};
353}
354
355#[cfg(test)]
356#[allow(dead_code)]
357mod tests {
358    use std::collections::HashMap;
359
360    custom_string!(
361        #[doc = "A lowercase string."],
362        Lower,
363        |s: &str| if !s.as_bytes().iter().all(|c| c.is_ascii_lowercase()) {
364            Err("not lowercase")
365        } else {
366            Ok(())
367        }
368    );
369
370    #[test]
371    fn validation() {
372        assert!(Lower::is_valid("abc"));
373        assert!(!Lower::is_valid("ABC"));
374        assert!(!Lower::is_valid("aBc"));
375
376        let result: Result<&str, _> = Lower::validate("abc");
377        assert!(result.is_ok());
378
379        let error: crate::ValidationError = Lower::validate("ABC").unwrap_err();
380        assert_eq!(error.message(), "not lowercase");
381        assert_eq!(error.to_string(), "not lowercase");
382    }
383
384    #[test]
385    fn validation_ref() {
386        assert!(LowerRef::is_valid("abc"));
387        assert!(!LowerRef::is_valid("ABC"));
388
389        let error: crate::ValidationError = LowerRef::validate("ABC").unwrap_err();
390        assert_eq!(error.message(), "not lowercase");
391    }
392
393    #[test]
394    fn construction() {
395        let owned: Lower = Lower::new("abc").unwrap();
396        assert_eq!(owned.value(), "abc");
397
398        let error: crate::ValidationError = Lower::new("ABC").unwrap_err();
399        assert_eq!(error.message(), "not lowercase");
400
401        let owned_from_string: Lower = Lower::new(String::from("abc")).unwrap();
402        assert_eq!(owned_from_string.value(), "abc");
403    }
404
405    #[test]
406    fn construction_ref() {
407        let reference: LowerRef = LowerRef::new("abc").unwrap();
408        assert_eq!(reference.value(), "abc");
409
410        let error: crate::ValidationError = LowerRef::new("ABC").unwrap_err();
411        assert_eq!(error.message(), "not lowercase");
412    }
413
414    #[test]
415    fn properties() {
416        let owned: Lower = Lower::new("abc").unwrap();
417        assert_eq!(owned.value(), "abc");
418        assert_eq!(owned.len(), 3);
419        assert!(!owned.is_empty());
420
421        let reference: LowerRef = LowerRef::new("abc").unwrap();
422        assert_eq!(reference.value(), "abc");
423        assert_eq!(reference.len(), 3);
424        assert!(!reference.is_empty());
425    }
426
427    #[test]
428    fn equality() {
429        let one: Lower = Lower::new("one").unwrap();
430        let two: Lower = Lower::new("two").unwrap();
431
432        assert_eq!(one, "one");
433        assert_eq!(one, one);
434        assert_ne!(one, "two");
435        assert_ne!(one, two);
436
437        let one_ref: LowerRef = LowerRef::new("one").unwrap();
438        let two_ref: LowerRef = LowerRef::new("two").unwrap();
439
440        assert_eq!(one_ref, "one");
441        assert_eq!(one_ref, one_ref);
442        assert_ne!(one_ref, "two");
443        assert_ne!(one_ref, two_ref);
444    }
445
446    #[test]
447    fn ordering() {
448        let a: Lower = Lower::new("a").unwrap();
449        let b: Lower = Lower::new("b").unwrap();
450        assert!(a < b);
451
452        let a_ref: LowerRef = LowerRef::new("a").unwrap();
453        let b_ref: LowerRef = LowerRef::new("b").unwrap();
454        assert!(a_ref < b_ref);
455    }
456
457    #[test]
458    fn display() {
459        let owned: Lower = Lower::new("abc").unwrap();
460        assert_eq!(format!("{}", owned), "abc");
461
462        let reference: LowerRef = LowerRef::new("abc").unwrap();
463        assert_eq!(format!("{}", reference), "abc");
464    }
465
466    #[test]
467    fn deref() {
468        let owned: Lower = Lower::new("abc").unwrap();
469        let s: &str = &owned;
470        assert_eq!(s, "abc");
471        assert!(owned.starts_with("ab"));
472
473        let reference: LowerRef = LowerRef::new("abc").unwrap();
474        let s: &str = &reference;
475        assert_eq!(s, "abc");
476        assert!(reference.starts_with("ab"));
477    }
478
479    #[test]
480    fn conversions_owned_ref() {
481        let owned: Lower = Lower::new("abc").unwrap();
482        let reference: LowerRef = owned.to_ref();
483        assert_eq!(reference.value(), "abc");
484
485        let back_to_owned: Lower = reference.to_owned();
486        assert_eq!(back_to_owned.value(), "abc");
487
488        let from_ref: Lower = Lower::from(reference);
489        assert_eq!(from_ref.value(), "abc");
490    }
491
492    #[test]
493    fn conversions_to_string() {
494        let owned: Lower = Lower::new("abc").unwrap();
495        let s: String = String::from(owned);
496        assert_eq!(s, "abc");
497
498        let reference: LowerRef = LowerRef::new("abc").unwrap();
499        let s: String = String::from(reference);
500        assert_eq!(s, "abc");
501    }
502
503    #[test]
504    fn try_from() {
505        let owned: Lower = Lower::try_from("abc").unwrap();
506        assert_eq!(owned.value(), "abc");
507
508        let owned_from_string: Lower = Lower::try_from(String::from("abc")).unwrap();
509        assert_eq!(owned_from_string.value(), "abc");
510
511        let reference: LowerRef = LowerRef::try_from("abc").unwrap();
512        assert_eq!(reference.value(), "abc");
513
514        let error: crate::ValidationError = Lower::try_from("ABC").unwrap_err();
515        assert_eq!(error.message(), "not lowercase");
516    }
517
518    #[test]
519    fn as_ref_and_borrow() {
520        use std::borrow::Borrow;
521
522        let owned: Lower = Lower::new("abc").unwrap();
523        let s: &str = owned.as_ref();
524        assert_eq!(s, "abc");
525        let s: &str = owned.borrow();
526        assert_eq!(s, "abc");
527
528        let reference: LowerRef = LowerRef::new("abc").unwrap();
529        let s: &str = reference.as_ref();
530        assert_eq!(s, "abc");
531        let s: &str = reference.borrow();
532        assert_eq!(s, "abc");
533    }
534
535    #[test]
536    fn hash_map_lookup() {
537        let mut map: HashMap<Lower, i32> = HashMap::new();
538        let key: Lower = Lower::new("abc").unwrap();
539        map.insert(key, 42);
540
541        assert_eq!(map.get("abc"), Some(&42));
542        assert_eq!(map.get("xyz"), None);
543    }
544
545    #[test]
546    fn clone() {
547        let owned: Lower = Lower::new("abc").unwrap();
548        let cloned: Lower = owned.clone();
549        assert_eq!(owned, cloned);
550
551        let reference: LowerRef = LowerRef::new("abc").unwrap();
552        let copied: LowerRef = reference;
553        assert_eq!(reference, copied);
554    }
555
556    #[test]
557    fn cross_type_equality() {
558        let owned: Lower = Lower::new("abc").unwrap();
559        let reference: LowerRef = LowerRef::new("abc").unwrap();
560
561        assert_eq!(owned, reference);
562        assert_eq!(reference, owned);
563
564        let other_ref: LowerRef = LowerRef::new("xyz").unwrap();
565        assert_ne!(owned, other_ref);
566        assert_ne!(other_ref, owned);
567    }
568
569    #[test]
570    fn cross_type_ordering() {
571        let owned_a: Lower = Lower::new("a").unwrap();
572        let ref_b: LowerRef = LowerRef::new("b").unwrap();
573
574        assert!(owned_a < ref_b);
575        assert!(ref_b > owned_a);
576
577        let owned_b: Lower = Lower::new("b").unwrap();
578        let ref_a: LowerRef = LowerRef::new("a").unwrap();
579
580        assert!(owned_b > ref_a);
581        assert!(ref_a < owned_b);
582    }
583
584    #[test]
585    fn from_str() {
586        let owned: Lower = "abc".parse().unwrap();
587        assert_eq!(owned.value(), "abc");
588
589        let error: crate::ValidationError = "ABC".parse::<Lower>().unwrap_err();
590        assert_eq!(error.message(), "not lowercase");
591    }
592
593    #[cfg(feature = "serde")]
594    mod serde_tests {
595        use super::*;
596
597        #[test]
598        fn serialize_owned() {
599            let owned: Lower = Lower::new("abc").unwrap();
600            let json: String = serde_json::to_string(&owned).unwrap();
601            assert_eq!(json, "\"abc\"");
602        }
603
604        #[test]
605        fn serialize_ref() {
606            let reference: LowerRef = LowerRef::new("abc").unwrap();
607            let json: String = serde_json::to_string(&reference).unwrap();
608            assert_eq!(json, "\"abc\"");
609        }
610
611        #[test]
612        fn deserialize_valid() {
613            let owned: Lower = serde_json::from_str("\"abc\"").unwrap();
614            assert_eq!(owned.value(), "abc");
615        }
616
617        #[test]
618        fn deserialize_invalid() {
619            let result: Result<Lower, _> = serde_json::from_str("\"ABC\"");
620            assert!(result.is_err());
621        }
622
623        #[test]
624        fn round_trip() {
625            let original: Lower = Lower::new("abc").unwrap();
626            let json: String = serde_json::to_string(&original).unwrap();
627            let deserialized: Lower = serde_json::from_str(&json).unwrap();
628            assert_eq!(original, deserialized);
629        }
630    }
631
632    #[test]
633    fn validation_error() {
634        let error: crate::ValidationError = crate::ValidationError::new("test error");
635        assert_eq!(error.message(), "test error");
636        assert_eq!(error.to_string(), "test error");
637        assert_eq!(error, crate::ValidationError::new("test error"));
638        assert_ne!(error, crate::ValidationError::new("other error"));
639    }
640}