kx-utils 0.1.3

Common utils incubator
Documentation
//
/// 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);
    }
}