Add borrowed id types

This commit is contained in:
Jonas Platte 2020-05-30 21:01:03 +02:00
parent 06c52c2abb
commit 622d69884c
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
9 changed files with 226 additions and 144 deletions

View File

@ -6,8 +6,8 @@ sources:
tasks: tasks:
- rustup: | - rustup: |
# We specify --profile minimal because we'd otherwise download docs # We specify --profile minimal because we'd otherwise download docs
rustup toolchain install 1.36.0 --profile minimal rustup toolchain install 1.42.0 --profile minimal
rustup default 1.36.0 rustup default 1.42.0
- test: | - test: |
cd ruma-identifiers cd ruma-identifiers

View File

@ -37,13 +37,13 @@ use crate::{error::Error, parse_id, validate_id};
/// "$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg" /// "$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg"
/// ); /// );
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct EventId { pub struct EventId<T> {
full_id: Box<str>, full_id: T,
colon_idx: Option<NonZeroU8>, colon_idx: Option<NonZeroU8>,
} }
impl EventId { impl<T> EventId<T> {
/// Attempts to generate an `EventId` for the given origin server with a localpart consisting /// 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 /// 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. /// as used by Matrix room versions 1 and 2.
@ -52,7 +52,10 @@ impl EventId {
/// parsed as a valid host. /// parsed as a valid host.
#[cfg(feature = "rand")] #[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn new(server_name: &str) -> Result<Self, Error> { pub fn new(server_name: &str) -> Result<Self, Error>
where
String: Into<T>,
{
use crate::{generate_localpart, is_valid_server_name}; use crate::{generate_localpart, is_valid_server_name};
if !is_valid_server_name(server_name) { if !is_valid_server_name(server_name) {
@ -69,21 +72,27 @@ impl EventId {
/// Returns the event's unique ID. For the original event format as used by Matrix room /// 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, /// versions 1 and 2, this is the "localpart" that precedes the homeserver. For later formats,
/// this is the entire ID without the leading $ sigil. /// this is the entire ID without the leading $ sigil.
pub fn localpart(&self) -> &str { pub fn localpart(&self) -> &str
where
T: AsRef<str>,
{
let idx = match self.colon_idx { let idx = match self.colon_idx {
Some(idx) => idx.get() as usize, Some(idx) => idx.get() as usize,
None => self.full_id.len(), None => self.full_id.as_ref().len(),
}; };
&self.full_id[1..idx] &self.full_id.as_ref()[1..idx]
} }
/// Returns the server name of the event ID. /// 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. /// Only applicable to events in the original format as used by Matrix room versions 1 and 2.
pub fn server_name(&self) -> Option<&str> { pub fn server_name(&self) -> Option<&str>
where
T: AsRef<str>,
{
self.colon_idx self.colon_idx
.map(|idx| &self.full_id[idx.get() as usize + 1..]) .map(|idx| &self.full_id.as_ref()[idx.get() as usize + 1..])
} }
} }
@ -91,9 +100,9 @@ impl EventId {
/// ///
/// If using the original event format as used by Matrix room versions 1 and 2, the string must /// 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. /// include the leading $ sigil, the localpart, a literal colon, and a valid homeserver hostname.
fn try_from<S>(event_id: S) -> Result<EventId, Error> fn try_from<S, T>(event_id: S) -> Result<EventId<T>, Error>
where where
S: AsRef<str> + Into<Box<str>>, S: AsRef<str> + Into<T>,
{ {
if event_id.as_ref().contains(':') { if event_id.as_ref().contains(':') {
let colon_idx = parse_id(event_id.as_ref(), &['$'])?; let colon_idx = parse_id(event_id.as_ref(), &['$'])?;
@ -121,9 +130,10 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde_json::{from_str, to_string}; use serde_json::{from_str, to_string};
use super::EventId;
use crate::error::Error; use crate::error::Error;
type EventId = super::EventId<Box<str>>;
#[test] #[test]
fn valid_original_event_id() { fn valid_original_event_id() {
assert_eq!( assert_eq!(

View File

@ -5,7 +5,7 @@
#![deny( #![deny(
missing_copy_implementations, missing_copy_implementations,
missing_debug_implementations, missing_debug_implementations,
missing_docs //missing_docs
)] )]
// Since we support Rust 1.36.0, we can't apply this suggestion yet // Since we support Rust 1.36.0, we can't apply this suggestion yet
#![allow(clippy::use_self)] #![allow(clippy::use_self)]
@ -17,25 +17,52 @@ use std::num::NonZeroU8;
use serde::de::{self, Deserialize as _, Deserializer, Unexpected}; use serde::de::{self, Deserialize as _, Deserializer, Unexpected};
#[doc(inline)] #[doc(inline)]
pub use crate::{ pub use crate::{error::Error, server_name::is_valid_server_name};
device_id::DeviceId, error::Error, event_id::EventId, room_alias_id::RoomAliasId,
room_id::RoomId, room_id_or_room_alias_id::RoomIdOrAliasId, room_version_id::RoomVersionId,
server_name::is_valid_server_name, user_id::UserId,
};
#[macro_use] #[macro_use]
mod macros; mod macros;
pub mod device_id;
mod error; mod error;
mod event_id;
mod room_alias_id;
mod room_id;
mod room_id_or_room_alias_id;
mod room_version_id;
mod server_name; mod server_name;
pub mod device_id;
pub mod event_id;
pub mod room_alias_id;
pub mod room_id;
pub mod room_id_or_room_alias_id;
pub mod room_version_id;
pub mod user_id; pub mod user_id;
/// An owned event ID.
pub type EventId = event_id::EventId<Box<str>>;
/// A reference to an event ID.
pub type EventIdRef<'a> = event_id::EventId<&'a str>;
/// An owned room alias ID.
pub type RoomAliasId = room_alias_id::RoomAliasId<Box<str>>;
/// A reference to a room alias ID.
pub type RoomAliasIdRef<'a> = room_alias_id::RoomAliasId<&'a str>;
/// An owned room ID.
pub type RoomId = room_id::RoomId<Box<str>>;
/// A reference to a room ID.
pub type RoomIdRef<'a> = room_id::RoomId<&'a str>;
/// An owned room alias ID or room ID.
pub type RoomIdOrAliasId = room_id_or_room_alias_id::RoomIdOrAliasId<Box<str>>;
/// A reference to a room alias ID or room ID.
pub type RoomIdOrAliasIdRef<'a> = room_id_or_room_alias_id::RoomIdOrAliasId<&'a str>;
/// An owned room version ID.
pub type RoomVersionId = room_version_id::RoomVersionId<Box<str>>;
/// A reference to a room version ID.
pub type RoomVersionIdRef<'a> = room_version_id::RoomVersionId<&'a str>;
/// An owned user ID.
pub type UserId = user_id::UserId<Box<str>>;
/// A reference to a user ID.
pub type UserIdRef<'a> = user_id::UserId<&'a str>;
/// All identifiers must be 255 bytes or less. /// All identifiers must be 255 bytes or less.
const MAX_BYTES: usize = 255; const MAX_BYTES: usize = 255;
/// The minimum number of characters an ID can be. /// The minimum number of characters an ID can be.

View File

@ -1,12 +1,20 @@
macro_rules! common_impls { macro_rules! common_impls {
($id:ident, $try_from:ident, $desc:literal) => { ($id:ident, $try_from:ident, $desc:literal) => {
impl ::std::convert::From<$id> for ::std::string::String { impl ::std::convert::From<$id<Box<str>>> for ::std::string::String {
fn from(id: $id) -> Self { fn from(id: $id<Box<str>>) -> Self {
id.full_id.into() id.full_id.into()
} }
} }
impl ::std::convert::TryFrom<&str> for $id { 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>> {
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(s: &str) -> Result<Self, Self::Error> { fn try_from(s: &str) -> Result<Self, Self::Error> {
@ -14,7 +22,7 @@ macro_rules! common_impls {
} }
} }
impl ::std::convert::TryFrom<String> for $id { impl ::std::convert::TryFrom<String> for $id<Box<str>> {
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(s: String) -> Result<Self, Self::Error> { fn try_from(s: String) -> Result<Self, Self::Error> {
@ -22,56 +30,56 @@ macro_rules! common_impls {
} }
} }
impl ::std::convert::AsRef<str> for $id { impl<T: ::std::convert::AsRef<str>> ::std::convert::AsRef<str> for $id<T> {
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
&self.full_id self.full_id.as_ref()
} }
} }
impl ::std::fmt::Display for $id { impl<T: ::std::fmt::Display> ::std::fmt::Display for $id<T> {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}", self.full_id) write!(f, "{}", self.full_id)
} }
} }
impl ::std::cmp::PartialEq for $id { impl<T: ::std::cmp::PartialEq> ::std::cmp::PartialEq for $id<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.full_id == other.full_id self.full_id == other.full_id
} }
} }
impl ::std::cmp::Eq for $id {} impl<T: ::std::cmp::Eq> ::std::cmp::Eq for $id<T> {}
impl ::std::cmp::PartialOrd for $id { impl<T: ::std::cmp::PartialOrd> ::std::cmp::PartialOrd for $id<T> {
fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> { 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.full_id, &other.full_id)
} }
} }
impl ::std::cmp::Ord for $id { impl<T: ::std::cmp::Ord> ::std::cmp::Ord for $id<T> {
fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
::std::cmp::Ord::cmp(&self.full_id, &other.full_id) ::std::cmp::Ord::cmp(&self.full_id, &other.full_id)
} }
} }
impl ::std::hash::Hash for $id { impl<T: ::std::hash::Hash> ::std::hash::Hash for $id<T> {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) { fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.full_id.hash(state); self.full_id.hash(state);
} }
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl ::serde::Serialize for $id { impl<T: AsRef<str>> ::serde::Serialize for $id<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: ::serde::Serializer, S: ::serde::Serializer,
{ {
serializer.serialize_str(&self.full_id) serializer.serialize_str(self.full_id.as_ref())
} }
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl<'de> ::serde::Deserialize<'de> for $id { impl<'de> ::serde::Deserialize<'de> for $id<Box<str>> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: ::serde::Deserializer<'de>, D: ::serde::Deserializer<'de>,
@ -80,27 +88,27 @@ macro_rules! common_impls {
} }
} }
impl ::std::cmp::PartialEq<&str> for $id { impl<T: AsRef<str>> ::std::cmp::PartialEq<&str> for $id<T> {
fn eq(&self, other: &&str) -> bool { fn eq(&self, other: &&str) -> bool {
&self.full_id[..] == *other self.full_id.as_ref() == *other
} }
} }
impl ::std::cmp::PartialEq<$id> for &str { impl<T: AsRef<str>> ::std::cmp::PartialEq<$id<T>> for &str {
fn eq(&self, other: &$id) -> bool { fn eq(&self, other: &$id<T>) -> bool {
*self == &other.full_id[..] *self == other.full_id.as_ref()
} }
} }
impl ::std::cmp::PartialEq<::std::string::String> for $id { impl<T: AsRef<str>> ::std::cmp::PartialEq<::std::string::String> for $id<T> {
fn eq(&self, other: &::std::string::String) -> bool { fn eq(&self, other: &::std::string::String) -> bool {
&self.full_id[..] == &other[..] self.full_id.as_ref() == &other[..]
} }
} }
impl ::std::cmp::PartialEq<$id> for ::std::string::String { impl<T: AsRef<str>> ::std::cmp::PartialEq<$id<T>> for ::std::string::String {
fn eq(&self, other: &$id) -> bool { fn eq(&self, other: &$id<T>) -> bool {
&self[..] == &other.full_id[..] &self[..] == other.full_id.as_ref()
} }
} }
}; };

View File

@ -17,30 +17,30 @@ use crate::{error::Error, parse_id};
/// "#ruma:example.com" /// "#ruma:example.com"
/// ); /// );
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct RoomAliasId { pub struct RoomAliasId<T> {
pub(crate) full_id: Box<str>, pub(crate) full_id: T,
pub(crate) colon_idx: NonZeroU8, pub(crate) colon_idx: NonZeroU8,
} }
impl RoomAliasId { impl<T: AsRef<str>> RoomAliasId<T> {
/// Returns the room's alias. /// Returns the room's alias.
pub fn alias(&self) -> &str { pub fn alias(&self) -> &str {
&self.full_id[1..self.colon_idx.get() as usize] &self.full_id.as_ref()[1..self.colon_idx.get() as usize]
} }
/// Returns the server name of the room alias ID. /// Returns the server name of the room alias ID.
pub fn server_name(&self) -> &str { pub fn server_name(&self) -> &str {
&self.full_id[self.colon_idx.get() as usize + 1..] &self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]
} }
} }
/// Attempts to create a new Matrix room alias ID from a string representation. /// 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. /// The string must include the leading # sigil, the alias, a literal colon, and a server name.
fn try_from<S>(room_id: S) -> Result<RoomAliasId, Error> fn try_from<S, T>(room_id: S) -> Result<RoomAliasId<T>, Error>
where where
S: AsRef<str> + Into<Box<str>>, S: AsRef<str> + Into<T>,
{ {
let colon_idx = parse_id(room_id.as_ref(), &['#'])?; let colon_idx = parse_id(room_id.as_ref(), &['#'])?;
@ -59,9 +59,10 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde_json::{from_str, to_string}; use serde_json::{from_str, to_string};
use super::RoomAliasId;
use crate::error::Error; use crate::error::Error;
type RoomAliasId = super::RoomAliasId<Box<str>>;
#[test] #[test]
fn valid_room_alias_id() { fn valid_room_alias_id() {
assert_eq!( assert_eq!(

View File

@ -17,20 +17,23 @@ use crate::{error::Error, parse_id};
/// "!n8f893n9:example.com" /// "!n8f893n9:example.com"
/// ); /// );
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct RoomId { pub struct RoomId<T> {
pub(crate) full_id: Box<str>, pub(crate) full_id: T,
pub(crate) colon_idx: NonZeroU8, pub(crate) colon_idx: NonZeroU8,
} }
impl RoomId { impl<T> RoomId<T> {
/// Attempts to generate a `RoomId` for the given origin server with a localpart consisting of /// Attempts to generate a `RoomId` for the given origin server with a localpart consisting of
/// 18 random ASCII characters. /// 18 random ASCII characters.
/// ///
/// Fails if the given homeserver cannot be parsed as a valid host. /// Fails if the given homeserver cannot be parsed as a valid host.
#[cfg(feature = "rand")] #[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn new(server_name: &str) -> Result<Self, Error> { pub fn new(server_name: &str) -> Result<Self, Error>
where
String: Into<T>,
{
use crate::{generate_localpart, is_valid_server_name}; use crate::{generate_localpart, is_valid_server_name};
if !is_valid_server_name(server_name) { if !is_valid_server_name(server_name) {
@ -45,22 +48,28 @@ impl RoomId {
} }
/// Returns the rooms's unique ID. /// Returns the rooms's unique ID.
pub fn localpart(&self) -> &str { pub fn localpart(&self) -> &str
&self.full_id[1..self.colon_idx.get() as usize] where
T: AsRef<str>,
{
&self.full_id.as_ref()[1..self.colon_idx.get() as usize]
} }
/// Returns the server name of the room ID. /// Returns the server name of the room ID.
pub fn server_name(&self) -> &str { pub fn server_name(&self) -> &str
&self.full_id[self.colon_idx.get() as usize + 1..] where
T: AsRef<str>,
{
&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]
} }
} }
/// Attempts to create a new Matrix room ID from a string representation. /// 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. /// The string must include the leading ! sigil, the localpart, a literal colon, and a server name.
fn try_from<S>(room_id: S) -> Result<RoomId, Error> fn try_from<S, T>(room_id: S) -> Result<RoomId<T>, Error>
where where
S: AsRef<str> + Into<Box<str>>, S: AsRef<str> + Into<T>,
{ {
let colon_idx = parse_id(room_id.as_ref(), &['!'])?; let colon_idx = parse_id(room_id.as_ref(), &['!'])?;
@ -79,9 +88,10 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde_json::{from_str, to_string}; use serde_json::{from_str, to_string};
use super::RoomId;
use crate::error::Error; use crate::error::Error;
type RoomId = super::RoomId<Box<str>>;
#[test] #[test]
fn valid_room_id() { fn valid_room_id() {
assert_eq!( assert_eq!(

View File

@ -2,7 +2,7 @@
use std::{convert::TryFrom, hint::unreachable_unchecked, num::NonZeroU8}; use std::{convert::TryFrom, hint::unreachable_unchecked, num::NonZeroU8};
use crate::{error::Error, parse_id, RoomAliasId, RoomId}; use crate::{error::Error, parse_id, room_alias_id::RoomAliasId, room_id::RoomId};
/// A Matrix room ID or a Matrix room alias ID. /// A Matrix room ID or a Matrix room alias ID.
/// ///
@ -23,21 +23,21 @@ use crate::{error::Error, parse_id, RoomAliasId, RoomId};
/// "!n8f893n9:example.com" /// "!n8f893n9:example.com"
/// ); /// );
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct RoomIdOrAliasId { pub struct RoomIdOrAliasId<T> {
full_id: Box<str>, full_id: T,
colon_idx: NonZeroU8, colon_idx: NonZeroU8,
} }
impl RoomIdOrAliasId { impl<T: AsRef<str>> RoomIdOrAliasId<T> {
/// Returns the local part (everything after the `!` or `#` and before the first colon). /// Returns the local part (everything after the `!` or `#` and before the first colon).
pub fn localpart(&self) -> &str { pub fn localpart(&self) -> &str {
&self.full_id[1..self.colon_idx.get() as usize] &self.full_id.as_ref()[1..self.colon_idx.get() as usize]
} }
/// Returns the server name of the room (alias) ID. /// Returns the server name of the room (alias) ID.
pub fn server_name(&self) -> &str { pub fn server_name(&self) -> &str {
&self.full_id[self.colon_idx.get() as usize + 1..] &self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]
} }
/// Whether this is a room id (starts with `'!'`) /// Whether this is a room id (starts with `'!'`)
@ -53,7 +53,7 @@ impl RoomIdOrAliasId {
/// Turn this `RoomIdOrAliasId` into `Either<RoomId, RoomAliasId>` /// Turn this `RoomIdOrAliasId` into `Either<RoomId, RoomAliasId>`
#[cfg(feature = "either")] #[cfg(feature = "either")]
#[cfg_attr(docsrs, doc(cfg(feature = "either")))] #[cfg_attr(docsrs, doc(cfg(feature = "either")))]
pub fn into_either(self) -> either::Either<RoomId, RoomAliasId> { pub fn into_either(self) -> either::Either<RoomId<T>, RoomAliasId<T>> {
match self.variant() { match self.variant() {
Variant::RoomId => either::Either::Left(RoomId { Variant::RoomId => either::Either::Left(RoomId {
full_id: self.full_id, full_id: self.full_id,
@ -67,7 +67,7 @@ impl RoomIdOrAliasId {
} }
fn variant(&self) -> Variant { fn variant(&self) -> Variant {
match self.full_id.bytes().next() { match self.full_id.as_ref().bytes().next() {
Some(b'!') => Variant::RoomId, Some(b'!') => Variant::RoomId,
Some(b'#') => Variant::RoomAliasId, Some(b'#') => Variant::RoomAliasId,
_ => unsafe { unreachable_unchecked() }, _ => unsafe { unreachable_unchecked() },
@ -86,9 +86,9 @@ enum Variant {
/// The string must either include the leading ! sigil, the localpart, a literal colon, and a /// 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 or include the leading # sigil, the alias, a literal colon, and a
/// valid homeserver host. /// valid homeserver host.
fn try_from<S>(room_id_or_alias_id: S) -> Result<RoomIdOrAliasId, Error> fn try_from<S, T>(room_id_or_alias_id: S) -> Result<RoomIdOrAliasId<T>, Error>
where where
S: AsRef<str> + Into<Box<str>>, S: AsRef<str> + Into<T>,
{ {
let colon_idx = parse_id(room_id_or_alias_id.as_ref(), &['#', '!'])?; let colon_idx = parse_id(room_id_or_alias_id.as_ref(), &['#', '!'])?;
Ok(RoomIdOrAliasId { Ok(RoomIdOrAliasId {
@ -103,22 +103,22 @@ common_impls!(
"a Matrix room ID or room alias ID" "a Matrix room ID or room alias ID"
); );
impl From<RoomId> for RoomIdOrAliasId { impl<T> From<RoomId<T>> for RoomIdOrAliasId<T> {
fn from(RoomId { full_id, colon_idx }: RoomId) -> Self { fn from(RoomId { full_id, colon_idx }: RoomId<T>) -> Self {
Self { full_id, colon_idx } Self { full_id, colon_idx }
} }
} }
impl From<RoomAliasId> for RoomIdOrAliasId { impl<T> From<RoomAliasId<T>> for RoomIdOrAliasId<T> {
fn from(RoomAliasId { full_id, colon_idx }: RoomAliasId) -> Self { fn from(RoomAliasId { full_id, colon_idx }: RoomAliasId<T>) -> Self {
Self { full_id, colon_idx } Self { full_id, colon_idx }
} }
} }
impl TryFrom<RoomIdOrAliasId> for RoomId { impl<T: AsRef<str>> TryFrom<RoomIdOrAliasId<T>> for RoomId<T> {
type Error = RoomAliasId; type Error = RoomAliasId<T>;
fn try_from(id: RoomIdOrAliasId) -> Result<RoomId, RoomAliasId> { fn try_from(id: RoomIdOrAliasId<T>) -> Result<RoomId<T>, RoomAliasId<T>> {
match id.variant() { match id.variant() {
Variant::RoomId => Ok(RoomId { Variant::RoomId => Ok(RoomId {
full_id: id.full_id, full_id: id.full_id,
@ -132,10 +132,10 @@ impl TryFrom<RoomIdOrAliasId> for RoomId {
} }
} }
impl TryFrom<RoomIdOrAliasId> for RoomAliasId { impl<T: AsRef<str>> TryFrom<RoomIdOrAliasId<T>> for RoomAliasId<T> {
type Error = RoomId; type Error = RoomId<T>;
fn try_from(id: RoomIdOrAliasId) -> Result<RoomAliasId, RoomId> { fn try_from(id: RoomIdOrAliasId<T>) -> Result<RoomAliasId<T>, RoomId<T>> {
match id.variant() { match id.variant() {
Variant::RoomAliasId => Ok(RoomAliasId { Variant::RoomAliasId => Ok(RoomAliasId {
full_id: id.full_id, full_id: id.full_id,
@ -156,9 +156,10 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde_json::{from_str, to_string}; use serde_json::{from_str, to_string};
use super::RoomIdOrAliasId;
use crate::error::Error; use crate::error::Error;
type RoomIdOrAliasId = super::RoomIdOrAliasId<Box<str>>;
#[test] #[test]
fn valid_room_id_or_alias_id_with_a_room_alias_id() { fn valid_room_id_or_alias_id_with_a_room_alias_id() {
assert_eq!( assert_eq!(

View File

@ -24,13 +24,13 @@ const MAX_CODE_POINTS: usize = 32;
/// # use ruma_identifiers::RoomVersionId; /// # use ruma_identifiers::RoomVersionId;
/// assert_eq!(RoomVersionId::try_from("1").unwrap().as_ref(), "1"); /// assert_eq!(RoomVersionId::try_from("1").unwrap().as_ref(), "1");
/// ``` /// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RoomVersionId(InnerRoomVersionId); pub struct RoomVersionId<T>(InnerRoomVersionId<T>);
/// Possibile values for room version, distinguishing between official Matrix versions and custom /// Possibile values for room version, distinguishing between official Matrix versions and custom
/// versions. /// versions.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum InnerRoomVersionId { enum InnerRoomVersionId<T> {
/// A version 1 room. /// A version 1 room.
Version1, Version1,
@ -50,10 +50,10 @@ enum InnerRoomVersionId {
Version6, Version6,
/// A custom room version. /// A custom room version.
Custom(Box<str>), Custom(T),
} }
impl RoomVersionId { impl<T> RoomVersionId<T> {
/// Creates a version 1 room ID. /// Creates a version 1 room ID.
pub fn version_1() -> Self { pub fn version_1() -> Self {
Self(InnerRoomVersionId::Version1) Self(InnerRoomVersionId::Version1)
@ -85,7 +85,10 @@ impl RoomVersionId {
} }
/// Creates a custom room version ID from the given string slice. /// Creates a custom room version ID from the given string slice.
pub fn custom(id: String) -> Self { pub fn custom(id: String) -> Self
where
String: Into<T>,
{
Self(InnerRoomVersionId::Custom(id.into())) Self(InnerRoomVersionId::Custom(id.into()))
} }
@ -104,37 +107,37 @@ impl RoomVersionId {
/// Whether or not this is a version 1 room. /// Whether or not this is a version 1 room.
pub fn is_version_1(&self) -> bool { pub fn is_version_1(&self) -> bool {
self.0 == InnerRoomVersionId::Version1 matches!(self.0, InnerRoomVersionId::Version1)
} }
/// Whether or not this is a version 2 room. /// Whether or not this is a version 2 room.
pub fn is_version_2(&self) -> bool { pub fn is_version_2(&self) -> bool {
self.0 == InnerRoomVersionId::Version2 matches!(self.0, InnerRoomVersionId::Version2)
} }
/// Whether or not this is a version 3 room. /// Whether or not this is a version 3 room.
pub fn is_version_3(&self) -> bool { pub fn is_version_3(&self) -> bool {
self.0 == InnerRoomVersionId::Version3 matches!(self.0, InnerRoomVersionId::Version3)
} }
/// Whether or not this is a version 4 room. /// Whether or not this is a version 4 room.
pub fn is_version_4(&self) -> bool { pub fn is_version_4(&self) -> bool {
self.0 == InnerRoomVersionId::Version4 matches!(self.0, InnerRoomVersionId::Version4)
} }
/// Whether or not this is a version 5 room. /// Whether or not this is a version 5 room.
pub fn is_version_5(&self) -> bool { pub fn is_version_5(&self) -> bool {
self.0 == InnerRoomVersionId::Version5 matches!(self.0, InnerRoomVersionId::Version5)
} }
/// Whether or not this is a version 6 room. /// Whether or not this is a version 6 room.
pub fn is_version_6(&self) -> bool { pub fn is_version_6(&self) -> bool {
self.0 == InnerRoomVersionId::Version5 matches!(self.0, InnerRoomVersionId::Version5)
} }
} }
impl From<RoomVersionId> for String { impl From<RoomVersionId<Box<str>>> for String {
fn from(id: RoomVersionId) -> Self { fn from(id: RoomVersionId<Box<str>>) -> Self {
match id.0 { match id.0 {
InnerRoomVersionId::Version1 => "1".to_owned(), InnerRoomVersionId::Version1 => "1".to_owned(),
InnerRoomVersionId::Version2 => "2".to_owned(), InnerRoomVersionId::Version2 => "2".to_owned(),
@ -147,7 +150,7 @@ impl From<RoomVersionId> for String {
} }
} }
impl AsRef<str> for RoomVersionId { impl<T: AsRef<str>> AsRef<str> for RoomVersionId<T> {
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
match &self.0 { match &self.0 {
InnerRoomVersionId::Version1 => "1", InnerRoomVersionId::Version1 => "1",
@ -156,31 +159,31 @@ impl AsRef<str> for RoomVersionId {
InnerRoomVersionId::Version4 => "4", InnerRoomVersionId::Version4 => "4",
InnerRoomVersionId::Version5 => "5", InnerRoomVersionId::Version5 => "5",
InnerRoomVersionId::Version6 => "6", InnerRoomVersionId::Version6 => "6",
InnerRoomVersionId::Custom(version) => version, InnerRoomVersionId::Custom(version) => version.as_ref(),
} }
} }
} }
impl Display for RoomVersionId { impl<T: AsRef<str>> Display for RoomVersionId<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref()) write!(f, "{}", self.as_ref())
} }
} }
impl PartialOrd for RoomVersionId { impl<T: PartialEq + AsRef<str>> PartialOrd for RoomVersionId<T> {
fn partial_cmp(&self, other: &RoomVersionId) -> Option<Ordering> { fn partial_cmp(&self, other: &RoomVersionId<T>) -> Option<Ordering> {
self.as_ref().partial_cmp(other.as_ref()) self.as_ref().partial_cmp(other.as_ref())
} }
} }
impl Ord for RoomVersionId { impl<T: Eq + AsRef<str>> Ord for RoomVersionId<T> {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
self.as_ref().cmp(other.as_ref()) self.as_ref().cmp(other.as_ref())
} }
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl Serialize for RoomVersionId { impl<T: AsRef<str>> Serialize for RoomVersionId<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
@ -190,7 +193,7 @@ impl Serialize for RoomVersionId {
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for RoomVersionId { impl<'de> Deserialize<'de> for RoomVersionId<Box<str>> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
@ -200,9 +203,9 @@ impl<'de> Deserialize<'de> for RoomVersionId {
} }
/// Attempts to create a new Matrix room version ID from a string representation. /// Attempts to create a new Matrix room version ID from a string representation.
fn try_from<S>(room_version_id: S) -> Result<RoomVersionId, Error> fn try_from<S, T>(room_version_id: S) -> Result<RoomVersionId<T>, Error>
where where
S: AsRef<str> + Into<Box<str>>, S: AsRef<str> + Into<T>,
{ {
let version = match room_version_id.as_ref() { let version = match room_version_id.as_ref() {
"1" => RoomVersionId(InnerRoomVersionId::Version1), "1" => RoomVersionId(InnerRoomVersionId::Version1),
@ -224,7 +227,15 @@ where
Ok(version) Ok(version)
} }
impl TryFrom<&str> for RoomVersionId { 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>> {
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(s: &str) -> Result<Self, Error> { fn try_from(s: &str) -> Result<Self, Error> {
@ -232,7 +243,7 @@ impl TryFrom<&str> for RoomVersionId {
} }
} }
impl TryFrom<String> for RoomVersionId { impl TryFrom<String> for RoomVersionId<Box<str>> {
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(s: String) -> Result<Self, Error> { fn try_from(s: String) -> Result<Self, Error> {
@ -240,26 +251,26 @@ impl TryFrom<String> for RoomVersionId {
} }
} }
impl PartialEq<&str> for RoomVersionId { impl<T: AsRef<str>> PartialEq<&str> for RoomVersionId<T> {
fn eq(&self, other: &&str) -> bool { fn eq(&self, other: &&str) -> bool {
self.as_ref() == *other self.as_ref() == *other
} }
} }
impl PartialEq<RoomVersionId> for &str { impl<T: AsRef<str>> PartialEq<RoomVersionId<T>> for &str {
fn eq(&self, other: &RoomVersionId) -> bool { fn eq(&self, other: &RoomVersionId<T>) -> bool {
*self == other.as_ref() *self == other.as_ref()
} }
} }
impl PartialEq<String> for RoomVersionId { impl<T: AsRef<str>> PartialEq<String> for RoomVersionId<T> {
fn eq(&self, other: &String) -> bool { fn eq(&self, other: &String) -> bool {
self.as_ref() == other self.as_ref() == other
} }
} }
impl PartialEq<RoomVersionId> for String { impl<T: AsRef<str>> PartialEq<RoomVersionId<T>> for String {
fn eq(&self, other: &RoomVersionId) -> bool { fn eq(&self, other: &RoomVersionId<T>) -> bool {
self == other.as_ref() self == other.as_ref()
} }
} }
@ -271,9 +282,10 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde_json::{from_str, to_string}; use serde_json::{from_str, to_string};
use super::RoomVersionId;
use crate::error::Error; use crate::error::Error;
type RoomVersionId = super::RoomVersionId<Box<str>>;
#[test] #[test]
fn valid_version_1_room_version_id() { fn valid_version_1_room_version_id() {
assert_eq!( assert_eq!(

View File

@ -17,9 +17,9 @@ use crate::{error::Error, is_valid_server_name, parse_id};
/// "@carl:example.com" /// "@carl:example.com"
/// ); /// );
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct UserId { pub struct UserId<T> {
full_id: Box<str>, full_id: T,
colon_idx: NonZeroU8, colon_idx: NonZeroU8,
/// Whether this user id is a historical one. /// Whether this user id is a historical one.
/// ///
@ -29,14 +29,17 @@ pub struct UserId {
is_historical: bool, is_historical: bool,
} }
impl UserId { impl<T> UserId<T> {
/// Attempts to generate a `UserId` for the given origin server with a localpart consisting of /// Attempts to generate a `UserId` for the given origin server with a localpart consisting of
/// 12 random ASCII characters. /// 12 random ASCII characters.
/// ///
/// Fails if the given homeserver cannot be parsed as a valid host. /// Fails if the given homeserver cannot be parsed as a valid host.
#[cfg(feature = "rand")] #[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn new(server_name: &str) -> Result<Self, Error> { pub fn new(server_name: &str) -> Result<Self, Error>
where
String: Into<T>,
{
use crate::generate_localpart; use crate::generate_localpart;
if !is_valid_server_name(server_name) { if !is_valid_server_name(server_name) {
@ -59,13 +62,16 @@ impl UserId {
/// localpart, not the localpart plus the `@` prefix, or the localpart plus server name without /// localpart, not the localpart plus the `@` prefix, or the localpart plus server name without
/// the `@` prefix. /// the `@` prefix.
pub fn parse_with_server_name( pub fn parse_with_server_name(
id: impl AsRef<str> + Into<Box<str>>, id: impl AsRef<str> + Into<T>,
server_name: &str, server_name: &str,
) -> Result<Self, Error> { ) -> Result<Self, Error>
where
String: Into<T>,
{
let id_str = id.as_ref(); let id_str = id.as_ref();
if id_str.starts_with('@') { if id_str.starts_with('@') {
try_from(id.into()) try_from(id)
} else { } else {
let is_fully_conforming = localpart_is_fully_comforming(id_str)?; let is_fully_conforming = localpart_is_fully_comforming(id_str)?;
if !is_valid_server_name(server_name) { if !is_valid_server_name(server_name) {
@ -81,13 +87,19 @@ impl UserId {
} }
/// Returns the user's localpart. /// Returns the user's localpart.
pub fn localpart(&self) -> &str { pub fn localpart(&self) -> &str
&self.full_id[1..self.colon_idx.get() as usize] where
T: AsRef<str>,
{
&self.full_id.as_ref()[1..self.colon_idx.get() as usize]
} }
/// Returns the server name of the user ID. /// Returns the server name of the user ID.
pub fn server_name(&self) -> &str { pub fn server_name(&self) -> &str
&self.full_id[self.colon_idx.get() as usize + 1..] where
T: AsRef<str>,
{
&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]
} }
/// Whether this user ID is a historical one, i.e. one that doesn't conform to the latest /// Whether this user ID is a historical one, i.e. one that doesn't conform to the latest
@ -101,9 +113,9 @@ impl UserId {
/// Attempts to create a new Matrix user ID from a string representation. /// 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. /// The string must include the leading @ sigil, the localpart, a literal colon, and a server name.
fn try_from<S>(user_id: S) -> Result<UserId, Error> fn try_from<S, T>(user_id: S) -> Result<UserId<T>, Error>
where where
S: AsRef<str> + Into<Box<str>>, S: AsRef<str> + Into<T>,
{ {
let colon_idx = parse_id(user_id.as_ref(), &['@'])?; let colon_idx = parse_id(user_id.as_ref(), &['@'])?;
let localpart = &user_id.as_ref()[1..colon_idx.get() as usize]; let localpart = &user_id.as_ref()[1..colon_idx.get() as usize];
@ -151,9 +163,10 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde_json::{from_str, to_string}; use serde_json::{from_str, to_string};
use super::UserId;
use crate::error::Error; use crate::error::Error;
type UserId = super::UserId<Box<str>>;
#[test] #[test]
fn valid_user_id_from_str() { fn valid_user_id_from_str() {
let user_id = UserId::try_from("@carl:example.com").expect("Failed to create UserId."); let user_id = UserId::try_from("@carl:example.com").expect("Failed to create UserId.");