kx_utils/
newtype.rs

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

            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
        impl std::ops::DerefMut for $name {
            fn deref_mut(&mut self) -> &mut Self::Target {
                &mut self.0
            }
        }
        impl From<$name> for $type {
            fn from(value: $name) -> $type {
                value.peel()
            }
        }
    };
}

#[cfg(test)]
mod tests {

    use derive_more::Display;

    // a newtype with a String inner value
    newtype!(A, String, "a type");

    #[test]
    fn newtype_reflexive() {
        let a = A::new("bob");
        assert_eq!(a, a);
    }
    #[test]
    fn newtype_new_is_equal_from() {
        assert_eq!(A::new("bob"), A::from("bob".to_string()));
    }

    #[test]
    fn newtype_is_clone() {
        let a1 = A::new("bob");
        let a2 = a1.clone();
        assert_eq!(a1, a2);
    }

    #[test]
    fn newtype_is_debug() {
        assert_eq!(format!("{:?}", A::new("bob")), r#"A("bob")"#);
    }

    #[test]
    fn newtype_with_derive_display_is_display() {
        newtype!(B, String, Display, "displayable");
        assert_eq!(format!("{}", B::new("bob")), r"bob");
    }

    #[test]
    fn newtype_is_peelable() {
        let a = A::new("bob");
        assert_eq!(a.peel(), "bob");
    }

    #[test]
    fn newtype_as_ref() {
        let a = A::new("bob");
        let b: &str = a.as_ref();
        assert_eq!(b, "bob");
    }

    #[test]
    fn newtype_serialize() {
        let a = A::new("bob");
        let s = serde_json::to_string(&a).unwrap();
        assert_eq!(s, "\"bob\"");
    }

    #[test]
    fn newtype_deserialize() {
        let a = A::new("bob");
        let s: A = serde_json::from_str("\"bob\"").unwrap();
        assert_eq!(s, a);
    }
}