pub trait Microtype {
type Inner;
fn new(inner: Self::Inner) -> Self;
fn into_inner(self) -> Self::Inner;
fn inner(&self) -> &Self::Inner;
fn inner_mut(&mut self) -> &mut Self::Inner;
fn transmute<T: Microtype<Inner = Self::Inner>>(self) -> T;
}
#[macro_export]
macro_rules! microtype {
($inner:ty => [$name:ident]) => {microtype!($inner => $name);};
($inner:ty => [$name:ident], $($traits:tt)*) => {microtype!($inner => $name, $($traits)*);};
($inner:ty => [$name:ident, $($names:ident),*]) => {
microtype!($inner => $name);
microtype!($inner => [$($names),*]);
};
($inner:ty => [$name:ident, $($names:ident),*], $($traits:tt)*) => {
microtype!($inner => $name, $($traits)*);
microtype!($inner => [$($names),*], $($traits)*);
};
($inner:ty => $name:ident) => {
microtype!($inner => $name, Debug, Clone, Eq, PartialEq);
};
($inner:ty => $name:ident, $($traits:tt),*) => {
#[repr(transparent)]
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(transparent)]
#[derive($($traits),*)]
pub struct $name {
inner: $inner,
}
impl $crate::Microtype for $name {
type Inner = $inner;
fn new(inner: Self::Inner) -> Self {
Self { inner }
}
fn into_inner(self) -> Self::Inner {
self.inner
}
fn inner(&self) -> &Self::Inner {
&self.inner
}
fn inner_mut(&mut self) -> &mut Self::Inner {
&mut self.inner
}
fn transmute<T: Microtype<Inner = Self::Inner>>(self) -> T {
T::new(self.into_inner())
}
}
};
}
#[macro_export]
macro_rules! copy_microtype {
($inner:ty => $name:ident) => {
microtype!($inner => $name, Debug, Clone, Eq, PartialEq, Copy);
}
}
#[cfg(test)]
mod tests {
use super::*;
microtype!(f64 => Coord, );
microtype!(String => Email);
microtype!(String => Username);
#[test]
fn email_example() {
let email = "email".to_string();
let mut email = Email::new(email);
assert_eq!(email.inner(), "email");
assert_eq!(email.inner_mut(), "email");
assert_eq!(email.into_inner(), "email");
}
#[test]
fn email_clone() {
let email = Email::new("email".into());
let cloned = email.clone();
assert_eq!(email, cloned);
}
#[test]
fn can_transmute() {
let email = Email::new("user@example.com".to_string());
let cloned = email.clone();
let username = email.transmute::<Username>();
assert_eq!(cloned.into_inner(), username.into_inner());
}
microtype!(String => [Name, Address]);
#[test]
fn multiple_declarations() {
let name = Name::new("name".into());
let address = Address::new("example road".into());
assert_eq!(name.into_inner(), "name");
assert_eq!(address.into_inner(), "example road");
}
microtype!(f64 => [X, Y, Z], Clone, Copy, PartialEq, Debug);
#[test]
fn multiple_declarations_with_traits() {
let x = X::new(1.0);
let y = Y::new(2.0);
let z = Z::new(3.0);
assert_eq!(x.into_inner(), 1.0);
assert_eq!(y.into_inner(), 2.0);
assert_eq!(z.into_inner(), 3.0);
}
#[derive(serde::Deserialize)]
struct Example {
email: Email,
username: Username,
}
#[test]
fn serde_transparent() {
let json = r#"{"email": "1234", "username": "2345"}"#;
let example: Example = serde_json::from_str(json).unwrap();
assert_eq!(example.email.inner(), "1234");
assert_eq!(example.username.inner(), "2345");
}
}