From f658487c504bdd1d13c1d074b396a6881af480fa Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 13 Apr 2022 20:13:26 +0200 Subject: [PATCH] identifiers: Replace most macro_rules! code with proc-macro code --- crates/ruma-common/src/identifiers.rs | 3 - .../src/identifiers/client_secret.rs | 13 +- .../ruma-common/src/identifiers/device_id.rs | 8 +- .../src/identifiers/device_key_id.rs | 13 +- .../ruma-common/src/identifiers/event_id.rs | 13 +- crates/ruma-common/src/identifiers/key_id.rs | 19 + .../ruma-common/src/identifiers/key_name.rs | 8 +- crates/ruma-common/src/identifiers/macros.rs | 438 ---------------- crates/ruma-common/src/identifiers/mxc_uri.rs | 7 +- .../src/identifiers/room_alias_id.rs | 13 +- crates/ruma-common/src/identifiers/room_id.rs | 9 +- .../ruma-common/src/identifiers/room_name.rs | 13 +- .../src/identifiers/room_or_room_alias_id.rs | 13 +- .../src/identifiers/server_name.rs | 13 +- .../ruma-common/src/identifiers/session_id.rs | 13 +- .../src/identifiers/transaction_id.rs | 8 +- crates/ruma-common/src/identifiers/user_id.rs | 8 +- crates/ruma-macros/src/identifiers.rs | 484 +++++++++++++++++- crates/ruma-macros/src/lib.rs | 10 +- 19 files changed, 560 insertions(+), 546 deletions(-) delete mode 100644 crates/ruma-common/src/identifiers/macros.rs diff --git a/crates/ruma-common/src/identifiers.rs b/crates/ruma-common/src/identifiers.rs index 115b712e..dcbe88bf 100644 --- a/crates/ruma-common/src/identifiers.rs +++ b/crates/ruma-common/src/identifiers.rs @@ -33,9 +33,6 @@ pub use self::{ #[doc(inline)] pub use ruma_identifiers_validation::error::Error as IdParseError; -#[macro_use] -mod macros; - pub mod matrix_uri; pub mod user_id; diff --git a/crates/ruma-common/src/identifiers/client_secret.rs b/crates/ruma-common/src/identifiers/client_secret.rs index bde826c5..e7c7204d 100644 --- a/crates/ruma-common/src/identifiers/client_secret.rs +++ b/crates/ruma-common/src/identifiers/client_secret.rs @@ -1,5 +1,7 @@ //! Client secret identifier. +use ruma_macros::IdZst; + /// A client secret. /// /// Client secrets in Matrix are opaque character sequences of `[0-9a-zA-Z.=_-]`. Their length must @@ -9,7 +11,8 @@ /// use `ClientSecret::new()` to generate a random one. If that function is not available for you, /// you need to activate this crate's `rand` Cargo feature. #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::client_secret::validate)] pub struct ClientSecret(str); impl ClientSecret { @@ -24,14 +27,6 @@ impl ClientSecret { } } -owned_identifier!(OwnedClientSecret, ClientSecret); - -opaque_identifier_validated!( - ClientSecret, - OwnedClientSecret, - ruma_identifiers_validation::client_secret::validate -); - #[cfg(test)] mod tests { use std::convert::TryFrom; diff --git a/crates/ruma-common/src/identifiers/device_id.rs b/crates/ruma-common/src/identifiers/device_id.rs index db7015ba..2b3bf29d 100644 --- a/crates/ruma-common/src/identifiers/device_id.rs +++ b/crates/ruma-common/src/identifiers/device_id.rs @@ -1,3 +1,5 @@ +use ruma_macros::IdZst; + #[cfg(feature = "rand")] use super::generate_localpart; @@ -26,13 +28,9 @@ use super::generate_localpart; /// assert_eq!(owned_id.as_str(), "ijklmnop"); /// ``` #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] pub struct DeviceId(str); -owned_identifier!(OwnedDeviceId, DeviceId); - -opaque_identifier!(DeviceId, OwnedDeviceId); - impl DeviceId { /// Generates a random `DeviceId`, suitable for assignment to a new device. #[cfg(feature = "rand")] diff --git a/crates/ruma-common/src/identifiers/device_key_id.rs b/crates/ruma-common/src/identifiers/device_key_id.rs index 622f8b8f..7acdfb2a 100644 --- a/crates/ruma-common/src/identifiers/device_key_id.rs +++ b/crates/ruma-common/src/identifiers/device_key_id.rs @@ -1,20 +1,15 @@ //! Identifiers for device keys for end-to-end encryption. +use ruma_macros::IdZst; + use super::{crypto_algorithms::DeviceKeyAlgorithm, DeviceId}; /// A key algorithm and a device id, combined with a ':'. #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::device_key_id::validate)] pub struct DeviceKeyId(str); -owned_identifier!(OwnedDeviceKeyId, DeviceKeyId); - -opaque_identifier_validated!( - DeviceKeyId, - OwnedDeviceKeyId, - ruma_identifiers_validation::device_key_id::validate -); - impl DeviceKeyId { /// Create a `DeviceKeyId` from a `DeviceKeyAlgorithm` and a `DeviceId`. pub fn from_parts(algorithm: DeviceKeyAlgorithm, device_id: &DeviceId) -> Box { diff --git a/crates/ruma-common/src/identifiers/event_id.rs b/crates/ruma-common/src/identifiers/event_id.rs index 364014bc..b876ae79 100644 --- a/crates/ruma-common/src/identifiers/event_id.rs +++ b/crates/ruma-common/src/identifiers/event_id.rs @@ -1,5 +1,7 @@ //! Matrix event identifiers. +use ruma_macros::IdZst; + use super::ServerName; /// A Matrix [event ID]. @@ -35,17 +37,10 @@ use super::ServerName; /// /// [event ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::event_id::validate)] pub struct EventId(str); -owned_identifier!(OwnedEventId, EventId); - -opaque_identifier_validated!( - EventId, - OwnedEventId, - ruma_identifiers_validation::event_id::validate -); - impl EventId { /// Attempts to generate an `EventId` for the given origin server with a localpart consisting /// of 18 random ASCII characters. diff --git a/crates/ruma-common/src/identifiers/key_id.rs b/crates/ruma-common/src/identifiers/key_id.rs index 7cd35226..2407dc2e 100644 --- a/crates/ruma-common/src/identifiers/key_id.rs +++ b/crates/ruma-common/src/identifiers/key_id.rs @@ -263,5 +263,24 @@ impl TryFrom for Box> { } } +macro_rules! partial_eq_string { + ($id:ty $([$( $g:ident ),*])?) => { + partial_eq_string!(@imp $(<$($g),*>)?, $id, str); + partial_eq_string!(@imp $(<$($g),*>)?, $id, &str); + partial_eq_string!(@imp $(<$($g),*>)?, $id, String); + partial_eq_string!(@imp $(<$($g),*>)?, str, $id); + partial_eq_string!(@imp $(<$($g),*>)?, &str, $id); + partial_eq_string!(@imp $(<$($g),*>)?, String, $id); + }; + (@imp $(<$( $g:ident ),*>)?, $l:ty, $r:ty) => { + impl $(<$($g),*>)? PartialEq<$r> for $l { + fn eq(&self, other: &$r) -> bool { + AsRef::::as_ref(self) + == AsRef::::as_ref(other) + } + } + } +} + #[rustfmt::skip] partial_eq_string!(KeyId [A, K]); diff --git a/crates/ruma-common/src/identifiers/key_name.rs b/crates/ruma-common/src/identifiers/key_name.rs index 2e1b58a6..3b5ee994 100644 --- a/crates/ruma-common/src/identifiers/key_name.rs +++ b/crates/ruma-common/src/identifiers/key_name.rs @@ -1,11 +1,9 @@ +use ruma_macros::IdZst; + /// A Matrix key identifier. /// /// Key identifiers in Matrix are opaque character sequences of `[a-zA-Z_]`. This type is /// provided simply for its semantic value. #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] pub struct KeyName(str); - -owned_identifier!(OwnedKeyName, KeyName); - -opaque_identifier!(KeyName, OwnedKeyName); diff --git a/crates/ruma-common/src/identifiers/macros.rs b/crates/ruma-common/src/identifiers/macros.rs deleted file mode 100644 index 81b425a7..00000000 --- a/crates/ruma-common/src/identifiers/macros.rs +++ /dev/null @@ -1,438 +0,0 @@ -/// Declares an item with a doc attribute computed by some macro expression. -/// This allows documentation to be dynamically generated based on input. -/// Necessary to work around . -macro_rules! doc_concat { - ( $( #[doc = $doc:expr] $( $thing:tt )* )* ) => ( $( #[doc = $doc] $( $thing )* )* ); -} - -macro_rules! partial_eq_string { - ($id:ty $([$( $g:ident ),*])?) => { - partial_eq_string!(@imp $(<$($g),*>)?, $id, str); - partial_eq_string!(@imp $(<$($g),*>)?, $id, &str); - partial_eq_string!(@imp $(<$($g),*>)?, $id, String); - partial_eq_string!(@imp $(<$($g),*>)?, str, $id); - partial_eq_string!(@imp $(<$($g),*>)?, &str, $id); - partial_eq_string!(@imp $(<$($g),*>)?, String, $id); - }; - (@imp $(<$( $g:ident ),*>)?, $l:ty, $r:ty) => { - impl $(<$($g),*>)? PartialEq<$r> for $l { - fn eq(&self, other: &$r) -> bool { - AsRef::::as_ref(self) - == AsRef::::as_ref(other) - } - } - } -} - -macro_rules! owned_identifier { - ($owned:ident, $id:ident) => { - #[doc = concat!("Owned variant of ", stringify!($id))] - /// - /// The wrapper type for this type is variable, by default it'll use [`Box`], - /// but you can change that by setting "`--cfg=ruma_identifiers_storage=...`" using - /// `RUSTFLAGS` or `.cargo/config.toml` (under `[build]` -> `rustflags = ["..."]`) - /// to the following; - /// - `ruma_identifiers_storage="Arc"` to use [`Arc`](std::sync::Arc) as a wrapper type. - #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct $owned { - #[cfg(not(any(ruma_identifiers_storage = "Arc")))] - inner: Box<$id>, - #[cfg(ruma_identifiers_storage = "Arc")] - inner: std::sync::Arc<$id>, - } - - impl AsRef<$id> for $owned { - fn as_ref(&self) -> &$id { - &*self.inner - } - } - - impl AsRef for $owned { - fn as_ref(&self) -> &str { - (*self.inner).as_ref() - } - } - - impl std::ops::Deref for $owned { - type Target = $id; - - fn deref(&self) -> &Self::Target { - &self.inner - } - } - - impl std::borrow::Borrow<$id> for $owned { - fn borrow(&self) -> &$id { - self.as_ref() - } - } - - impl From<&'_ $id> for $owned { - fn from(id: &$id) -> $owned { - $owned { inner: id.into() } - } - } - - impl From> for $owned { - fn from(b: Box<$id>) -> $owned { - Self { inner: b.into() } - } - } - - impl From> for $owned { - fn from(a: std::sync::Arc<$id>) -> $owned { - Self { - #[cfg(not(any(ruma_identifiers_storage = "Arc")))] - inner: a.as_ref().into(), - #[cfg(ruma_identifiers_storage = "Arc")] - inner: a, - } - } - } - - #[cfg(feature = "serde")] - impl serde::Serialize for $owned { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.as_ref().as_str()) - } - } - - partial_eq_string!($owned); - - impl PartialEq> for $owned { - fn eq(&self, other: &Box<$id>) -> bool { - AsRef::<$id>::as_ref(self) == AsRef::<$id>::as_ref(other) - } - } - - impl PartialEq<$owned> for Box<$id> { - fn eq(&self, other: &$owned) -> bool { - AsRef::<$id>::as_ref(self) == AsRef::<$id>::as_ref(other) - } - } - }; -} - -macro_rules! opaque_identifier_common_impls { - ($id:ident, $owned:ident) => { - impl $id { - pub(super) fn from_borrowed(s: &str) -> &Self { - unsafe { std::mem::transmute(s) } - } - - pub(super) fn from_owned(s: Box) -> Box { - unsafe { Box::from_raw(Box::into_raw(s) as _) } - } - - pub(super) fn from_rc(s: std::rc::Rc) -> std::rc::Rc { - unsafe { std::rc::Rc::from_raw(std::rc::Rc::into_raw(s) as _) } - } - - pub(super) fn from_arc(s: std::sync::Arc) -> std::sync::Arc { - unsafe { std::sync::Arc::from_raw(std::sync::Arc::into_raw(s) as _) } - } - - pub(super) fn into_owned(self: Box) -> Box { - unsafe { Box::from_raw(Box::into_raw(self) as _) } - } - - doc_concat! { - #[doc = concat!("Creates a string slice from this `", stringify!($id), "`.")] - pub fn as_str(&self) -> &str { - &self.0 - } - } - - doc_concat! { - #[doc = concat!("Creates a byte slice from this `", stringify!($id), "`.")] - pub fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() - } - } - } - - impl Clone for Box<$id> { - fn clone(&self) -> Self { - (**self).into() - } - } - - // impl ToOwned for $id { - // type Owned = $owned; - - // fn to_owned(&self) -> Self::Owned { - // Self::from_owned(self.0.into()).into() - // } - // } - - // TODO swap below with above after codebase has been converted - // to not use `to_owned` as equivalent to "into Box" - impl ToOwned for $id { - type Owned = Box<$id>; - - fn to_owned(&self) -> Self::Owned { - Self::from_owned(self.0.into()) - } - } - - impl AsRef for $id { - fn as_ref(&self) -> &str { - self.as_str() - } - } - - impl AsRef for Box<$id> { - fn as_ref(&self) -> &str { - self.as_str() - } - } - - impl From<&$id> for Box<$id> { - fn from(id: &$id) -> Self { - $id::from_owned(id.0.into()) - } - } - - impl From<&$id> for std::rc::Rc<$id> { - fn from(s: &$id) -> std::rc::Rc<$id> { - let rc = std::rc::Rc::::from(s.as_str()); - <$id>::from_rc(rc) - } - } - - impl From<&$id> for std::sync::Arc<$id> { - fn from(s: &$id) -> std::sync::Arc<$id> { - let arc = std::sync::Arc::::from(s.as_str()); - <$id>::from_arc(arc) - } - } - - impl PartialEq<$id> for Box<$id> { - fn eq(&self, other: &$id) -> bool { - self.as_str() == other.as_str() - } - } - - impl PartialEq<&'_ $id> for Box<$id> { - fn eq(&self, other: &&$id) -> bool { - self.as_str() == other.as_str() - } - } - - impl PartialEq> for $id { - fn eq(&self, other: &Box<$id>) -> bool { - self.as_str() == other.as_str() - } - } - - impl PartialEq> for &'_ $id { - fn eq(&self, other: &Box<$id>) -> bool { - self.as_str() == other.as_str() - } - } - - impl std::fmt::Debug for $id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(self.as_str(), f) - } - } - - impl std::fmt::Display for $id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_str()) - } - } - - impl serde::Serialize for $id { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.as_str()) - } - } - - partial_eq_string!($id); - partial_eq_string!(Box<$id>); // todo: Remove when all instances of Box have been converted to Owned - }; -} - -macro_rules! opaque_identifier { - ($id:ident, $owned:ident) => { - opaque_identifier_common_impls!($id, $owned); - - impl<'a> From<&'a str> for &'a $id { - fn from(s: &'a str) -> Self { - $id::from_borrowed(s) - } - } - - impl From<&str> for Box<$id> { - fn from(s: &str) -> Self { - $id::from_owned(s.into()) - } - } - - impl From> for Box<$id> { - fn from(s: Box) -> Self { - $id::from_owned(s) - } - } - - impl From for Box<$id> { - fn from(s: String) -> Self { - $id::from_owned(s.into()) - } - } - - impl From> for Box { - fn from(id: Box<$id>) -> Self { - id.into_owned() - } - } - - impl From> for String { - fn from(id: Box<$id>) -> Self { - id.into_owned().into() - } - } - - impl<'de> serde::Deserialize<'de> for Box<$id> { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Box::::deserialize(deserializer).map($id::from_owned) - } - } - - #[cfg(feature = "serde")] - impl<'de> serde::Deserialize<'de> for $owned { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Box::::deserialize(deserializer).map($id::from_owned).map(Into::into) - } - } - }; -} - -macro_rules! opaque_identifier_validated { - ($id:ident, $owned:ident, $validate_id:expr) => { - impl $id { - #[rustfmt::skip] - doc_concat! { - #[doc = concat!("\ - Try parsing a `&str` into a `Box<", stringify!($id), ">`.\n\ - \n\ - The same can also be done using `FromStr`, `TryFrom` or `TryInto`.\n\ - This function is simply more constrained and thus useful in generic contexts.\ - ")] - pub fn parse( - s: impl AsRef + Into>, - ) -> Result, crate::IdParseError> { - $validate_id(s.as_ref())?; - Ok($id::from_owned(s.into())) - } - } - - doc_concat! { - #[doc = concat!("Try parsing a `&str` into an `Rc<", stringify!($id), ">`.")] - pub fn parse_rc( - s: impl AsRef + Into>, - ) -> Result, crate::IdParseError> { - $validate_id(s.as_ref())?; - Ok($id::from_rc(s.into())) - } - } - - doc_concat! { - #[doc = concat!("Try parsing a `&str` into an `Arc<", stringify!($id), ">`.")] - pub fn parse_arc( - s: impl AsRef + Into>, - ) -> Result, crate::IdParseError> { - $validate_id(s.as_ref())?; - Ok($id::from_arc(s.into())) - } - } - } - - opaque_identifier_common_impls!($id, $owned); - - impl From> for String { - fn from(id: Box<$id>) -> Self { - id.into_owned().into() - } - } - - impl<'de> serde::Deserialize<'de> for Box<$id> { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - - let s = String::deserialize(deserializer)?; - - match $id::parse(s) { - Ok(o) => Ok(o), - Err(e) => Err(D::Error::custom(e)), - } - } - } - - #[cfg(feature = "serde")] - impl<'de> serde::Deserialize<'de> for $owned { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - - let s = String::deserialize(deserializer)?; - - match $id::parse(s) { - Ok(o) => Ok(o.into()), - Err(e) => Err(D::Error::custom(e)), - } - } - } - - impl<'a> std::convert::TryFrom<&'a str> for &'a $id { - type Error = crate::IdParseError; - - fn try_from(s: &'a str) -> Result { - $validate_id(s)?; - Ok($id::from_borrowed(s)) - } - } - - impl std::str::FromStr for Box<$id> { - type Err = crate::IdParseError; - - fn from_str(s: &str) -> Result { - $id::parse(s) - } - } - - impl std::convert::TryFrom<&str> for Box<$id> { - type Error = crate::IdParseError; - - fn try_from(s: &str) -> Result { - $id::parse(s) - } - } - - impl std::convert::TryFrom for Box<$id> { - type Error = crate::IdParseError; - - fn try_from(s: String) -> Result { - $id::parse(s) - } - } - }; -} diff --git a/crates/ruma-common/src/identifiers/mxc_uri.rs b/crates/ruma-common/src/identifiers/mxc_uri.rs index 9b6a79ce..1a09cd4e 100644 --- a/crates/ruma-common/src/identifiers/mxc_uri.rs +++ b/crates/ruma-common/src/identifiers/mxc_uri.rs @@ -5,6 +5,7 @@ use std::num::NonZeroU8; use ruma_identifiers_validation::{error::MxcUriError, mxc_uri::validate}; +use ruma_macros::IdZst; use super::ServerName; @@ -15,13 +16,9 @@ type Result = std::result::Result; /// [MXC URI]: https://spec.matrix.org/v1.2/client-server-api/#matrix-content-mxc-uris #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] pub struct MxcUri(str); -owned_identifier!(OwnedMxcUri, MxcUri); - -opaque_identifier!(MxcUri, OwnedMxcUri); - impl MxcUri { /// If this is a valid MXC URI, returns the media ID. pub fn media_id(&self) -> Result<&str> { diff --git a/crates/ruma-common/src/identifiers/room_alias_id.rs b/crates/ruma-common/src/identifiers/room_alias_id.rs index 0d4ce65d..cf5101a1 100644 --- a/crates/ruma-common/src/identifiers/room_alias_id.rs +++ b/crates/ruma-common/src/identifiers/room_alias_id.rs @@ -1,5 +1,7 @@ //! Matrix room alias identifiers. +use ruma_macros::IdZst; + use super::{matrix_uri::UriAction, server_name::ServerName, EventId, MatrixToUri, MatrixUri}; /// A Matrix [room alias ID]. @@ -15,17 +17,10 @@ use super::{matrix_uri::UriAction, server_name::ServerName, EventId, MatrixToUri /// /// [room alias ID]: https://spec.matrix.org/v1.2/appendices/#room-aliases #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::room_alias_id::validate)] pub struct RoomAliasId(str); -owned_identifier!(OwnedRoomAliasId, RoomAliasId); - -opaque_identifier_validated!( - RoomAliasId, - OwnedRoomAliasId, - ruma_identifiers_validation::room_alias_id::validate -); - impl RoomAliasId { /// Returns the room's alias. pub fn alias(&self) -> &str { diff --git a/crates/ruma-common/src/identifiers/room_id.rs b/crates/ruma-common/src/identifiers/room_id.rs index ac603b8f..24aae9c7 100644 --- a/crates/ruma-common/src/identifiers/room_id.rs +++ b/crates/ruma-common/src/identifiers/room_id.rs @@ -1,5 +1,7 @@ //! Matrix room identifiers. +use ruma_macros::IdZst; + use super::{matrix_uri::UriAction, EventId, MatrixToUri, MatrixUri, ServerName}; /// A Matrix [room ID]. @@ -15,13 +17,10 @@ use super::{matrix_uri::UriAction, EventId, MatrixToUri, MatrixUri, ServerName}; /// /// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::room_id::validate)] pub struct RoomId(str); -owned_identifier!(OwnedRoomId, RoomId); - -opaque_identifier_validated!(RoomId, OwnedRoomId, ruma_identifiers_validation::room_id::validate); - impl RoomId { /// Attempts to generate a `RoomId` for the given origin server with a localpart consisting of /// 18 random ASCII characters. diff --git a/crates/ruma-common/src/identifiers/room_name.rs b/crates/ruma-common/src/identifiers/room_name.rs index 60647a97..58cf33a9 100644 --- a/crates/ruma-common/src/identifiers/room_name.rs +++ b/crates/ruma-common/src/identifiers/room_name.rs @@ -1,16 +1,11 @@ //! Matrix room name. +use ruma_macros::IdZst; + /// The name of a room. /// /// It can't exceed 255 bytes or be empty. #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::room_name::validate)] pub struct RoomName(str); - -owned_identifier!(OwnedRoomName, RoomName); - -opaque_identifier_validated!( - RoomName, - OwnedRoomName, - ruma_identifiers_validation::room_name::validate -); diff --git a/crates/ruma-common/src/identifiers/room_or_room_alias_id.rs b/crates/ruma-common/src/identifiers/room_or_room_alias_id.rs index 53266d8a..955f4096 100644 --- a/crates/ruma-common/src/identifiers/room_or_room_alias_id.rs +++ b/crates/ruma-common/src/identifiers/room_or_room_alias_id.rs @@ -2,6 +2,8 @@ use std::{convert::TryFrom, hint::unreachable_unchecked}; +use ruma_macros::IdZst; + use super::{server_name::ServerName, RoomAliasId, RoomId}; /// A Matrix [room ID] or a Matrix [room alias ID]. @@ -24,17 +26,10 @@ use super::{server_name::ServerName, RoomAliasId, RoomId}; /// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids /// [room alias ID]: https://spec.matrix.org/v1.2/appendices/#room-aliases #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::room_id_or_alias_id::validate)] pub struct RoomOrAliasId(str); -owned_identifier!(OwnedRoomOrAliasId, RoomOrAliasId); - -opaque_identifier_validated!( - RoomOrAliasId, - OwnedRoomOrAliasId, - ruma_identifiers_validation::room_id_or_alias_id::validate -); - impl RoomOrAliasId { /// Returns the local part (everything after the `!` or `#` and before the first colon). pub fn localpart(&self) -> &str { diff --git a/crates/ruma-common/src/identifiers/server_name.rs b/crates/ruma-common/src/identifiers/server_name.rs index 2e27c0b7..3beb562e 100644 --- a/crates/ruma-common/src/identifiers/server_name.rs +++ b/crates/ruma-common/src/identifiers/server_name.rs @@ -2,23 +2,18 @@ use std::net::Ipv4Addr; +use ruma_macros::IdZst; + /// A Matrix-spec compliant [server name]. /// /// It consists of a host and an optional port (separated by a colon if present). /// /// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::server_name::validate)] pub struct ServerName(str); -owned_identifier!(OwnedServerName, ServerName); - -opaque_identifier_validated!( - ServerName, - OwnedServerName, - ruma_identifiers_validation::server_name::validate -); - impl ServerName { /// Returns the host of the server name. /// diff --git a/crates/ruma-common/src/identifiers/session_id.rs b/crates/ruma-common/src/identifiers/session_id.rs index a47b1502..e7e9ff0e 100644 --- a/crates/ruma-common/src/identifiers/session_id.rs +++ b/crates/ruma-common/src/identifiers/session_id.rs @@ -1,17 +1,12 @@ //! Matrix session ID. +use ruma_macros::IdZst; + /// A session ID. /// /// Session IDs in Matrix are opaque character sequences of `[0-9a-zA-Z.=_-]`. Their length must /// must not exceed 255 characters. #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::session_id::validate)] pub struct SessionId(str); - -owned_identifier!(OwnedSessionId, SessionId); - -opaque_identifier_validated!( - SessionId, - OwnedSessionId, - ruma_identifiers_validation::session_id::validate -); diff --git a/crates/ruma-common/src/identifiers/transaction_id.rs b/crates/ruma-common/src/identifiers/transaction_id.rs index cdb4b1a2..e3c89694 100644 --- a/crates/ruma-common/src/identifiers/transaction_id.rs +++ b/crates/ruma-common/src/identifiers/transaction_id.rs @@ -1,3 +1,5 @@ +use ruma_macros::IdZst; + /// A Matrix transaction ID. /// /// Transaction IDs in Matrix are opaque strings. This type is provided simply for its semantic @@ -7,7 +9,7 @@ /// `TransactionId::new()` to generate a random one. If that function is not available for you, you /// need to activate this crate's `rand` Cargo feature. #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] pub struct TransactionId(str); impl TransactionId { @@ -21,7 +23,3 @@ impl TransactionId { Self::from_owned(id.to_simple().to_string().into_boxed_str()) } } - -owned_identifier!(OwnedTransactionId, TransactionId); - -opaque_identifier!(TransactionId, OwnedTransactionId); diff --git a/crates/ruma-common/src/identifiers/user_id.rs b/crates/ruma-common/src/identifiers/user_id.rs index 511217d2..e677d216 100644 --- a/crates/ruma-common/src/identifiers/user_id.rs +++ b/crates/ruma-common/src/identifiers/user_id.rs @@ -17,13 +17,10 @@ use super::{matrix_uri::UriAction, IdParseError, MatrixToUri, MatrixUri, ServerN /// /// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers #[repr(transparent)] -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +#[ruma_id(validate = ruma_identifiers_validation::user_id::validate)] pub struct UserId(str); -owned_identifier!(OwnedUserId, UserId); - -opaque_identifier_validated!(UserId, OwnedUserId, ruma_identifiers_validation::user_id::validate); - impl UserId { /// Attempts to generate a `UserId` for the given origin server with a localpart consisting of /// 12 random ASCII characters. @@ -150,6 +147,7 @@ impl UserId { } pub use ruma_identifiers_validation::user_id::localpart_is_fully_conforming; +use ruma_macros::IdZst; #[cfg(test)] mod tests { diff --git a/crates/ruma-macros/src/identifiers.rs b/crates/ruma-macros/src/identifiers.rs index feaa0421..6d0570ae 100644 --- a/crates/ruma-macros/src/identifiers.rs +++ b/crates/ruma-macros/src/identifiers.rs @@ -1,6 +1,12 @@ //! Methods and types for generating identifiers. -use syn::{parse::Parse, LitStr, Path, Token}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Ident, ItemStruct, LitStr, Path, Token, +}; pub struct IdentifierInput { pub dollar_crate: Path, @@ -8,7 +14,7 @@ pub struct IdentifierInput { } impl Parse for IdentifierInput { - fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let dollar_crate = input.parse()?; let _: Token![,] = input.parse()?; let id = input.parse()?; @@ -16,3 +22,477 @@ impl Parse for IdentifierInput { Ok(Self { dollar_crate, id }) } } + +pub fn expand_id_zst(input: ItemStruct) -> syn::Result { + let id = &input.ident; + let owned = format_ident!("Owned{}", id); + + let owned_decl = expand_owned_id(id, &owned); + + let meta = input.attrs.iter().filter(|attr| attr.path.is_ident("ruma_id")).try_fold( + IdZstMeta::default(), + |meta, attr| { + let list: Punctuated = + attr.parse_args_with(Punctuated::parse_terminated)?; + + list.into_iter().try_fold(meta, IdZstMeta::merge) + }, + )?; + + let extra_impls = if let Some(validate) = meta.validate { + expand_checked_impls(id, &owned, validate) + } else { + expand_unchecked_impls(id, &owned) + }; + + let as_str_docs = format!("Creates a string slice from this `{}`.", id); + let as_bytes_docs = format!("Creates a byte slice from this `{}`.", id); + + let partial_eq_string = expand_partial_eq_string(id); + // FIXME: Remove? + let box_partial_eq_string = expand_partial_eq_string(quote! { Box<#id> }); + + Ok(quote! { + #owned_decl + + impl #id { + pub(super) fn from_borrowed(s: &str) -> &Self { + unsafe { std::mem::transmute(s) } + } + + pub(super) fn from_owned(s: Box) -> Box { + unsafe { Box::from_raw(Box::into_raw(s) as _) } + } + + pub(super) fn from_rc(s: std::rc::Rc) -> std::rc::Rc { + unsafe { std::rc::Rc::from_raw(std::rc::Rc::into_raw(s) as _) } + } + + pub(super) fn from_arc(s: std::sync::Arc) -> std::sync::Arc { + unsafe { std::sync::Arc::from_raw(std::sync::Arc::into_raw(s) as _) } + } + + pub(super) fn into_owned(self: Box) -> Box { + unsafe { Box::from_raw(Box::into_raw(self) as _) } + } + + #[doc = #as_str_docs] + pub fn as_str(&self) -> &str { + &self.0 + } + + #[doc = #as_bytes_docs] + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } + } + + impl Clone for Box<#id> { + fn clone(&self) -> Self { + (**self).into() + } + } + + impl ToOwned for #id { + type Owned = Box<#id>; + + fn to_owned(&self) -> Self::Owned { + Self::from_owned(self.0.into()) + } + } + + impl AsRef for #id { + fn as_ref(&self) -> &str { + self.as_str() + } + } + + impl AsRef for Box<#id> { + fn as_ref(&self) -> &str { + self.as_str() + } + } + + impl From> for String { + fn from(id: Box<#id>) -> Self { + id.into_owned().into() + } + } + + impl From<&#id> for Box<#id> { + fn from(id: &#id) -> Self { + #id::from_owned(id.0.into()) + } + } + + impl From<&#id> for std::rc::Rc<#id> { + fn from(s: &#id) -> std::rc::Rc<#id> { + let rc = std::rc::Rc::::from(s.as_str()); + <#id>::from_rc(rc) + } + } + + impl From<&#id> for std::sync::Arc<#id> { + fn from(s: &#id) -> std::sync::Arc<#id> { + let arc = std::sync::Arc::::from(s.as_str()); + <#id>::from_arc(arc) + } + } + + impl PartialEq<#id> for Box<#id> { + fn eq(&self, other: &#id) -> bool { + self.as_str() == other.as_str() + } + } + + impl PartialEq<&'_ #id> for Box<#id> { + fn eq(&self, other: &&#id) -> bool { + self.as_str() == other.as_str() + } + } + + impl PartialEq> for #id { + fn eq(&self, other: &Box<#id>) -> bool { + self.as_str() == other.as_str() + } + } + + impl PartialEq> for &'_ #id { + fn eq(&self, other: &Box<#id>) -> bool { + self.as_str() == other.as_str() + } + } + + impl std::fmt::Debug for #id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(self.as_str(), f) + } + } + + impl std::fmt::Display for #id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } + } + + impl serde::Serialize for #id { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } + } + + #partial_eq_string + #box_partial_eq_string + #extra_impls + }) +} + +fn expand_owned_id(id: &Ident, owned: &Ident) -> TokenStream { + let doc_header = format!("Owned variant of {}", id); + let partial_eq_string = expand_partial_eq_string(owned); + + quote! { + #[doc = #doc_header] + /// + /// The wrapper type for this type is variable, by default it'll use [`Box`], + /// but you can change that by setting "`--cfg=ruma_identifiers_storage=...`" using + /// `RUSTFLAGS` or `.cargo/config.toml` (under `[build]` -> `rustflags = ["..."]`) + /// to the following; + /// - `ruma_identifiers_storage="Arc"` to use [`Arc`](std::sync::Arc) as a wrapper type. + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct #owned { + #[cfg(not(any(ruma_identifiers_storage = "Arc")))] + inner: Box<#id>, + #[cfg(ruma_identifiers_storage = "Arc")] + inner: std::sync::Arc<#id>, + } + + impl AsRef<#id> for #owned { + fn as_ref(&self) -> &#id { + &*self.inner + } + } + + impl AsRef for #owned { + fn as_ref(&self) -> &str { + (*self.inner).as_ref() + } + } + + impl std::ops::Deref for #owned { + type Target = #id; + + fn deref(&self) -> &Self::Target { + &self.inner + } + } + + impl std::borrow::Borrow<#id> for #owned { + fn borrow(&self) -> &#id { + self.as_ref() + } + } + + impl From<&'_ #id> for #owned { + fn from(id: &#id) -> #owned { + #owned { inner: id.into() } + } + } + + impl From> for #owned { + fn from(b: Box<#id>) -> #owned { + Self { inner: b.into() } + } + } + + impl From> for #owned { + fn from(a: std::sync::Arc<#id>) -> #owned { + Self { + #[cfg(not(any(ruma_identifiers_storage = "Arc")))] + inner: a.as_ref().into(), + #[cfg(ruma_identifiers_storage = "Arc")] + inner: a, + } + } + } + + impl serde::Serialize for #owned { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } + } + + #partial_eq_string + + impl PartialEq> for #owned { + fn eq(&self, other: &Box<#id>) -> bool { + AsRef::<#id>::as_ref(self) == AsRef::<#id>::as_ref(other) + } + } + + impl PartialEq<#owned> for Box<#id> { + fn eq(&self, other: &#owned) -> bool { + AsRef::<#id>::as_ref(self) == AsRef::<#id>::as_ref(other) + } + } + } +} + +fn expand_checked_impls(id: &Ident, owned: &Ident, validate: Path) -> TokenStream { + let parse_doc_header = format!("Try parsing a `&str` into a `Box<{}>`.", id); + let parse_rc_docs = format!("Try parsing a `&str` into an `Rc<{}>`.", id); + let parse_arc_docs = format!("Try parsing a `&str` into an `Arc<{}>`.", id); + + quote! { + impl #id { + #[doc = #parse_doc_header] + /// + /// The same can also be done using `FromStr`, `TryFrom` or `TryInto`. + /// This function is simply more constrained and thus useful in generic contexts. + pub fn parse( + s: impl AsRef + Into>, + ) -> Result, crate::IdParseError> { + #validate(s.as_ref())?; + Ok(#id::from_owned(s.into())) + } + + #[doc = #parse_rc_docs] + pub fn parse_rc( + s: impl AsRef + Into>, + ) -> Result, crate::IdParseError> { + #validate(s.as_ref())?; + Ok(#id::from_rc(s.into())) + } + + #[doc = #parse_arc_docs] + pub fn parse_arc( + s: impl AsRef + Into>, + ) -> Result, crate::IdParseError> { + #validate(s.as_ref())?; + Ok(#id::from_arc(s.into())) + } + } + + impl<'de> serde::Deserialize<'de> for Box<#id> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + let s = String::deserialize(deserializer)?; + + match #id::parse(s) { + Ok(o) => Ok(o), + Err(e) => Err(D::Error::custom(e)), + } + } + } + + impl<'de> serde::Deserialize<'de> for #owned { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + let s = String::deserialize(deserializer)?; + + match #id::parse(s) { + Ok(o) => Ok(o.into()), + Err(e) => Err(D::Error::custom(e)), + } + } + } + + impl<'a> std::convert::TryFrom<&'a str> for &'a #id { + type Error = crate::IdParseError; + + fn try_from(s: &'a str) -> Result { + #validate(s)?; + Ok(#id::from_borrowed(s)) + } + } + + impl std::str::FromStr for Box<#id> { + type Err = crate::IdParseError; + + fn from_str(s: &str) -> Result { + #id::parse(s) + } + } + + impl std::convert::TryFrom<&str> for Box<#id> { + type Error = crate::IdParseError; + + fn try_from(s: &str) -> Result { + #id::parse(s) + } + } + + impl std::convert::TryFrom for Box<#id> { + type Error = crate::IdParseError; + + fn try_from(s: String) -> Result { + #id::parse(s) + } + } + } +} + +fn expand_unchecked_impls(id: &Ident, owned: &Ident) -> TokenStream { + quote! { + impl<'a> From<&'a str> for &'a #id { + fn from(s: &'a str) -> Self { + #id::from_borrowed(s) + } + } + + impl From<&str> for Box<#id> { + fn from(s: &str) -> Self { + #id::from_owned(s.into()) + } + } + + impl From> for Box<#id> { + fn from(s: Box) -> Self { + #id::from_owned(s) + } + } + + impl From for Box<#id> { + fn from(s: String) -> Self { + #id::from_owned(s.into()) + } + } + + impl From> for Box { + fn from(id: Box<#id>) -> Self { + id.into_owned() + } + } + + impl<'de> serde::Deserialize<'de> for Box<#id> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Box::::deserialize(deserializer).map(#id::from_owned) + } + } + + impl<'de> serde::Deserialize<'de> for #owned { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // FIXME: Deserialize inner, convert that + Box::::deserialize(deserializer).map(#id::from_owned).map(Into::into) + } + } + } +} + +fn expand_partial_eq_string(id: impl ToTokens) -> TokenStream { + fn single_impl(lhs: impl ToTokens, rhs: impl ToTokens) -> TokenStream { + quote! { + impl PartialEq<#rhs> for #lhs { + fn eq(&self, other: &#rhs) -> bool { + AsRef::::as_ref(self) + == AsRef::::as_ref(other) + } + } + } + } + + let id = &id; + + let mut res = TokenStream::new(); + res.extend(single_impl(id, quote! { str })); + res.extend(single_impl(id, quote! { &str })); + res.extend(single_impl(id, quote! { String })); + res.extend(single_impl(quote! { str }, id)); + res.extend(single_impl(quote! { &str }, id)); + res.extend(single_impl(quote! { String }, id)); + res +} + +mod kw { + syn::custom_keyword!(validate); +} + +#[derive(Default)] +struct IdZstMeta { + validate: Option, +} + +impl IdZstMeta { + fn merge(self, other: IdZstMeta) -> syn::Result { + let validate = match (self.validate, other.validate) { + (None, None) => None, + (Some(val), None) | (None, Some(val)) => Some(val), + (Some(a), Some(b)) => { + let mut error = syn::Error::new_spanned(b, "duplicate attribute argument"); + error.combine(syn::Error::new_spanned(a, "note: first one here")); + return Err(error); + } + }; + + Ok(Self { validate }) + } +} + +impl Parse for IdZstMeta { + fn parse(input: ParseStream<'_>) -> syn::Result { + let _: kw::validate = input.parse()?; + let _: Token![=] = input.parse()?; + let validate = Some(input.parse()?); + Ok(Self { validate }) + } +} diff --git a/crates/ruma-macros/src/lib.rs b/crates/ruma-macros/src/lib.rs index 379c61bd..6f8440b5 100644 --- a/crates/ruma-macros/src/lib.rs +++ b/crates/ruma-macros/src/lib.rs @@ -6,6 +6,7 @@ #![warn(missing_docs)] +use identifiers::expand_id_zst; use proc_macro::TokenStream; use proc_macro2 as pm2; use quote::quote; @@ -13,7 +14,7 @@ use ruma_identifiers_validation::{ device_key_id, event_id, key_id, mxc_uri, room_alias_id, room_id, room_version_id, server_name, user_id, }; -use syn::{parse_macro_input, DeriveInput, ItemEnum}; +use syn::{parse_macro_input, DeriveInput, ItemEnum, ItemStruct}; mod api; mod events; @@ -116,6 +117,13 @@ pub fn derive_from_event_to_enum(input: TokenStream) -> TokenStream { expand_from_impls_derived(input).into() } +/// Generate methods and trait impl's for ZST identifier type. +#[proc_macro_derive(IdZst, attributes(ruma_id))] +pub fn derive_id_zst(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemStruct); + expand_id_zst(input).unwrap_or_else(syn::Error::into_compile_error).into() +} + /// Compile-time checked `DeviceKeyId` construction. #[proc_macro] pub fn device_key_id(input: TokenStream) -> TokenStream {