Revert making identifier types generic over the underlying string type

At the same time, this commit makes `ServerName` a newtype around str so
other identifier types can borrow out their server name part as a
`&ServerName`. This technique works for `ServerName` because it keeps no
additional metadata. For the other identifier types to support being
created in borrowed form from a string slice, custom DSTs first have to
be added to Rust.
This commit is contained in:
Jonas Platte 2020-07-14 20:34:29 +02:00
parent 8683901e14
commit a3e5d679a1
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
29 changed files with 402 additions and 518 deletions

View File

@ -35,7 +35,7 @@ ruma_api! {
/// If this does not correspond to a known client device, a new device will be created.
/// The server will auto-generate a device_id if this is not specified.
#[serde(skip_serializing_if = "Option::is_none")]
pub device_id: Option<DeviceId>,
pub device_id: Option<Box<DeviceId>>,
/// A display name to assign to the newly-created device.
///
@ -78,7 +78,7 @@ ruma_api! {
/// ID of the registered device.
///
/// Will be the same as the corresponding parameter in the request, if one was specified.
pub device_id: Option<DeviceId>,
pub device_id: Option<Box<DeviceId>>,
}
error: UiaaResponse

View File

@ -15,7 +15,7 @@ pub mod update_device;
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
pub struct Device {
/// Device ID
pub device_id: DeviceId,
pub device_id: Box<DeviceId>,
/// Public display name of the device.
pub display_name: Option<String>,

View File

@ -18,7 +18,7 @@ ruma_api! {
request: {
/// The device to delete.
#[ruma_api(path)]
pub device_id: DeviceId,
pub device_id: Box<DeviceId>,
/// Additional authentication information for the user-interactive authentication API.
#[serde(skip_serializing_if = "Option::is_none")]

View File

@ -17,7 +17,7 @@ ruma_api! {
request: {
/// List of devices to delete.
pub devices: Vec<DeviceId>,
pub devices: Vec<Box<DeviceId>>,
/// Additional authentication information for the user-interactive authentication API.
#[serde(skip_serializing_if = "Option::is_none")]

View File

@ -17,7 +17,7 @@ ruma_api! {
request: {
/// The device to retrieve.
#[ruma_api(path)]
pub device_id: DeviceId,
pub device_id: Box<DeviceId>,
}
response: {

View File

@ -16,7 +16,7 @@ ruma_api! {
request: {
/// The device to update.
#[ruma_api(path)]
pub device_id: DeviceId,
pub device_id: Box<DeviceId>,
/// The new display name for this device. If this is `None`, the display name won't be
/// changed.

View File

@ -60,7 +60,7 @@ impl TryFrom<&'_ str> for KeyAlgorithm {
/// A key algorithm and a device id, combined with a ':'
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct AlgorithmAndDeviceId(pub KeyAlgorithm, pub DeviceId);
pub struct AlgorithmAndDeviceId(pub KeyAlgorithm, pub Box<DeviceId>);
impl Display for AlgorithmAndDeviceId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
@ -102,7 +102,7 @@ impl<'de> Deserialize<'de> for AlgorithmAndDeviceId {
let algorithm_result = KeyAlgorithm::try_from(parts[0]);
match algorithm_result {
Ok(algorithm) => Ok(AlgorithmAndDeviceId(algorithm, parts[1].to_string())),
Ok(algorithm) => Ok(AlgorithmAndDeviceId(algorithm, parts[1].into())),
Err(_) => {
Err(de::Error::invalid_value(Unexpected::Str(parts[0]), &"valid key algorithm"))
}
@ -117,7 +117,7 @@ pub struct DeviceKeys {
pub user_id: UserId,
/// The ID of the device these keys belong to. Must match the device ID used when logging in.
pub device_id: DeviceId,
pub device_id: Box<DeviceId>,
/// The encryption algorithms supported by this device.
pub algorithms: Vec<Algorithm>,

View File

@ -31,7 +31,7 @@ ruma_api! {
pub timeout: Option<Duration>,
/// The keys to be claimed.
pub one_time_keys: BTreeMap<UserId, BTreeMap<DeviceId, KeyAlgorithm>>,
pub one_time_keys: BTreeMap<UserId, BTreeMap<Box<DeviceId>, KeyAlgorithm>>,
}
response: {
@ -40,8 +40,11 @@ ruma_api! {
pub failures: BTreeMap<String, JsonValue>,
/// One-time keys for the queried devices.
pub one_time_keys: BTreeMap<UserId, BTreeMap<DeviceId, BTreeMap<AlgorithmAndDeviceId, OneTimeKey>>>,
pub one_time_keys: BTreeMap<UserId, OneTimeKeys>,
}
error: crate::Error
}
/// The one-time keys for a given device.
pub type OneTimeKeys = BTreeMap<Box<DeviceId>, BTreeMap<AlgorithmAndDeviceId, OneTimeKey>>;

View File

@ -30,7 +30,7 @@ ruma_api! {
/// The keys to be downloaded. An empty list indicates all devices for
/// the corresponding user.
pub device_keys: BTreeMap<UserId, Vec<DeviceId>>,
pub device_keys: BTreeMap<UserId, Vec<Box<DeviceId>>>,
/// If the client is fetching keys as a result of a device update
/// received in a sync request, this should be the 'since' token of that
@ -48,7 +48,7 @@ ruma_api! {
pub failures: BTreeMap<String, JsonValue>,
/// Information on the queried devices.
pub device_keys: BTreeMap<UserId, BTreeMap<DeviceId, DeviceKeys>>,
pub device_keys: BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeys>>,
}
error: crate::Error

View File

@ -27,7 +27,7 @@ ruma_api! {
/// ID of the client device
#[serde(skip_serializing_if = "Option::is_none")]
pub device_id: Option<DeviceId>,
pub device_id: Option<Box<DeviceId>>,
/// A display name to assign to the newly-created device. Ignored if device_id corresponds
/// to a known device.

View File

@ -17,7 +17,7 @@ pub mod send_event_to_device;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DeviceIdOrAllDevices {
/// Represents a device Id for one of a user's devices.
DeviceId(DeviceId),
DeviceId(Box<DeviceId>),
/// Represents all devices for a user.
AllDevices,
@ -40,7 +40,7 @@ impl TryFrom<&str> for DeviceIdOrAllDevices {
} else if "*" == device_id_or_all_devices {
Ok(DeviceIdOrAllDevices::AllDevices)
} else {
Ok(DeviceIdOrAllDevices::DeviceId(device_id_or_all_devices.to_string()))
Ok(DeviceIdOrAllDevices::DeviceId(device_id_or_all_devices.into()))
}
}
}

View File

@ -38,7 +38,7 @@ impl DerefMut for DirectEventContent {
mod tests {
use std::{collections::BTreeMap, convert::TryFrom};
use ruma_identifiers::{RoomId, ServerNameRef, UserId};
use ruma_identifiers::{RoomId, ServerName, UserId};
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::{DirectEvent, DirectEventContent};
@ -47,7 +47,7 @@ mod tests {
#[test]
fn serialization() {
let mut content = DirectEventContent(BTreeMap::new());
let server_name = ServerNameRef::try_from("ruma.io").unwrap();
let server_name = <&ServerName>::try_from("ruma.io").unwrap();
let alice = UserId::new(server_name);
let room = vec![RoomId::new(server_name)];
@ -66,7 +66,7 @@ mod tests {
#[test]
fn deserialization() {
let server_name = ServerNameRef::try_from("ruma.io").unwrap();
let server_name = <&ServerName>::try_from("ruma.io").unwrap();
let alice = UserId::new(server_name);
let rooms = vec![RoomId::new(server_name), RoomId::new(server_name)];

View File

@ -19,7 +19,7 @@ pub type RequestEvent = BasicEvent<RequestEventContent>;
#[ruma_event(type = "m.key.verification.request")]
pub struct RequestEventContent {
/// The device ID which is initiating the request.
pub from_device: DeviceId,
pub from_device: Box<DeviceId>,
/// An opaque identifier for the verification request.
///

View File

@ -29,7 +29,7 @@ pub enum StartEventContent {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MSasV1Content {
/// The device ID which is initiating the process.
pub(crate) from_device: DeviceId,
pub(crate) from_device: Box<DeviceId>,
/// An opaque identifier for the verification process.
///
@ -63,7 +63,7 @@ pub struct MSasV1Content {
#[derive(Clone, Debug, Deserialize)]
pub struct MSasV1ContentOptions {
/// The device ID which is initiating the process.
pub from_device: DeviceId,
pub from_device: Box<DeviceId>,
/// An opaque identifier for the verification process.
///
@ -270,7 +270,7 @@ mod tests {
key_agreement_protocols,
message_authentication_codes,
short_authentication_string,
}) if from_device == "123"
}) if from_device.as_ref() == "123"
&& transaction_id == "456"
&& hashes == vec![HashAlgorithm::Sha256]
&& key_agreement_protocols == vec![KeyAgreementProtocol::Curve25519]
@ -305,7 +305,7 @@ mod tests {
message_authentication_codes,
short_authentication_string,
})
} if from_device == "123"
} if from_device.as_ref() == "123"
&& transaction_id == "456"
&& hashes == vec![HashAlgorithm::Sha256]
&& key_agreement_protocols == vec![KeyAgreementProtocol::Curve25519]

View File

@ -60,7 +60,7 @@ pub struct MegolmV1AesSha2Content {
pub sender_key: String,
/// The ID of the sending device.
pub device_id: DeviceId,
pub device_id: Box<DeviceId>,
/// The ID of the session used to encrypt the message.
pub session_id: String,
@ -117,7 +117,7 @@ mod tests {
session_id,
}) if ciphertext == "ciphertext"
&& sender_key == "sender_key"
&& device_id == "device_id"
&& device_id.as_ref() == "device_id"
&& session_id == "session_id"
);
}

View File

@ -24,7 +24,7 @@ mod tests {
time::{Duration, UNIX_EPOCH},
};
use ruma_identifiers::{EventId, RoomId, ServerNameRef, UserId};
use ruma_identifiers::{EventId, RoomId, ServerName, UserId};
use serde_json::to_string;
use super::PinnedEventsEventContent;
@ -33,7 +33,7 @@ mod tests {
#[test]
fn serialization_deserialization() {
let mut content: PinnedEventsEventContent = PinnedEventsEventContent { pinned: Vec::new() };
let server_name = ServerNameRef::try_from("example.com").unwrap();
let server_name = <&ServerName>::try_from("example.com").unwrap();
content.pinned.push(EventId::new(server_name));
content.pinned.push(EventId::new(server_name));

View File

@ -26,7 +26,7 @@ pub struct RoomKeyRequestEventContent {
pub body: Option<RequestedKeyInfo>,
/// ID of the device requesting the key.
pub requesting_device_id: DeviceId,
pub requesting_device_id: Box<DeviceId>,
/// A random string uniquely identifying the request for a key.
///

View File

@ -7,12 +7,12 @@ use crate::generate_localpart;
///
/// Device identifiers in Matrix are completely opaque character sequences. This type alias is
/// provided simply for its semantic value.
pub type DeviceId = String;
pub type DeviceId = str;
/// Generates a random `DeviceId`, suitable for assignment to a new device.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn generate() -> DeviceId {
pub fn generate() -> Box<DeviceId> {
generate_localpart(8)
}

View File

@ -1,24 +1,16 @@
//! Identifiers for device keys for end-to-end encryption.
use crate::{error::Error, key_algorithms::DeviceKeyAlgorithm, DeviceIdRef};
use crate::{error::Error, key_algorithms::DeviceKeyAlgorithm, DeviceId};
use std::{num::NonZeroU8, str::FromStr};
/// A key algorithm and a device id, combined with a ':'
#[derive(Clone, Debug)]
pub struct DeviceKeyId<T> {
full_id: T,
pub struct DeviceKeyId {
full_id: Box<str>,
colon_idx: NonZeroU8,
}
impl<T> DeviceKeyId<T>
where
T: AsRef<str>,
{
/// Creates a reference to this `DeviceKeyId`.
pub fn as_ref(&self) -> DeviceKeyId<&str> {
DeviceKeyId { full_id: self.full_id.as_ref(), colon_idx: self.colon_idx }
}
impl DeviceKeyId {
/// Returns key algorithm of the device key ID.
pub fn algorithm(&self) -> DeviceKeyAlgorithm {
DeviceKeyAlgorithm::from_str(&self.full_id.as_ref()[..self.colon_idx.get() as usize])
@ -26,14 +18,14 @@ where
}
/// Returns device ID of the device key ID.
pub fn device_id(&self) -> DeviceIdRef<'_> {
pub fn device_id(&self) -> &DeviceId {
&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]
}
}
fn try_from<S, T>(key_id: S) -> Result<DeviceKeyId<T>, Error>
fn try_from<S>(key_id: S) -> Result<DeviceKeyId, Error>
where
S: AsRef<str> + Into<T>,
S: AsRef<str> + Into<Box<str>>,
{
let key_str = key_id.as_ref();
let colon_idx =
@ -56,12 +48,12 @@ mod test {
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::DeviceKeyId;
use crate::{device_id::DeviceId, error::Error, key_algorithms::DeviceKeyAlgorithm};
use crate::{error::Error, key_algorithms::DeviceKeyAlgorithm};
#[test]
fn convert_device_key_id() {
assert_eq!(
DeviceKeyId::<&str>::try_from("ed25519:JLAFKJWSCS")
DeviceKeyId::try_from("ed25519:JLAFKJWSCS")
.expect("Failed to create device key ID.")
.as_ref(),
"ed25519:JLAFKJWSCS"
@ -71,7 +63,7 @@ mod test {
#[cfg(feature = "serde")]
#[test]
fn serialize_device_key_id() {
let device_key_id = DeviceKeyId::<&str>::try_from("ed25519:JLAFKJWSCS").unwrap();
let device_key_id = DeviceKeyId::try_from("ed25519:JLAFKJWSCS").unwrap();
let serialized = to_json_value(device_key_id).unwrap();
let expected = json!("ed25519:JLAFKJWSCS");
@ -81,7 +73,7 @@ mod test {
#[cfg(feature = "serde")]
#[test]
fn deserialize_device_key_id() {
let deserialized: DeviceKeyId<_> = from_json_value(json!("ed25519:JLAFKJWSCS")).unwrap();
let deserialized: DeviceKeyId = from_json_value(json!("ed25519:JLAFKJWSCS")).unwrap();
let expected = DeviceKeyId::try_from("ed25519:JLAFKJWSCS").unwrap();
assert_eq!(deserialized, expected);
@ -89,16 +81,13 @@ mod test {
#[test]
fn missing_key_algorithm() {
assert_eq!(
DeviceKeyId::<&str>::try_from(":JLAFKJWSCS").unwrap_err(),
Error::UnknownKeyAlgorithm
);
assert_eq!(DeviceKeyId::try_from(":JLAFKJWSCS").unwrap_err(), Error::UnknownKeyAlgorithm);
}
#[test]
fn missing_delimiter() {
assert_eq!(
DeviceKeyId::<&str>::try_from("ed25519|JLAFKJWSCS").unwrap_err(),
DeviceKeyId::try_from("ed25519|JLAFKJWSCS").unwrap_err(),
Error::MissingDeviceKeyDelimiter,
);
}
@ -106,25 +95,25 @@ mod test {
#[test]
fn unknown_key_algorithm() {
assert_eq!(
DeviceKeyId::<&str>::try_from("signed_curve25510:JLAFKJWSCS").unwrap_err(),
DeviceKeyId::try_from("signed_curve25510:JLAFKJWSCS").unwrap_err(),
Error::UnknownKeyAlgorithm,
);
}
#[test]
fn empty_device_id_ok() {
assert!(DeviceKeyId::<&str>::try_from("ed25519:").is_ok());
assert!(DeviceKeyId::try_from("ed25519:").is_ok());
}
#[test]
fn valid_key_algorithm() {
let device_key_id = DeviceKeyId::<&str>::try_from("ed25519:JLAFKJWSCS").unwrap();
let device_key_id = DeviceKeyId::try_from("ed25519:JLAFKJWSCS").unwrap();
assert_eq!(device_key_id.algorithm(), DeviceKeyAlgorithm::Ed25519);
}
#[test]
fn valid_device_id() {
let device_key_id = DeviceKeyId::<&str>::try_from("ed25519:JLAFKJWSCS").unwrap();
assert_eq!(device_key_id.device_id(), DeviceId::from("JLAFKJWSCS"));
let device_key_id = DeviceKeyId::try_from("ed25519:JLAFKJWSCS").unwrap();
assert_eq!(device_key_id.device_id(), "JLAFKJWSCS");
}
}

View File

@ -2,16 +2,13 @@
use std::{convert::TryFrom, num::NonZeroU8};
use crate::{error::Error, parse_id, validate_id, ServerNameRef};
use crate::{error::Error, parse_id, validate_id, ServerName};
/// A Matrix event ID.
///
/// An `EventId` is generated randomly or converted from a string slice, and can be converted back
/// into a string as needed.
///
/// It is discouraged to use this type directly instead use one of the aliases (`EventId` and
/// `EventIdRef`) in the crate root.
///
/// # Room versions
///
/// Matrix specifies multiple [room versions](https://matrix.org/docs/spec/#room-versions) and the
@ -40,16 +37,13 @@ use crate::{error::Error, parse_id, validate_id, ServerNameRef};
/// "$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg"
/// );
/// ```
#[derive(Clone, Copy, Debug)]
pub struct EventId<T> {
full_id: T,
#[derive(Clone, Debug)]
pub struct EventId {
full_id: Box<str>,
colon_idx: Option<NonZeroU8>,
}
impl<T> EventId<T>
where
String: Into<T>,
{
impl EventId {
/// Attempts to generate an `EventId` for the given origin server with a localpart consisting
/// of 18 random ASCII characters. This should only be used for events in the original format
/// as used by Matrix room versions 1 and 2.
@ -58,7 +52,7 @@ where
/// parsed as a valid host.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn new(server_name: ServerNameRef<'_>) -> Self {
pub fn new(server_name: &ServerName) -> Self {
use crate::generate_localpart;
let full_id = format!("${}:{}", generate_localpart(18), server_name).into();
@ -67,33 +61,25 @@ where
}
}
impl<T> EventId<T>
where
T: AsRef<str>,
{
/// Creates a reference to this `EventId`.
pub fn as_ref(&self) -> EventId<&str> {
EventId { full_id: self.full_id.as_ref(), colon_idx: self.colon_idx }
}
impl EventId {
/// Returns the event's unique ID. For the original event format as used by Matrix room
/// versions 1 and 2, this is the "localpart" that precedes the homeserver. For later formats,
/// this is the entire ID without the leading $ sigil.
pub fn localpart(&self) -> &str {
let idx = match self.colon_idx {
Some(idx) => idx.get() as usize,
None => self.full_id.as_ref().len(),
None => self.full_id.len(),
};
&self.full_id.as_ref()[1..idx]
&self.full_id[1..idx]
}
/// Returns the server name of the event ID.
///
/// Only applicable to events in the original format as used by Matrix room versions 1 and 2.
pub fn server_name(&self) -> Option<ServerNameRef<'_>> {
pub fn server_name(&self) -> Option<&ServerName> {
self.colon_idx.map(|idx| {
ServerNameRef::try_from(&self.full_id.as_ref()[idx.get() as usize + 1..]).unwrap()
<&ServerName>::try_from(&self.full_id.as_ref()[idx.get() as usize + 1..]).unwrap()
})
}
}
@ -102,9 +88,9 @@ where
///
/// If using the original event format as used by Matrix room versions 1 and 2, the string must
/// include the leading $ sigil, the localpart, a literal colon, and a valid homeserver hostname.
fn try_from<S, T>(event_id: S) -> Result<EventId<T>, Error>
fn try_from<S>(event_id: S) -> Result<EventId, Error>
where
S: AsRef<str> + Into<T>,
S: AsRef<str> + Into<Box<str>>,
{
if event_id.as_ref().contains(':') {
let colon_idx = parse_id(event_id.as_ref(), &['$'])?;
@ -126,9 +112,8 @@ mod tests {
#[cfg(feature = "serde")]
use serde_json::{from_str, to_string};
use crate::{error::Error, ServerNameRef};
type EventId = super::EventId<Box<str>>;
use super::EventId;
use crate::{error::Error, ServerName};
#[test]
fn valid_original_event_id() {
@ -164,7 +149,7 @@ mod tests {
#[test]
fn generate_random_valid_event_id() {
let server_name =
ServerNameRef::try_from("example.com").expect("Failed to parse ServerName");
<&ServerName>::try_from("example.com").expect("Failed to parse ServerName");
let event_id = EventId::new(server_name);
let id_str = event_id.as_str();

View File

@ -13,152 +13,44 @@ use std::{convert::TryFrom, num::NonZeroU8};
use serde::de::{self, Deserialize as _, Deserializer, Unexpected};
#[doc(inline)]
pub use crate::error::Error;
pub use crate::{
device_id::DeviceId,
device_key_id::DeviceKeyId,
error::Error,
event_id::EventId,
key_algorithms::{DeviceKeyAlgorithm, ServerKeyAlgorithm},
room_alias_id::RoomAliasId,
room_id::RoomId,
room_id_or_room_alias_id::RoomIdOrAliasId,
room_version_id::RoomVersionId,
server_key_id::ServerKeyId,
server_name::ServerName,
user_id::UserId,
};
#[macro_use]
mod macros;
mod error;
pub mod device_id;
pub mod device_key_id;
pub mod event_id;
pub mod key_algorithms;
pub mod room_alias_id;
pub mod room_id;
pub mod room_id_or_room_alias_id;
pub mod room_version_id;
pub mod server_key_id;
#[allow(deprecated)]
pub mod server_name;
pub mod user_id;
/// Allowed algorithms for homeserver signing keys.
pub type DeviceKeyAlgorithm = key_algorithms::DeviceKeyAlgorithm;
/// An owned device key identifier containing a key algorithm and device ID.
///
/// Can be created via `TryFrom<String>` and `TryFrom<&str>`; implements `Serialize`
/// and `Deserialize` if the `serde` feature is enabled.
pub type DeviceKeyId = device_key_id::DeviceKeyId<Box<str>>;
/// A reference to a device key identifier containing a key algorithm and device ID.
///
/// Can be created via `TryFrom<&str>`; implements `Serialize` and `Deserialize`
/// if the `serde` feature is enabled.
pub type DeviceKeyIdRef<'a> = device_key_id::DeviceKeyId<&'a str>;
/// An owned device ID.
///
/// While this is currently just a `String`, that will likely change in the future.
pub use device_id::DeviceId;
/// A reference to a device ID.
///
/// While this is currently just a string slice, that will likely change in the future.
pub type DeviceIdRef<'a> = &'a str;
/// An owned event ID.
///
/// Can be created via `new` (if the `rand` feature is enabled) and `TryFrom<String>` +
/// `TryFrom<&str>`, implements `Serialize` and `Deserialize` if the `serde` feature is enabled.
pub type EventId = event_id::EventId<Box<str>>;
/// A reference to an event ID.
///
/// Can be created via `TryFrom<&str>`, implements `Serialize` if the `serde` feature is enabled.
pub type EventIdRef<'a> = event_id::EventId<&'a str>;
/// An owned room alias ID.
///
/// Can be created via `TryFrom<String>` and `TryFrom<&str>`, implements `Serialize` and
/// `Deserialize` if the `serde` feature is enabled.
pub type RoomAliasId = room_alias_id::RoomAliasId<Box<str>>;
/// A reference to a room alias ID.
///
/// Can be created via `TryFrom<&str>`, implements `Serialize` if the `serde` feature is enabled.
pub type RoomAliasIdRef<'a> = room_alias_id::RoomAliasId<&'a str>;
/// An owned room ID.
///
/// Can be created via `new` (if the `rand` feature is enabled) and `TryFrom<String>` +
/// `TryFrom<&str>`, implements `Serialize` and `Deserialize` if the `serde` feature is enabled.
pub type RoomId = room_id::RoomId<Box<str>>;
/// A reference to a room ID.
///
/// Can be created via `TryFrom<&str>`, implements `Serialize` if the `serde` feature is enabled.
pub type RoomIdRef<'a> = room_id::RoomId<&'a str>;
/// An owned room alias ID or room ID.
///
/// Can be created via `TryFrom<String>`, `TryFrom<&str>`, `From<RoomId>` and `From<RoomAliasId>`;
/// implements `Serialize` and `Deserialize` if the `serde` feature is enabled.
pub type RoomIdOrAliasId = room_id_or_room_alias_id::RoomIdOrAliasId<Box<str>>;
/// A reference to a room alias ID or room ID.
///
/// Can be created via `TryFrom<&str>`, `From<RoomIdRef>` and `From<RoomAliasIdRef>`; implements
/// `Serialize` if the `serde` feature is enabled.
pub type RoomIdOrAliasIdRef<'a> = room_id_or_room_alias_id::RoomIdOrAliasId<&'a str>;
/// An owned room version ID.
///
/// Can be created using the `version_N` constructor functions, `TryFrom<String>` and
/// `TryFrom<&str>`; implements `Serialize` and `Deserialize` if the `serde` feature is enabled.
pub type RoomVersionId = room_version_id::RoomVersionId<Box<str>>;
/// A reference to a room version ID.
///
/// Can be created using the `version_N` constructor functions and via `TryFrom<&str>`, implements
/// `Serialize` if the `serde` feature is enabled.
pub type RoomVersionIdRef<'a> = room_version_id::RoomVersionId<&'a str>;
/// Allowed algorithms for homeserver signing keys.
pub type ServerKeyAlgorithm = key_algorithms::ServerKeyAlgorithm;
/// An owned homeserver signing key identifier containing a key algorithm and version.
///
/// Can be created via `TryFrom<String>` and `TryFrom<&str>`; implements `Serialize`
/// and `Deserialize` if the `serde` feature is enabled.
pub type ServerKeyId = server_key_id::ServerKeyId<Box<str>>;
/// A reference to a homeserver signing key identifier containing a key
/// algorithm and version.
///
/// Can be created via `TryFrom<&str>`; implements `Serialize`
/// and `Deserialize` if the `serde` feature is enabled.
pub type ServerKeyIdRef<'a> = server_key_id::ServerKeyId<&'a str>;
/// An owned homeserver IP address or hostname.
///
/// Can be created via `TryFrom<String>` and `TryFrom<&str>`; implements `Serialize`
/// and `Deserialize` if the `serde` feature is enabled.
pub type ServerName = server_name::ServerName<Box<str>>;
/// A reference to a homeserver IP address or hostname.
///
/// Can be created via `TryFrom<&str>`; implements `Serialize`
/// and `Deserialize` if the `serde` feature is enabled.
pub type ServerNameRef<'a> = server_name::ServerName<&'a str>;
/// An owned user ID.
///
/// Can be created via `new` (if the `rand` feature is enabled) and `TryFrom<String>` +
/// `TryFrom<&str>`, implements `Serialize` and `Deserialize` if the `serde` feature is enabled.
pub type UserId = user_id::UserId<Box<str>>;
/// A reference to a user ID.
///
/// Can be created via `TryFrom<&str>`, implements `Serialize` if the `serde` feature is enabled.
pub type UserIdRef<'a> = user_id::UserId<&'a str>;
mod device_key_id;
mod error;
mod event_id;
mod key_algorithms;
mod room_alias_id;
mod room_id;
mod room_id_or_room_alias_id;
mod room_version_id;
mod server_key_id;
mod server_name;
/// Check whether a given string is a valid server name according to [the specification][].
///
/// [the specification]: https://matrix.org/docs/spec/appendices#server-name
#[deprecated = "Use the [`ServerName`](server_name/struct.ServerName.html) type instead."]
pub fn is_valid_server_name(name: &str) -> bool {
ServerNameRef::try_from(name).is_ok()
<&ServerName>::try_from(name).is_ok()
}
/// All identifiers must be 255 bytes or less.
@ -171,9 +63,13 @@ const MIN_CHARS: usize = 4;
/// Generates a random identifier localpart.
#[cfg(feature = "rand")]
fn generate_localpart(length: usize) -> String {
fn generate_localpart(length: usize) -> Box<str> {
use rand::Rng as _;
rand::thread_rng().sample_iter(&rand::distributions::Alphanumeric).take(length).collect()
rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(length)
.collect::<String>()
.into_boxed_str()
}
/// Checks if an identifier is valid.
@ -203,7 +99,7 @@ fn parse_id(id: &str, valid_sigils: &[char]) -> Result<NonZeroU8, Error> {
return Err(Error::InvalidLocalPart);
}
server_name::ServerName::<&str>::try_from(&id[colon_idx + 1..])?;
server_name::validate(&id[colon_idx + 1..])?;
Ok(NonZeroU8::new(colon_idx as u8).unwrap())
}

View File

@ -6,8 +6,8 @@ macro_rules! doc_concat {
}
macro_rules! common_impls {
($id:ident, $try_from:ident, $desc:literal) => {
impl<T: ::std::convert::AsRef<str>> $id<T> {
($id:ty, $try_from:ident, $desc:literal) => {
impl $id {
doc_concat! {
#[doc = concat!("Creates a string slice from this `", stringify!($id), "`")]
pub fn as_str(&self) -> &str {
@ -16,27 +16,19 @@ macro_rules! common_impls {
}
}
impl<'a> ::std::convert::From<&'a $id<Box<str>>> for $id<&'a str> {
fn from(id: &'a $id<Box<str>>) -> Self {
id.as_ref()
impl ::std::convert::AsRef<str> for $id {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl ::std::convert::From<$id<Box<str>>> for ::std::string::String {
fn from(id: $id<Box<str>>) -> Self {
impl ::std::convert::From<$id> for ::std::string::String {
fn from(id: $id) -> Self {
id.full_id.into()
}
}
impl<'a> ::std::convert::TryFrom<&'a str> for $id<&'a str> {
type Error = crate::error::Error;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
$try_from(s)
}
}
impl ::std::convert::TryFrom<&str> for $id<Box<str>> {
impl ::std::convert::TryFrom<&str> for $id {
type Error = crate::error::Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
@ -44,7 +36,7 @@ macro_rules! common_impls {
}
}
impl ::std::convert::TryFrom<String> for $id<Box<str>> {
impl ::std::convert::TryFrom<String> for $id {
type Error = crate::error::Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
@ -52,56 +44,50 @@ macro_rules! common_impls {
}
}
impl<T: ::std::convert::AsRef<str>> ::std::convert::AsRef<str> for $id<T> {
fn as_ref(&self) -> &str {
self.full_id.as_ref()
}
}
impl<T: ::std::fmt::Display> ::std::fmt::Display for $id<T> {
impl ::std::fmt::Display for $id {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}", self.full_id)
write!(f, "{}", self.as_str())
}
}
impl<T: ::std::cmp::PartialEq> ::std::cmp::PartialEq for $id<T> {
impl ::std::cmp::PartialEq for $id {
fn eq(&self, other: &Self) -> bool {
self.full_id == other.full_id
self.as_str() == other.as_str()
}
}
impl<T: ::std::cmp::Eq> ::std::cmp::Eq for $id<T> {}
impl ::std::cmp::Eq for $id {}
impl<T: ::std::cmp::PartialOrd> ::std::cmp::PartialOrd for $id<T> {
impl ::std::cmp::PartialOrd for $id {
fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
::std::cmp::PartialOrd::partial_cmp(&self.full_id, &other.full_id)
::std::cmp::PartialOrd::partial_cmp(self.as_str(), other.as_str())
}
}
impl<T: ::std::cmp::Ord> ::std::cmp::Ord for $id<T> {
impl ::std::cmp::Ord for $id {
fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
::std::cmp::Ord::cmp(&self.full_id, &other.full_id)
::std::cmp::Ord::cmp(self.as_str(), other.as_str())
}
}
impl<T: ::std::hash::Hash> ::std::hash::Hash for $id<T> {
impl ::std::hash::Hash for $id {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.full_id.hash(state);
self.as_str().hash(state);
}
}
#[cfg(feature = "serde")]
impl<T: AsRef<str>> ::serde::Serialize for $id<T> {
impl ::serde::Serialize for $id {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
serializer.serialize_str(self.full_id.as_ref())
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
impl<'de> ::serde::Deserialize<'de> for $id<Box<str>> {
impl<'de> ::serde::Deserialize<'de> for $id {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
@ -110,27 +96,27 @@ macro_rules! common_impls {
}
}
impl<T: AsRef<str>> ::std::cmp::PartialEq<&str> for $id<T> {
impl ::std::cmp::PartialEq<&str> for $id {
fn eq(&self, other: &&str) -> bool {
self.full_id.as_ref() == *other
self.as_str() == *other
}
}
impl<T: AsRef<str>> ::std::cmp::PartialEq<$id<T>> for &str {
fn eq(&self, other: &$id<T>) -> bool {
*self == other.full_id.as_ref()
impl ::std::cmp::PartialEq<$id> for &str {
fn eq(&self, other: &$id) -> bool {
*self == other.as_str()
}
}
impl<T: AsRef<str>> ::std::cmp::PartialEq<::std::string::String> for $id<T> {
impl ::std::cmp::PartialEq<::std::string::String> for $id {
fn eq(&self, other: &::std::string::String) -> bool {
self.full_id.as_ref() == &other[..]
self.as_str() == other.as_str()
}
}
impl<T: AsRef<str>> ::std::cmp::PartialEq<$id<T>> for ::std::string::String {
fn eq(&self, other: &$id<T>) -> bool {
&self[..] == other.full_id.as_ref()
impl ::std::cmp::PartialEq<$id> for ::std::string::String {
fn eq(&self, other: &$id) -> bool {
self.as_str() == other.as_str()
}
}
};

View File

@ -6,9 +6,6 @@ use crate::{error::Error, parse_id, server_name::ServerName};
/// A Matrix room alias ID.
///
/// It is discouraged to use this type directly instead use one of the aliases (`RoomAliasId` and
/// `RoomAliasIdRef`) in the crate root.
///
/// A `RoomAliasId` is converted from a string slice, and can be converted back into a string as
/// needed.
///
@ -20,38 +17,30 @@ use crate::{error::Error, parse_id, server_name::ServerName};
/// "#ruma:example.com"
/// );
/// ```
#[derive(Clone, Copy, Debug)]
pub struct RoomAliasId<T> {
pub(crate) full_id: T,
#[derive(Clone, Debug)]
pub struct RoomAliasId {
pub(crate) full_id: Box<str>,
pub(crate) colon_idx: NonZeroU8,
}
impl<T> RoomAliasId<T>
where
T: AsRef<str>,
{
/// Creates a reference to this `RoomAliasId`.
pub fn as_ref(&self) -> RoomAliasId<&str> {
RoomAliasId { full_id: self.full_id.as_ref(), colon_idx: self.colon_idx }
}
impl RoomAliasId {
/// Returns the room's alias.
pub fn alias(&self) -> &str {
&self.full_id.as_ref()[1..self.colon_idx.get() as usize]
&self.full_id[1..self.colon_idx.get() as usize]
}
/// Returns the server name of the room alias ID.
pub fn server_name(&self) -> ServerName<&str> {
ServerName::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]).unwrap()
pub fn server_name(&self) -> &ServerName {
<&ServerName>::try_from(&self.full_id[self.colon_idx.get() as usize + 1..]).unwrap()
}
}
/// Attempts to create a new Matrix room alias ID from a string representation.
///
/// The string must include the leading # sigil, the alias, a literal colon, and a server name.
fn try_from<S, T>(room_id: S) -> Result<RoomAliasId<T>, Error>
fn try_from<S>(room_id: S) -> Result<RoomAliasId, Error>
where
S: AsRef<str> + Into<T>,
S: AsRef<str> + Into<Box<str>>,
{
let colon_idx = parse_id(room_id.as_ref(), &['#'])?;
@ -67,10 +56,9 @@ mod tests {
#[cfg(feature = "serde")]
use serde_json::{from_str, to_string};
use super::RoomAliasId;
use crate::error::Error;
type RoomAliasId = super::RoomAliasId<Box<str>>;
#[test]
fn valid_room_alias_id() {
assert_eq!(

View File

@ -2,16 +2,13 @@
use std::{convert::TryFrom, num::NonZeroU8};
use crate::{error::Error, parse_id, ServerNameRef};
use crate::{error::Error, parse_id, ServerName};
/// A Matrix room ID.
///
/// A `RoomId` is generated randomly or converted from a string slice, and can be converted back
/// into a string as needed.
///
/// It is discouraged to use this type directly instead use one of the aliases (`RoomId` and
/// `RoomIdRef`) in the crate root.
///
/// ```
/// # use std::convert::TryFrom;
/// # use ruma_identifiers::RoomId;
@ -20,23 +17,20 @@ use crate::{error::Error, parse_id, ServerNameRef};
/// "!n8f893n9:example.com"
/// );
/// ```
#[derive(Clone, Copy, Debug)]
pub struct RoomId<T> {
pub(crate) full_id: T,
#[derive(Clone, Debug)]
pub struct RoomId {
pub(crate) full_id: Box<str>,
pub(crate) colon_idx: NonZeroU8,
}
impl<T> RoomId<T>
where
String: Into<T>,
{
impl RoomId {
/// Attempts to generate a `RoomId` for the given origin server with a localpart consisting of
/// 18 random ASCII characters.
///
/// Fails if the given homeserver cannot be parsed as a valid host.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn new(server_name: ServerNameRef<'_>) -> Self {
pub fn new(server_name: &ServerName) -> Self {
use crate::generate_localpart;
let full_id = format!("!{}:{}", generate_localpart(18), server_name).into();
@ -45,33 +39,24 @@ where
}
}
impl<T> RoomId<T>
where
T: AsRef<str>,
{
/// Creates a reference to this `RoomId`.
pub fn as_ref(&self) -> RoomId<&str> {
RoomId { full_id: self.full_id.as_ref(), colon_idx: self.colon_idx }
}
impl RoomId {
/// Returns the rooms's unique ID.
pub fn localpart(&self) -> &str {
&self.full_id.as_ref()[1..self.colon_idx.get() as usize]
&self.full_id[1..self.colon_idx.get() as usize]
}
/// Returns the server name of the room ID.
pub fn server_name(&self) -> ServerNameRef<'_> {
ServerNameRef::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..])
.unwrap()
pub fn server_name(&self) -> &ServerName {
<&ServerName>::try_from(&self.full_id[self.colon_idx.get() as usize + 1..]).unwrap()
}
}
/// Attempts to create a new Matrix room ID from a string representation.
///
/// The string must include the leading ! sigil, the localpart, a literal colon, and a server name.
fn try_from<S, T>(room_id: S) -> Result<RoomId<T>, Error>
fn try_from<S>(room_id: S) -> Result<RoomId, Error>
where
S: AsRef<str> + Into<T>,
S: AsRef<str> + Into<Box<str>>,
{
let colon_idx = parse_id(room_id.as_ref(), &['!'])?;
@ -87,9 +72,8 @@ mod tests {
#[cfg(feature = "serde")]
use serde_json::{from_str, to_string};
use crate::{error::Error, ServerNameRef};
type RoomId = super::RoomId<Box<str>>;
use super::RoomId;
use crate::{error::Error, ServerName};
#[test]
fn valid_room_id() {
@ -105,7 +89,7 @@ mod tests {
#[test]
fn generate_random_valid_room_id() {
let server_name =
ServerNameRef::try_from("example.com").expect("Failed to parse ServerName");
<&ServerName>::try_from("example.com").expect("Failed to parse ServerName");
let room_id = RoomId::new(server_name);
let id_str = room_id.as_str();

View File

@ -2,9 +2,7 @@
use std::{convert::TryFrom, hint::unreachable_unchecked, num::NonZeroU8};
use crate::{
error::Error, parse_id, room_alias_id::RoomAliasId, room_id::RoomId, server_name::ServerName,
};
use crate::{error::Error, parse_id, server_name::ServerName, RoomAliasId, RoomId};
/// A Matrix room ID or a Matrix room alias ID.
///
@ -12,9 +10,6 @@ use crate::{
/// from a string slice, and can be converted back into a string as needed. When converted from a
/// string slice, the variant is determined by the leading sigil character.
///
/// It is discouraged to use this type directly instead use one of the aliases
/// (`RoomIdOrRoomAliasId` and `RoomIdOrRoomAliasIdRef`) in the crate root.
///
/// ```
/// # use std::convert::TryFrom;
/// # use ruma_identifiers::RoomIdOrAliasId;
@ -28,29 +23,21 @@ use crate::{
/// "!n8f893n9:example.com"
/// );
/// ```
#[derive(Clone, Copy, Debug)]
pub struct RoomIdOrAliasId<T> {
full_id: T,
#[derive(Clone, Debug)]
pub struct RoomIdOrAliasId {
full_id: Box<str>,
colon_idx: NonZeroU8,
}
impl<T> RoomIdOrAliasId<T>
where
T: AsRef<str>,
{
/// Creates a reference to this `RoomIdOrAliasId`.
pub fn as_ref(&self) -> RoomIdOrAliasId<&str> {
RoomIdOrAliasId { full_id: self.full_id.as_ref(), colon_idx: self.colon_idx }
}
impl RoomIdOrAliasId {
/// Returns the local part (everything after the `!` or `#` and before the first colon).
pub fn localpart(&self) -> &str {
&self.full_id.as_ref()[1..self.colon_idx.get() as usize]
&self.full_id[1..self.colon_idx.get() as usize]
}
/// Returns the server name of the room (alias) ID.
pub fn server_name(&self) -> ServerName<&str> {
ServerName::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]).unwrap()
pub fn server_name(&self) -> &ServerName {
<&ServerName>::try_from(&self.full_id[self.colon_idx.get() as usize + 1..]).unwrap()
}
/// Whether this is a room id (starts with `'!'`)
@ -66,7 +53,7 @@ where
/// Turn this `RoomIdOrAliasId` into `Either<RoomId, RoomAliasId>`
#[cfg(feature = "either")]
#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
pub fn into_either(self) -> either::Either<RoomId<T>, RoomAliasId<T>> {
pub fn into_either(self) -> either::Either<RoomId, RoomAliasId> {
match self.variant() {
Variant::RoomId => {
either::Either::Left(RoomId { full_id: self.full_id, colon_idx: self.colon_idx })
@ -79,7 +66,7 @@ where
}
fn variant(&self) -> Variant {
match self.full_id.as_ref().bytes().next() {
match self.full_id.bytes().next() {
Some(b'!') => Variant::RoomId,
Some(b'#') => Variant::RoomAliasId,
_ => unsafe { unreachable_unchecked() },
@ -98,9 +85,9 @@ enum Variant {
/// The string must either include the leading ! sigil, the localpart, a literal colon, and a
/// valid homeserver host or include the leading # sigil, the alias, a literal colon, and a
/// valid homeserver host.
fn try_from<S, T>(room_id_or_alias_id: S) -> Result<RoomIdOrAliasId<T>, Error>
fn try_from<S>(room_id_or_alias_id: S) -> Result<RoomIdOrAliasId, Error>
where
S: AsRef<str> + Into<T>,
S: AsRef<str> + Into<Box<str>>,
{
let colon_idx = parse_id(room_id_or_alias_id.as_ref(), &['#', '!'])?;
Ok(RoomIdOrAliasId { full_id: room_id_or_alias_id.into(), colon_idx })
@ -108,22 +95,22 @@ where
common_impls!(RoomIdOrAliasId, try_from, "a Matrix room ID or room alias ID");
impl<T> From<RoomId<T>> for RoomIdOrAliasId<T> {
fn from(RoomId { full_id, colon_idx }: RoomId<T>) -> Self {
impl From<RoomId> for RoomIdOrAliasId {
fn from(RoomId { full_id, colon_idx }: RoomId) -> Self {
Self { full_id, colon_idx }
}
}
impl<T> From<RoomAliasId<T>> for RoomIdOrAliasId<T> {
fn from(RoomAliasId { full_id, colon_idx }: RoomAliasId<T>) -> Self {
impl From<RoomAliasId> for RoomIdOrAliasId {
fn from(RoomAliasId { full_id, colon_idx }: RoomAliasId) -> Self {
Self { full_id, colon_idx }
}
}
impl<T: AsRef<str>> TryFrom<RoomIdOrAliasId<T>> for RoomId<T> {
type Error = RoomAliasId<T>;
impl TryFrom<RoomIdOrAliasId> for RoomId {
type Error = RoomAliasId;
fn try_from(id: RoomIdOrAliasId<T>) -> Result<RoomId<T>, RoomAliasId<T>> {
fn try_from(id: RoomIdOrAliasId) -> Result<RoomId, RoomAliasId> {
match id.variant() {
Variant::RoomId => Ok(RoomId { full_id: id.full_id, colon_idx: id.colon_idx }),
Variant::RoomAliasId => {
@ -133,10 +120,10 @@ impl<T: AsRef<str>> TryFrom<RoomIdOrAliasId<T>> for RoomId<T> {
}
}
impl<T: AsRef<str>> TryFrom<RoomIdOrAliasId<T>> for RoomAliasId<T> {
type Error = RoomId<T>;
impl TryFrom<RoomIdOrAliasId> for RoomAliasId {
type Error = RoomId;
fn try_from(id: RoomIdOrAliasId<T>) -> Result<RoomAliasId<T>, RoomId<T>> {
fn try_from(id: RoomIdOrAliasId) -> Result<RoomAliasId, RoomId> {
match id.variant() {
Variant::RoomAliasId => {
Ok(RoomAliasId { full_id: id.full_id, colon_idx: id.colon_idx })
@ -153,10 +140,9 @@ mod tests {
#[cfg(feature = "serde")]
use serde_json::{from_str, to_string};
use super::RoomIdOrAliasId;
use crate::error::Error;
type RoomIdOrAliasId = super::RoomIdOrAliasId<Box<str>>;
#[test]
fn valid_room_id_or_alias_id_with_a_room_alias_id() {
assert_eq!(

View File

@ -19,21 +19,18 @@ const MAX_CODE_POINTS: usize = 32;
/// A `RoomVersionId` can be or converted or deserialized from a string slice, and can be converted
/// or serialized back into a string as needed.
///
/// It is discouraged to use this type directly instead use one of the aliases (`RoomVersionId`
/// and `RoomVersionIdRef`) in the crate root.
///
/// ```
/// # use std::convert::TryFrom;
/// # use ruma_identifiers::RoomVersionId;
/// assert_eq!(RoomVersionId::try_from("1").unwrap().as_ref(), "1");
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RoomVersionId<T>(InnerRoomVersionId<T>);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RoomVersionId(InnerRoomVersionId);
/// Possibile values for room version, distinguishing between official Matrix versions and custom
/// versions.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum InnerRoomVersionId<T> {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum InnerRoomVersionId {
/// A version 1 room.
Version1,
@ -53,10 +50,10 @@ enum InnerRoomVersionId<T> {
Version6,
/// A custom room version.
Custom(T),
Custom(Box<str>),
}
impl<T> RoomVersionId<T> {
impl RoomVersionId {
/// Creates a version 1 room ID.
pub fn version_1() -> Self {
Self(InnerRoomVersionId::Version1)
@ -102,37 +99,37 @@ impl<T> RoomVersionId<T> {
/// Whether or not this is a version 1 room.
pub fn is_version_1(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version1)
self.0 == InnerRoomVersionId::Version1
}
/// Whether or not this is a version 2 room.
pub fn is_version_2(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version2)
self.0 == InnerRoomVersionId::Version2
}
/// Whether or not this is a version 3 room.
pub fn is_version_3(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version3)
self.0 == InnerRoomVersionId::Version3
}
/// Whether or not this is a version 4 room.
pub fn is_version_4(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version4)
self.0 == InnerRoomVersionId::Version4
}
/// Whether or not this is a version 5 room.
pub fn is_version_5(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version5)
self.0 == InnerRoomVersionId::Version5
}
/// Whether or not this is a version 6 room.
pub fn is_version_6(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version5)
self.0 == InnerRoomVersionId::Version5
}
}
impl From<RoomVersionId<Box<str>>> for String {
fn from(id: RoomVersionId<Box<str>>) -> Self {
impl From<RoomVersionId> for String {
fn from(id: RoomVersionId) -> Self {
match id.0 {
InnerRoomVersionId::Version1 => "1".to_owned(),
InnerRoomVersionId::Version2 => "2".to_owned(),
@ -145,7 +142,7 @@ impl From<RoomVersionId<Box<str>>> for String {
}
}
impl<T: AsRef<str>> AsRef<str> for RoomVersionId<T> {
impl AsRef<str> for RoomVersionId {
fn as_ref(&self) -> &str {
match &self.0 {
InnerRoomVersionId::Version1 => "1",
@ -154,31 +151,31 @@ impl<T: AsRef<str>> AsRef<str> for RoomVersionId<T> {
InnerRoomVersionId::Version4 => "4",
InnerRoomVersionId::Version5 => "5",
InnerRoomVersionId::Version6 => "6",
InnerRoomVersionId::Custom(version) => version.as_ref(),
InnerRoomVersionId::Custom(version) => version,
}
}
}
impl<T: AsRef<str>> Display for RoomVersionId<T> {
impl Display for RoomVersionId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl<T: PartialEq + AsRef<str>> PartialOrd for RoomVersionId<T> {
fn partial_cmp(&self, other: &RoomVersionId<T>) -> Option<Ordering> {
impl PartialOrd for RoomVersionId {
fn partial_cmp(&self, other: &RoomVersionId) -> Option<Ordering> {
self.as_ref().partial_cmp(other.as_ref())
}
}
impl<T: Eq + AsRef<str>> Ord for RoomVersionId<T> {
impl Ord for RoomVersionId {
fn cmp(&self, other: &Self) -> Ordering {
self.as_ref().cmp(other.as_ref())
}
}
#[cfg(feature = "serde")]
impl<T: AsRef<str>> Serialize for RoomVersionId<T> {
impl Serialize for RoomVersionId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@ -188,7 +185,7 @@ impl<T: AsRef<str>> Serialize for RoomVersionId<T> {
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for RoomVersionId<Box<str>> {
impl<'de> Deserialize<'de> for RoomVersionId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
@ -198,9 +195,9 @@ impl<'de> Deserialize<'de> for RoomVersionId<Box<str>> {
}
/// Attempts to create a new Matrix room version ID from a string representation.
fn try_from<S, T>(room_version_id: S) -> Result<RoomVersionId<T>, Error>
fn try_from<S>(room_version_id: S) -> Result<RoomVersionId, Error>
where
S: AsRef<str> + Into<T>,
S: AsRef<str> + Into<Box<str>>,
{
let version = match room_version_id.as_ref() {
"1" => RoomVersionId(InnerRoomVersionId::Version1),
@ -222,15 +219,7 @@ where
Ok(version)
}
impl<'a> TryFrom<&'a str> for RoomVersionId<&'a str> {
type Error = crate::error::Error;
fn try_from(s: &'a str) -> Result<Self, Error> {
try_from(s)
}
}
impl TryFrom<&str> for RoomVersionId<Box<str>> {
impl TryFrom<&str> for RoomVersionId {
type Error = crate::error::Error;
fn try_from(s: &str) -> Result<Self, Error> {
@ -238,7 +227,7 @@ impl TryFrom<&str> for RoomVersionId<Box<str>> {
}
}
impl TryFrom<String> for RoomVersionId<Box<str>> {
impl TryFrom<String> for RoomVersionId {
type Error = crate::error::Error;
fn try_from(s: String) -> Result<Self, Error> {
@ -246,26 +235,26 @@ impl TryFrom<String> for RoomVersionId<Box<str>> {
}
}
impl<T: AsRef<str>> PartialEq<&str> for RoomVersionId<T> {
impl PartialEq<&str> for RoomVersionId {
fn eq(&self, other: &&str) -> bool {
self.as_ref() == *other
}
}
impl<T: AsRef<str>> PartialEq<RoomVersionId<T>> for &str {
fn eq(&self, other: &RoomVersionId<T>) -> bool {
impl PartialEq<RoomVersionId> for &str {
fn eq(&self, other: &RoomVersionId) -> bool {
*self == other.as_ref()
}
}
impl<T: AsRef<str>> PartialEq<String> for RoomVersionId<T> {
impl PartialEq<String> for RoomVersionId {
fn eq(&self, other: &String) -> bool {
self.as_ref() == other
}
}
impl<T: AsRef<str>> PartialEq<RoomVersionId<T>> for String {
fn eq(&self, other: &RoomVersionId<T>) -> bool {
impl PartialEq<RoomVersionId> for String {
fn eq(&self, other: &RoomVersionId) -> bool {
self == other.as_ref()
}
}
@ -277,10 +266,9 @@ mod tests {
#[cfg(feature = "serde")]
use serde_json::{from_str, to_string};
use super::RoomVersionId;
use crate::error::Error;
type RoomVersionId = super::RoomVersionId<Box<str>>;
#[test]
fn valid_version_1_room_version_id() {
assert_eq!(

View File

@ -6,20 +6,12 @@ use crate::{error::Error, key_algorithms::ServerKeyAlgorithm};
/// Key identifiers used for homeserver signing keys.
#[derive(Clone, Debug)]
pub struct ServerKeyId<T> {
full_id: T,
pub struct ServerKeyId {
full_id: Box<str>,
colon_idx: NonZeroU8,
}
impl<T> ServerKeyId<T>
where
T: AsRef<str>,
{
/// Creates a reference to this `ServerKeyId`.
pub fn as_ref(&self) -> ServerKeyId<&str> {
ServerKeyId { full_id: self.full_id.as_ref(), colon_idx: self.colon_idx }
}
impl ServerKeyId {
/// Returns key algorithm of the server key ID.
pub fn algorithm(&self) -> ServerKeyAlgorithm {
ServerKeyAlgorithm::from_str(&self.full_id.as_ref()[..self.colon_idx.get() as usize])
@ -32,9 +24,9 @@ where
}
}
fn try_from<S, T>(key_id: S) -> Result<ServerKeyId<T>, Error>
fn try_from<S>(key_id: S) -> Result<ServerKeyId, Error>
where
S: AsRef<str> + Into<T>,
S: AsRef<str> + Into<Box<str>>,
{
let key_str = key_id.as_ref();
let colon_idx =
@ -83,7 +75,7 @@ mod tests {
#[cfg(feature = "serde")]
#[test]
fn deserialize_id() {
let server_key_id: ServerKeyId<_> = from_json_value(json!("ed25519:Abc_1")).unwrap();
let server_key_id: ServerKeyId = from_json_value(json!("ed25519:Abc_1")).unwrap();
assert_eq!(server_key_id.algorithm(), ServerKeyAlgorithm::Ed25519);
assert_eq!(server_key_id.version(), "Abc_1");
}
@ -91,22 +83,19 @@ mod tests {
#[cfg(feature = "serde")]
#[test]
fn serialize_id() {
let server_key_id: ServerKeyId<&str> = ServerKeyId::try_from("ed25519:abc123").unwrap();
let server_key_id: ServerKeyId = ServerKeyId::try_from("ed25519:abc123").unwrap();
assert_eq!(to_json_value(&server_key_id).unwrap(), json!("ed25519:abc123"));
}
#[test]
fn invalid_version_characters() {
assert_eq!(
ServerKeyId::<&str>::try_from("ed25519:Abc-1").unwrap_err(),
Error::InvalidCharacters,
);
assert_eq!(ServerKeyId::try_from("ed25519:Abc-1").unwrap_err(), Error::InvalidCharacters,);
}
#[test]
fn invalid_key_algorithm() {
assert_eq!(
ServerKeyId::<&str>::try_from("signed_curve25519:Abc-1").unwrap_err(),
ServerKeyId::try_from("signed_curve25519:Abc-1").unwrap_err(),
Error::UnknownKeyAlgorithm,
);
}
@ -114,7 +103,7 @@ mod tests {
#[test]
fn missing_delimiter() {
assert_eq!(
ServerKeyId::<&str>::try_from("ed25519|Abc_1").unwrap_err(),
ServerKeyId::try_from("ed25519|Abc_1").unwrap_err(),
Error::MissingServerKeyDelimiter,
);
}

View File

@ -1,53 +1,75 @@
//! Matrix-spec compliant server names.
use std::{
convert::TryFrom,
fmt::{self, Display},
mem,
};
use crate::error::Error;
/// A Matrix-spec compliant server name.
///
/// It is discouraged to use this type directly instead use one of the aliases ([`ServerName`](../type.ServerName.html) and
/// [`ServerNameRef`](../type.ServerNameRef.html)) in the crate root.
#[derive(Clone, Copy, Debug)]
pub struct ServerName<T> {
full_id: T,
}
#[repr(transparent)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct ServerName(str);
impl<T> ServerName<T>
where
T: AsRef<str>,
{
/// Creates a reference to this `ServerName`.
pub fn as_ref(&self) -> ServerName<&str> {
ServerName { full_id: self.full_id.as_ref() }
impl ServerName {
#[allow(clippy::transmute_ptr_to_ptr)]
fn from_borrowed(s: &str) -> &Self {
unsafe { mem::transmute(s) }
}
fn from_owned(s: Box<str>) -> Box<Self> {
unsafe { mem::transmute(s) }
}
fn into_owned(self: Box<Self>) -> Box<str> {
unsafe { mem::transmute(self) }
}
/// Creates a string slice from this `ServerName`.
pub fn as_str(&self) -> &str {
&self.0
}
}
fn try_from<S, T>(server_name: S) -> Result<ServerName<T>, Error>
where
S: AsRef<str> + Into<T>,
{
impl Clone for Box<ServerName> {
fn clone(&self) -> Self {
(**self).to_owned()
}
}
impl ToOwned for ServerName {
type Owned = Box<ServerName>;
fn to_owned(&self) -> Self::Owned {
Self::from_owned(self.0.to_owned().into_boxed_str())
}
}
pub(crate) fn validate(server_name: &str) -> Result<(), Error> {
use std::net::Ipv6Addr;
let name = server_name.as_ref();
if name.is_empty() {
if server_name.is_empty() {
return Err(Error::InvalidServerName);
}
let end_of_host = if name.starts_with('[') {
let end_of_ipv6 = match name.find(']') {
let end_of_host = if server_name.starts_with('[') {
let end_of_ipv6 = match server_name.find(']') {
Some(idx) => idx,
None => return Err(Error::InvalidServerName),
};
if name[1..end_of_ipv6].parse::<Ipv6Addr>().is_err() {
if server_name[1..end_of_ipv6].parse::<Ipv6Addr>().is_err() {
return Err(Error::InvalidServerName);
}
end_of_ipv6 + 1
} else {
let end_of_host = name.find(':').unwrap_or_else(|| name.len());
let end_of_host = server_name.find(':').unwrap_or_else(|| server_name.len());
if name[..end_of_host]
if server_name[..end_of_host]
.bytes()
.any(|byte| !(byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'.'))
{
@ -57,81 +79,170 @@ where
end_of_host
};
if name.len() != end_of_host
if server_name.len() != end_of_host
&& (
// hostname is followed by something other than ":port"
name.as_bytes()[end_of_host] != b':'
server_name.as_bytes()[end_of_host] != b':'
// the remaining characters after ':' are not a valid port
|| name[end_of_host + 1..].parse::<u16>().is_err()
|| server_name[end_of_host + 1..].parse::<u16>().is_err()
)
{
Err(Error::InvalidServerName)
} else {
Ok(ServerName { full_id: server_name.into() })
Ok(())
}
}
common_impls!(ServerName, try_from, "An IP address or hostname");
fn try_from<S>(server_name: S) -> Result<Box<ServerName>, Error>
where
S: AsRef<str> + Into<Box<str>>,
{
validate(server_name.as_ref())?;
Ok(ServerName::from_owned(server_name.into()))
}
impl AsRef<str> for ServerName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for Box<ServerName> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<Box<ServerName>> for String {
fn from(id: Box<ServerName>) -> Self {
id.into_owned().into()
}
}
impl<'a> TryFrom<&'a str> for &'a ServerName {
type Error = Error;
fn try_from(server_name: &'a str) -> Result<Self, Self::Error> {
validate(server_name)?;
Ok(ServerName::from_borrowed(server_name))
}
}
impl TryFrom<&str> for Box<ServerName> {
type Error = crate::error::Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
try_from(s)
}
}
impl TryFrom<String> for Box<ServerName> {
type Error = crate::error::Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
try_from(s)
}
}
impl Display for ServerName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Box<ServerName> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
crate::deserialize_id(deserializer, "An IP address or hostname")
}
}
impl PartialEq<str> for ServerName {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<ServerName> for str {
fn eq(&self, other: &ServerName) -> bool {
self == other.as_str()
}
}
impl PartialEq<String> for ServerName {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<ServerName> for String {
fn eq(&self, other: &ServerName) -> bool {
self.as_str() == other.as_str()
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use crate::ServerNameRef;
use super::ServerName;
#[test]
fn ipv4_host() {
assert!(ServerNameRef::try_from("127.0.0.1").is_ok());
assert!(<&ServerName>::try_from("127.0.0.1").is_ok());
}
#[test]
fn ipv4_host_and_port() {
assert!(ServerNameRef::try_from("1.1.1.1:12000").is_ok());
assert!(<&ServerName>::try_from("1.1.1.1:12000").is_ok());
}
#[test]
fn ipv6() {
assert!(ServerNameRef::try_from("[::1]").is_ok());
assert!(<&ServerName>::try_from("[::1]").is_ok());
}
#[test]
fn ipv6_with_port() {
assert!(ServerNameRef::try_from("[1234:5678::abcd]:5678").is_ok());
assert!(<&ServerName>::try_from("[1234:5678::abcd]:5678").is_ok());
}
#[test]
fn dns_name() {
assert!(ServerNameRef::try_from("example.com").is_ok());
assert!(<&ServerName>::try_from("example.com").is_ok());
}
#[test]
fn dns_name_with_port() {
assert!(ServerNameRef::try_from("ruma.io:8080").is_ok());
assert!(<&ServerName>::try_from("ruma.io:8080").is_ok());
}
#[test]
fn empty_string() {
assert!(ServerNameRef::try_from("").is_err());
assert!(<&ServerName>::try_from("").is_err());
}
#[test]
fn invalid_ipv6() {
assert!(ServerNameRef::try_from("[test::1]").is_err());
assert!(<&ServerName>::try_from("[test::1]").is_err());
}
#[test]
fn ipv4_with_invalid_port() {
assert!(ServerNameRef::try_from("127.0.0.1:").is_err());
assert!(<&ServerName>::try_from("127.0.0.1:").is_err());
}
#[test]
fn ipv6_with_invalid_port() {
assert!(ServerNameRef::try_from("[fe80::1]:100000").is_err());
assert!(ServerNameRef::try_from("[fe80::1]!").is_err());
assert!(<&ServerName>::try_from("[fe80::1]:100000").is_err());
assert!(<&ServerName>::try_from("[fe80::1]!").is_err());
}
#[test]
fn dns_name_with_invalid_port() {
assert!(ServerNameRef::try_from("matrix.org:hello").is_err());
assert!(<&ServerName>::try_from("matrix.org:hello").is_err());
}
}

View File

@ -2,16 +2,13 @@
use std::{convert::TryFrom, num::NonZeroU8};
use crate::{error::Error, parse_id, ServerNameRef};
use crate::{error::Error, parse_id, ServerName};
/// A Matrix user ID.
///
/// A `UserId` is generated randomly or converted from a string slice, and can be converted back
/// into a string as needed.
///
/// It is discouraged to use this type directly instead use one of the aliases (`UserId` and
/// `UserIdRef`) in the crate root.
///
/// ```
/// # use std::convert::TryFrom;
/// # use ruma_identifiers::UserId;
@ -20,9 +17,9 @@ use crate::{error::Error, parse_id, ServerNameRef};
/// "@carl:example.com"
/// );
/// ```
#[derive(Clone, Copy, Debug)]
pub struct UserId<T> {
full_id: T,
#[derive(Clone, Debug)]
pub struct UserId {
full_id: Box<str>,
colon_idx: NonZeroU8,
/// Whether this user id is a historical one.
///
@ -32,15 +29,12 @@ pub struct UserId<T> {
is_historical: bool,
}
impl<T> UserId<T>
where
String: Into<T>,
{
impl UserId {
/// Attempts to generate a `UserId` for the given origin server with a localpart consisting of
/// 12 random ASCII characters.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn new(server_name: ServerNameRef<'_>) -> Self {
pub fn new(server_name: &ServerName) -> Self {
use crate::generate_localpart;
let full_id = format!("@{}:{}", generate_localpart(12).to_lowercase(), server_name).into();
@ -56,13 +50,13 @@ where
/// localpart, not the localpart plus the `@` prefix, or the localpart plus server name without
/// the `@` prefix.
pub fn parse_with_server_name(
id: impl AsRef<str> + Into<T>,
server_name: ServerNameRef<'_>,
id: impl AsRef<str> + Into<Box<str>>,
server_name: &ServerName,
) -> Result<Self, Error> {
let id_str = id.as_ref();
if id_str.starts_with('@') {
try_from(id)
try_from(id.into())
} else {
let is_fully_conforming = localpart_is_fully_comforming(id_str)?;
@ -75,32 +69,18 @@ where
}
}
impl<T> UserId<T>
where
T: AsRef<str>,
{
/// Creates a reference to this `UserId`.
pub fn as_ref(&self) -> UserId<&str> {
UserId {
full_id: self.full_id.as_ref(),
colon_idx: self.colon_idx,
is_historical: self.is_historical,
}
}
impl UserId {
/// Returns the user's localpart.
pub fn localpart(&self) -> &str {
&self.full_id.as_ref()[1..self.colon_idx.get() as usize]
&self.full_id[1..self.colon_idx.get() as usize]
}
/// Returns the server name of the user ID.
pub fn server_name(&self) -> ServerNameRef<'_> {
ServerNameRef::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..])
pub fn server_name(&self) -> &ServerName {
<&ServerName>::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..])
.unwrap()
}
}
impl<T> UserId<T> {
/// Whether this user ID is a historical one, i.e. one that doesn't conform to the latest
/// specification of the user ID grammar but is still accepted because it was previously
/// allowed.
@ -112,9 +92,9 @@ impl<T> UserId<T> {
/// Attempts to create a new Matrix user ID from a string representation.
///
/// The string must include the leading @ sigil, the localpart, a literal colon, and a server name.
fn try_from<S, T>(user_id: S) -> Result<UserId<T>, Error>
fn try_from<S>(user_id: S) -> Result<UserId, Error>
where
S: AsRef<str> + Into<T>,
S: AsRef<str> + Into<Box<str>>,
{
let colon_idx = parse_id(user_id.as_ref(), &['@'])?;
let localpart = &user_id.as_ref()[1..colon_idx.get() as usize];
@ -158,9 +138,8 @@ mod tests {
#[cfg(feature = "serde")]
use serde_json::{from_str, to_string};
use crate::{error::Error, ServerNameRef};
type UserId = super::UserId<Box<str>>;
use super::UserId;
use crate::{error::Error, ServerName};
#[test]
fn valid_user_id_from_str() {
@ -173,7 +152,7 @@ mod tests {
#[test]
fn parse_valid_user_id() {
let server_name = ServerNameRef::try_from("example.com").unwrap();
let server_name = <&ServerName>::try_from("example.com").unwrap();
let user_id = UserId::parse_with_server_name("@carl:example.com", server_name)
.expect("Failed to create UserId.");
assert_eq!(user_id.as_ref(), "@carl:example.com");
@ -184,7 +163,7 @@ mod tests {
#[test]
fn parse_valid_user_id_parts() {
let server_name = ServerNameRef::try_from("example.com").unwrap();
let server_name = <&ServerName>::try_from("example.com").unwrap();
let user_id =
UserId::parse_with_server_name("carl", server_name).expect("Failed to create UserId.");
assert_eq!(user_id.as_ref(), "@carl:example.com");
@ -204,7 +183,7 @@ mod tests {
#[test]
fn parse_valid_historical_user_id() {
let server_name = ServerNameRef::try_from("example.com").unwrap();
let server_name = <&ServerName>::try_from("example.com").unwrap();
let user_id = UserId::parse_with_server_name("@a%b[irc]:example.com", server_name)
.expect("Failed to create UserId.");
assert_eq!(user_id.as_ref(), "@a%b[irc]:example.com");
@ -215,7 +194,7 @@ mod tests {
#[test]
fn parse_valid_historical_user_id_parts() {
let server_name = ServerNameRef::try_from("example.com").unwrap();
let server_name = <&ServerName>::try_from("example.com").unwrap();
let user_id = UserId::parse_with_server_name("a%b[irc]", server_name)
.expect("Failed to create UserId.");
assert_eq!(user_id.as_ref(), "@a%b[irc]:example.com");
@ -234,7 +213,7 @@ mod tests {
#[cfg(feature = "rand")]
#[test]
fn generate_random_valid_user_id() {
let server_name = ServerNameRef::try_from("example.com").unwrap();
let server_name = <&ServerName>::try_from("example.com").unwrap();
let user_id = UserId::new(server_name);
assert_eq!(user_id.localpart().len(), 12);
assert_eq!(user_id.server_name(), "example.com");