identifiers: Replace most macro_rules! code with proc-macro code

This commit is contained in:
Jonas Platte 2022-04-13 20:13:26 +02:00
parent a2df988c23
commit f658487c50
No known key found for this signature in database
GPG Key ID: BBA95679259D342F
19 changed files with 560 additions and 546 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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")]

View File

@ -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<Self> {

View File

@ -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.

View File

@ -263,5 +263,24 @@ impl<A, K: ?Sized> TryFrom<String> for Box<KeyId<A, K>> {
}
}
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::<str>::as_ref(self)
== AsRef::<str>::as_ref(other)
}
}
}
}
#[rustfmt::skip]
partial_eq_string!(KeyId<A, K> [A, K]);

View File

@ -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);

View File

@ -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 <https://github.com/rust-lang/rust/issues/52607>.
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::<str>::as_ref(self)
== AsRef::<str>::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<str> 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<Box<$id>> for $owned {
fn from(b: Box<$id>) -> $owned {
Self { inner: b.into() }
}
}
impl From<std::sync::Arc<$id>> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_ref().as_str())
}
}
partial_eq_string!($owned);
impl PartialEq<Box<$id>> 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<str>) -> Box<Self> {
unsafe { Box::from_raw(Box::into_raw(s) as _) }
}
pub(super) fn from_rc(s: std::rc::Rc<str>) -> std::rc::Rc<Self> {
unsafe { std::rc::Rc::from_raw(std::rc::Rc::into_raw(s) as _) }
}
pub(super) fn from_arc(s: std::sync::Arc<str>) -> std::sync::Arc<Self> {
unsafe { std::sync::Arc::from_raw(std::sync::Arc::into_raw(s) as _) }
}
pub(super) fn into_owned(self: Box<Self>) -> Box<str> {
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<str> for $id {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> 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::<str>::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::<str>::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<Box<$id>> for $id {
fn eq(&self, other: &Box<$id>) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<Box<$id>> 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 {
<str as std::fmt::Debug>::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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<Box<str>> for Box<$id> {
fn from(s: Box<str>) -> Self {
$id::from_owned(s)
}
}
impl From<String> for Box<$id> {
fn from(s: String) -> Self {
$id::from_owned(s.into())
}
}
impl From<Box<$id>> for Box<str> {
fn from(id: Box<$id>) -> Self {
id.into_owned()
}
}
impl From<Box<$id>> for String {
fn from(id: Box<$id>) -> Self {
id.into_owned().into()
}
}
impl<'de> serde::Deserialize<'de> for Box<$id> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Box::<str>::deserialize(deserializer).map($id::from_owned)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for $owned {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Box::<str>::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<str> + Into<Box<str>>,
) -> Result<Box<Self>, 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<str> + Into<std::rc::Rc<str>>,
) -> Result<std::rc::Rc<Self>, 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<str> + Into<std::sync::Arc<str>>,
) -> Result<std::sync::Arc<Self>, crate::IdParseError> {
$validate_id(s.as_ref())?;
Ok($id::from_arc(s.into()))
}
}
}
opaque_identifier_common_impls!($id, $owned);
impl From<Box<$id>> for String {
fn from(id: Box<$id>) -> Self {
id.into_owned().into()
}
}
impl<'de> serde::Deserialize<'de> for Box<$id> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<Self, Self::Error> {
$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<Self, Self::Err> {
$id::parse(s)
}
}
impl std::convert::TryFrom<&str> for Box<$id> {
type Error = crate::IdParseError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
$id::parse(s)
}
}
impl std::convert::TryFrom<String> for Box<$id> {
type Error = crate::IdParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
$id::parse(s)
}
}
};
}

View File

@ -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<T, E = MxcUriError> = std::result::Result<T, E>;
/// [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> {

View File

@ -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 {

View File

@ -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.

View File

@ -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
);

View File

@ -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 {

View File

@ -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.
///

View File

@ -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
);

View File

@ -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);

View File

@ -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 {

View File

@ -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<Self> {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
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<TokenStream> {
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<IdZstMeta, Token![,]> =
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<str>) -> Box<Self> {
unsafe { Box::from_raw(Box::into_raw(s) as _) }
}
pub(super) fn from_rc(s: std::rc::Rc<str>) -> std::rc::Rc<Self> {
unsafe { std::rc::Rc::from_raw(std::rc::Rc::into_raw(s) as _) }
}
pub(super) fn from_arc(s: std::sync::Arc<str>) -> std::sync::Arc<Self> {
unsafe { std::sync::Arc::from_raw(std::sync::Arc::into_raw(s) as _) }
}
pub(super) fn into_owned(self: Box<Self>) -> Box<str> {
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<str> for #id {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for Box<#id> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<Box<#id>> 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::<str>::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::<str>::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<Box<#id>> for #id {
fn eq(&self, other: &Box<#id>) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<Box<#id>> 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 {
<str as std::fmt::Debug>::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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<str> 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<Box<#id>> for #owned {
fn from(b: Box<#id>) -> #owned {
Self { inner: b.into() }
}
}
impl From<std::sync::Arc<#id>> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#partial_eq_string
impl PartialEq<Box<#id>> 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<str> + Into<Box<str>>,
) -> Result<Box<Self>, crate::IdParseError> {
#validate(s.as_ref())?;
Ok(#id::from_owned(s.into()))
}
#[doc = #parse_rc_docs]
pub fn parse_rc(
s: impl AsRef<str> + Into<std::rc::Rc<str>>,
) -> Result<std::rc::Rc<Self>, crate::IdParseError> {
#validate(s.as_ref())?;
Ok(#id::from_rc(s.into()))
}
#[doc = #parse_arc_docs]
pub fn parse_arc(
s: impl AsRef<str> + Into<std::sync::Arc<str>>,
) -> Result<std::sync::Arc<Self>, crate::IdParseError> {
#validate(s.as_ref())?;
Ok(#id::from_arc(s.into()))
}
}
impl<'de> serde::Deserialize<'de> for Box<#id> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<Self, Self::Error> {
#validate(s)?;
Ok(#id::from_borrowed(s))
}
}
impl std::str::FromStr for Box<#id> {
type Err = crate::IdParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#id::parse(s)
}
}
impl std::convert::TryFrom<&str> for Box<#id> {
type Error = crate::IdParseError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
#id::parse(s)
}
}
impl std::convert::TryFrom<String> for Box<#id> {
type Error = crate::IdParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
#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<Box<str>> for Box<#id> {
fn from(s: Box<str>) -> Self {
#id::from_owned(s)
}
}
impl From<String> for Box<#id> {
fn from(s: String) -> Self {
#id::from_owned(s.into())
}
}
impl From<Box<#id>> for Box<str> {
fn from(id: Box<#id>) -> Self {
id.into_owned()
}
}
impl<'de> serde::Deserialize<'de> for Box<#id> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Box::<str>::deserialize(deserializer).map(#id::from_owned)
}
}
impl<'de> serde::Deserialize<'de> for #owned {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// FIXME: Deserialize inner, convert that
Box::<str>::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::<str>::as_ref(self)
== AsRef::<str>::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<Path>,
}
impl IdZstMeta {
fn merge(self, other: IdZstMeta) -> syn::Result<Self> {
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<Self> {
let _: kw::validate = input.parse()?;
let _: Token![=] = input.parse()?;
let validate = Some(input.parse()?);
Ok(Self { validate })
}
}

View File

@ -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 {