kx_utils/
newtype.rs

1//
2/// Defines a new type that wraps another type.
3///
4/// You can specify an optional number of additional traits to be derived after the wrapped type.
5///
6/// # Example
7///
8/// ```rust
9/// use kx_utils::newtype;
10/// newtype!(Username, String, derive_more::Display, "The login name for the user");
11/// let username = Username::new("bob");
12/// assert_eq!(username.as_ref(), "bob");
13/// assert_eq!(format!("{username}"), r"bob"); // because `derive_more::Display`
14/// assert_eq!(format!("{username:?}"), r#"Username("bob")"#);
15/// let peeled = username.peel(); // moves value out of `username`
16/// assert_eq!(peeled, "bob".to_string());
17/// ```
18///
19/// In this example, [Username] also derives [derive_more::Display].
20#[macro_export]
21macro_rules! newtype {
22    ($name:ident, $type:ty $(, $derive:ty)*, $docs:literal) => {
23        #[doc = $docs]
24        #[derive(
25            Clone,
26            Debug,
27            derive_more::From,
28            PartialEq,
29            Eq,
30            derive_more::AsRef,
31            serde::Serialize,
32            serde::Deserialize,
33            $($derive),*
34        )]
35        pub struct $name($type);
36        impl $name {
37            pub fn new(value: impl Into<$type>) -> Self {
38                Self(value.into())
39            }
40            #[allow(clippy::missing_const_for_fn)]
41            #[must_use]
42            pub fn peel(self) -> $type {
43                self.0
44            }
45        }
46        impl std::ops::Deref for $name {
47            type Target = $type;
48
49            fn deref(&self) -> &Self::Target {
50                &self.0
51            }
52        }
53        impl std::ops::DerefMut for $name {
54            fn deref_mut(&mut self) -> &mut Self::Target {
55                &mut self.0
56            }
57        }
58        impl From<$name> for $type {
59            fn from(value: $name) -> $type {
60                value.peel()
61            }
62        }
63    };
64}
65
66#[cfg(test)]
67mod tests {
68
69    use derive_more::Display;
70
71    // a newtype with a String inner value
72    newtype!(A, String, "a type");
73
74    #[test]
75    fn newtype_reflexive() {
76        let a = A::new("bob");
77        assert_eq!(a, a);
78    }
79    #[test]
80    fn newtype_new_is_equal_from() {
81        assert_eq!(A::new("bob"), A::from("bob".to_string()));
82    }
83
84    #[test]
85    fn newtype_is_clone() {
86        let a1 = A::new("bob");
87        let a2 = a1.clone();
88        assert_eq!(a1, a2);
89    }
90
91    #[test]
92    fn newtype_is_debug() {
93        assert_eq!(format!("{:?}", A::new("bob")), r#"A("bob")"#);
94    }
95
96    #[test]
97    fn newtype_with_derive_display_is_display() {
98        newtype!(B, String, Display, "displayable");
99        assert_eq!(format!("{}", B::new("bob")), r"bob");
100    }
101
102    #[test]
103    fn newtype_is_peelable() {
104        let a = A::new("bob");
105        assert_eq!(a.peel(), "bob");
106    }
107
108    #[test]
109    fn newtype_as_ref() {
110        let a = A::new("bob");
111        let b: &str = a.as_ref();
112        assert_eq!(b, "bob");
113    }
114
115    #[test]
116    fn newtype_serialize() {
117        let a = A::new("bob");
118        let s = serde_json::to_string(&a).unwrap();
119        assert_eq!(s, "\"bob\"");
120    }
121
122    #[test]
123    fn newtype_deserialize() {
124        let a = A::new("bob");
125        let s: A = serde_json::from_str("\"bob\"").unwrap();
126        assert_eq!(s, a);
127    }
128}