#![feature(try_from)]
#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
#![deny(warnings)]
#[macro_use]
extern crate lazy_static;
extern crate rand;
extern crate regex;
extern crate serde;
extern crate url;
#[cfg(feature = "diesel")]
#[macro_use]
extern crate diesel;
#[cfg(test)]
extern crate serde_json;
use std::{
convert::TryFrom,
error::Error as StdError,
fmt::{Display, Formatter, Result as FmtResult},
};
#[cfg(feature = "diesel")]
use diesel::sql_types::Text;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use regex::Regex;
use serde::{
de::{Error as SerdeError, Unexpected, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use url::{ParseError, Url};
pub use url::Host;
const MAX_BYTES: usize = 255;
const MIN_CHARS: usize = 4;
const SIGIL_BYTES: usize = 1;
lazy_static! {
static ref USER_LOCALPART_PATTERN: Regex =
Regex::new(r"\A[a-z0-9._=-]+\z").expect("Failed to create user localpart regex.");
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum Error {
InvalidCharacters,
InvalidHost,
MaximumLengthExceeded,
MinimumLengthNotSatisfied,
MissingDelimiter,
MissingSigil,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))]
#[cfg_attr(feature = "diesel", sql_type = "Text")]
pub struct EventId {
hostname: Host,
opaque_id: String,
port: u16,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))]
#[cfg_attr(feature = "diesel", sql_type = "Text")]
pub struct RoomAliasId {
alias: String,
hostname: Host,
port: u16,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))]
#[cfg_attr(feature = "diesel", sql_type = "Text")]
pub struct RoomId {
hostname: Host,
opaque_id: String,
port: u16,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))]
#[cfg_attr(feature = "diesel", sql_type = "Text")]
pub enum RoomIdOrAliasId {
RoomAliasId(RoomAliasId),
RoomId(RoomId),
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))]
#[cfg_attr(feature = "diesel", sql_type = "Text")]
pub struct UserId {
hostname: Host,
localpart: String,
port: u16,
}
struct EventIdVisitor;
struct RoomAliasIdVisitor;
struct RoomIdVisitor;
struct RoomIdOrAliasIdVisitor;
struct UserIdVisitor;
fn display(
f: &mut Formatter,
sigil: char,
localpart: &str,
hostname: &Host,
port: u16,
) -> FmtResult {
if port == 443 {
write!(f, "{}{}:{}", sigil, localpart, hostname)
} else {
write!(f, "{}{}:{}:{}", sigil, localpart, hostname, port)
}
}
fn generate_localpart(length: usize) -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.collect()
}
fn validate_id<'a>(id: &'a str) -> Result<(), Error> {
if id.len() > MAX_BYTES {
return Err(Error::MaximumLengthExceeded);
}
if id.len() < MIN_CHARS {
return Err(Error::MinimumLengthNotSatisfied);
}
Ok(())
}
fn parse_id<'a>(required_sigil: char, id: &'a str) -> Result<(&'a str, Host, u16), Error> {
validate_id(id)?;
if !id.starts_with(required_sigil) {
return Err(Error::MissingSigil);
}
let delimiter_index = match id.find(':') {
Some(index) => index,
None => return Err(Error::MissingDelimiter),
};
let localpart = &id[1..delimiter_index];
let raw_host = &id[delimiter_index + SIGIL_BYTES..];
let url_string = format!("https://{}", raw_host);
let url = Url::parse(&url_string)?;
let host = match url.host() {
Some(host) => host.to_owned(),
None => return Err(Error::InvalidHost),
};
let port = url.port().unwrap_or(443);
Ok((localpart, host, port))
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", self.description())
}
}
impl StdError for Error {
fn description(&self) -> &str {
match *self {
Error::InvalidCharacters => "localpart contains invalid characters",
Error::InvalidHost => "server name is not a valid IP address or domain name",
Error::MaximumLengthExceeded => "ID exceeds 255 bytes",
Error::MinimumLengthNotSatisfied => "ID must be at least 4 characters",
Error::MissingDelimiter => "colon is required between localpart and server name",
Error::MissingSigil => "leading sigil is missing",
}
}
}
impl EventId {
pub fn new(server_name: &str) -> Result<Self, Error> {
let event_id = format!("${}:{}", generate_localpart(18), server_name);
let (opaque_id, host, port) = parse_id('$', &event_id)?;
Ok(EventId {
hostname: host,
opaque_id: opaque_id.to_string(),
port: port,
})
}
pub fn hostname(&self) -> &Host {
&self.hostname
}
pub fn opaque_id(&self) -> &str {
&self.opaque_id
}
pub fn port(&self) -> u16 {
self.port
}
}
impl RoomId {
pub fn new(server_name: &str) -> Result<Self, Error> {
let room_id = format!("!{}:{}", generate_localpart(18), server_name);
let (opaque_id, host, port) = parse_id('!', &room_id)?;
Ok(RoomId {
hostname: host,
opaque_id: opaque_id.to_string(),
port: port,
})
}
pub fn hostname(&self) -> &Host {
&self.hostname
}
pub fn opaque_id(&self) -> &str {
&self.opaque_id
}
pub fn port(&self) -> u16 {
self.port
}
}
impl RoomAliasId {
pub fn hostname(&self) -> &Host {
&self.hostname
}
pub fn alias(&self) -> &str {
&self.alias
}
pub fn port(&self) -> u16 {
self.port
}
}
impl UserId {
pub fn new(server_name: &str) -> Result<Self, Error> {
let user_id = format!("@{}:{}", generate_localpart(12).to_lowercase(), server_name);
let (localpart, host, port) = parse_id('@', &user_id)?;
Ok(UserId {
hostname: host,
localpart: localpart.to_string(),
port: port,
})
}
pub fn hostname(&self) -> &Host {
&self.hostname
}
pub fn localpart(&self) -> &str {
&self.localpart
}
pub fn port(&self) -> u16 {
self.port
}
}
impl From<ParseError> for Error {
fn from(_: ParseError) -> Error {
Error::InvalidHost
}
}
impl Display for EventId {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
display(f, '$', &self.opaque_id, &self.hostname, self.port)
}
}
impl Display for RoomAliasId {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
display(f, '#', &self.alias, &self.hostname, self.port)
}
}
impl Display for RoomId {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
display(f, '!', &self.opaque_id, &self.hostname, self.port)
}
}
impl Display for RoomIdOrAliasId {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
RoomIdOrAliasId::RoomAliasId(ref room_alias_id) => display(
f,
'#',
&room_alias_id.alias,
&room_alias_id.hostname,
room_alias_id.port,
),
RoomIdOrAliasId::RoomId(ref room_id) => {
display(f, '!', &room_id.opaque_id, &room_id.hostname, room_id.port)
}
}
}
}
impl Display for UserId {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
display(f, '@', &self.localpart, &self.hostname, self.port)
}
}
impl Serialize for EventId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Serialize for RoomAliasId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Serialize for RoomId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Serialize for RoomIdOrAliasId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
RoomIdOrAliasId::RoomAliasId(ref room_alias_id) => {
serializer.serialize_str(&room_alias_id.to_string())
}
RoomIdOrAliasId::RoomId(ref room_id) => serializer.serialize_str(&room_id.to_string()),
}
}
}
impl Serialize for UserId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for EventId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(EventIdVisitor)
}
}
impl<'de> Deserialize<'de> for RoomAliasId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(RoomAliasIdVisitor)
}
}
impl<'de> Deserialize<'de> for RoomId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(RoomIdVisitor)
}
}
impl<'de> Deserialize<'de> for RoomIdOrAliasId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(RoomIdOrAliasIdVisitor)
}
}
impl<'de> Deserialize<'de> for UserId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(UserIdVisitor)
}
}
impl<'a> TryFrom<&'a str> for EventId {
type Error = Error;
fn try_from(event_id: &'a str) -> Result<Self, Self::Error> {
let (opaque_id, host, port) = parse_id('$', event_id)?;
Ok(EventId {
hostname: host,
opaque_id: opaque_id.to_owned(),
port: port,
})
}
}
impl<'a> TryFrom<&'a str> for RoomAliasId {
type Error = Error;
fn try_from(room_id: &'a str) -> Result<Self, Error> {
let (alias, host, port) = parse_id('#', room_id)?;
Ok(RoomAliasId {
alias: alias.to_owned(),
hostname: host,
port: port,
})
}
}
impl<'a> TryFrom<&'a str> for RoomId {
type Error = Error;
fn try_from(room_id: &'a str) -> Result<Self, Error> {
let (opaque_id, host, port) = parse_id('!', room_id)?;
Ok(RoomId {
hostname: host,
opaque_id: opaque_id.to_owned(),
port: port,
})
}
}
impl<'a> TryFrom<&'a str> for RoomIdOrAliasId {
type Error = Error;
fn try_from(room_id_or_alias_id: &'a str) -> Result<Self, Error> {
validate_id(room_id_or_alias_id)?;
let mut chars = room_id_or_alias_id.chars();
let sigil = chars.nth(0).expect("ID missing first character.");
match sigil {
'#' => {
let room_alias_id = RoomAliasId::try_from(room_id_or_alias_id)?;
Ok(RoomIdOrAliasId::RoomAliasId(room_alias_id))
}
'!' => {
let room_id = RoomId::try_from(room_id_or_alias_id)?;
Ok(RoomIdOrAliasId::RoomId(room_id))
}
_ => Err(Error::MissingSigil),
}
}
}
impl<'a> TryFrom<&'a str> for UserId {
type Error = Error;
fn try_from(user_id: &'a str) -> Result<UserId, Error> {
let (localpart, host, port) = parse_id('@', user_id)?;
let downcased_localpart = localpart.to_lowercase();
if !USER_LOCALPART_PATTERN.is_match(&downcased_localpart) {
return Err(Error::InvalidCharacters);
}
Ok(UserId {
hostname: host,
port: port,
localpart: downcased_localpart.to_owned(),
})
}
}
impl<'de> Visitor<'de> for EventIdVisitor {
type Value = EventId;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
write!(formatter, "a Matrix event ID as a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
match EventId::try_from(v) {
Ok(event_id) => Ok(event_id),
Err(_) => Err(SerdeError::invalid_value(Unexpected::Str(v), &self)),
}
}
}
impl<'de> Visitor<'de> for RoomAliasIdVisitor {
type Value = RoomAliasId;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
write!(formatter, "a Matrix room alias ID as a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
match RoomAliasId::try_from(v) {
Ok(room_alias_id) => Ok(room_alias_id),
Err(_) => Err(SerdeError::invalid_value(Unexpected::Str(v), &self)),
}
}
}
impl<'de> Visitor<'de> for RoomIdVisitor {
type Value = RoomId;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
write!(formatter, "a Matrix room ID as a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
match RoomId::try_from(v) {
Ok(room_id) => Ok(room_id),
Err(_) => Err(SerdeError::invalid_value(Unexpected::Str(v), &self)),
}
}
}
impl<'de> Visitor<'de> for RoomIdOrAliasIdVisitor {
type Value = RoomIdOrAliasId;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
write!(formatter, "a Matrix room ID or room alias ID as a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
match RoomIdOrAliasId::try_from(v) {
Ok(room_id_or_alias_id) => Ok(room_id_or_alias_id),
Err(_) => Err(SerdeError::invalid_value(Unexpected::Str(v), &self)),
}
}
}
impl<'de> Visitor<'de> for UserIdVisitor {
type Value = UserId;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
write!(formatter, "a Matrix user ID as a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
match UserId::try_from(v) {
Ok(user_id) => Ok(user_id),
Err(_) => Err(SerdeError::invalid_value(Unexpected::Str(v), &self)),
}
}
}
#[cfg(feature = "diesel")]
mod diesel_integration {
use std::{convert::TryFrom, error::Error as StdError, io::Write};
use diesel::{
backend::Backend,
deserialize::{FromSql, Result as DeserializeResult},
serialize::{Output, Result as SerializeResult, ToSql},
sql_types::Text,
};
macro_rules! diesel_impl {
($name:ident) => {
impl<DB> ToSql<Text, DB> for $crate::$name
where
DB: Backend,
{
fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> SerializeResult {
ToSql::<Text, DB>::to_sql(&self.to_string(), out)
}
}
impl<DB> FromSql<Text, DB> for $crate::$name
where
String: FromSql<Text, DB>,
DB: Backend,
{
fn from_sql(value: Option<&<DB as Backend>::RawValue>) -> DeserializeResult<Self> {
let string = <String as FromSql<Text, DB>>::from_sql(value)?;
$crate::$name::try_from(string.as_str())
.map_err(|error| Box::new(error) as Box<StdError + Send + Sync>)
}
}
};
}
diesel_impl!(EventId);
diesel_impl!(RoomAliasId);
diesel_impl!(RoomId);
diesel_impl!(RoomIdOrAliasId);
diesel_impl!(UserId);
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use serde_json::{from_str, to_string};
use super::{Error, EventId, RoomAliasId, RoomId, RoomIdOrAliasId, UserId};
#[test]
fn valid_event_id() {
assert_eq!(
EventId::try_from("$39hvsi03hlne:example.com")
.expect("Failed to create EventId.")
.to_string(),
"$39hvsi03hlne:example.com"
);
}
#[test]
fn generate_random_valid_event_id() {
let event_id = EventId::new("example.com")
.expect("Failed to generate EventId.")
.to_string();
assert!(event_id.to_string().starts_with('$'));
assert_eq!(event_id.len(), 31);
}
#[test]
fn generate_random_invalid_event_id() {
assert!(EventId::new("").is_err());
}
#[test]
fn serialize_valid_event_id() {
assert_eq!(
to_string(
&EventId::try_from("$39hvsi03hlne:example.com").expect("Failed to create EventId.")
)
.expect("Failed to convert EventId to JSON."),
r#""$39hvsi03hlne:example.com""#
);
}
#[test]
fn deserialize_valid_event_id() {
assert_eq!(
from_str::<EventId>(r#""$39hvsi03hlne:example.com""#)
.expect("Failed to convert JSON to EventId"),
EventId::try_from("$39hvsi03hlne:example.com").expect("Failed to create EventId.")
);
}
#[test]
fn valid_event_id_with_explicit_standard_port() {
assert_eq!(
EventId::try_from("$39hvsi03hlne:example.com:443")
.expect("Failed to create EventId.")
.to_string(),
"$39hvsi03hlne:example.com"
);
}
#[test]
fn valid_event_id_with_non_standard_port() {
assert_eq!(
EventId::try_from("$39hvsi03hlne:example.com:5000")
.expect("Failed to create EventId.")
.to_string(),
"$39hvsi03hlne:example.com:5000"
);
}
#[test]
fn missing_event_id_sigil() {
assert_eq!(
EventId::try_from("39hvsi03hlne:example.com").err().unwrap(),
Error::MissingSigil
);
}
#[test]
fn missing_event_id_delimiter() {
assert_eq!(
EventId::try_from("$39hvsi03hlne").err().unwrap(),
Error::MissingDelimiter
);
}
#[test]
fn invalid_event_id_host() {
assert_eq!(
EventId::try_from("$39hvsi03hlne:-").err().unwrap(),
Error::InvalidHost
);
}
#[test]
fn invalid_event_id_port() {
assert_eq!(
EventId::try_from("$39hvsi03hlne:example.com:notaport")
.err()
.unwrap(),
Error::InvalidHost
);
}
#[test]
fn valid_room_alias_id() {
assert_eq!(
RoomAliasId::try_from("#ruma:example.com")
.expect("Failed to create RoomAliasId.")
.to_string(),
"#ruma:example.com"
);
}
#[test]
fn serialize_valid_room_alias_id() {
assert_eq!(
to_string(
&RoomAliasId::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.")
)
.expect("Failed to convert RoomAliasId to JSON."),
r##""#ruma:example.com""##
);
}
#[test]
fn deserialize_valid_room_alias_id() {
assert_eq!(
from_str::<RoomAliasId>(r##""#ruma:example.com""##)
.expect("Failed to convert JSON to RoomAliasId"),
RoomAliasId::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.")
);
}
#[test]
fn valid_room_alias_id_with_explicit_standard_port() {
assert_eq!(
RoomAliasId::try_from("#ruma:example.com:443")
.expect("Failed to create RoomAliasId.")
.to_string(),
"#ruma:example.com"
);
}
#[test]
fn valid_room_alias_id_with_non_standard_port() {
assert_eq!(
RoomAliasId::try_from("#ruma:example.com:5000")
.expect("Failed to create RoomAliasId.")
.to_string(),
"#ruma:example.com:5000"
);
}
#[test]
fn valid_room_alias_id_unicode() {
assert_eq!(
RoomAliasId::try_from("#老虎£я:example.com")
.expect("Failed to create RoomAliasId.")
.to_string(),
"#老虎£я:example.com"
);
}
#[test]
fn missing_room_alias_id_sigil() {
assert_eq!(
RoomAliasId::try_from("39hvsi03hlne:example.com")
.err()
.unwrap(),
Error::MissingSigil
);
}
#[test]
fn missing_room_alias_id_delimiter() {
assert_eq!(
RoomAliasId::try_from("#ruma").err().unwrap(),
Error::MissingDelimiter
);
}
#[test]
fn invalid_room_alias_id_host() {
assert_eq!(
RoomAliasId::try_from("#ruma:-").err().unwrap(),
Error::InvalidHost
);
}
#[test]
fn invalid_room_alias_id_port() {
assert_eq!(
RoomAliasId::try_from("#ruma:example.com:notaport")
.err()
.unwrap(),
Error::InvalidHost
);
}
#[test]
fn valid_room_id() {
assert_eq!(
RoomId::try_from("!29fhd83h92h0:example.com")
.expect("Failed to create RoomId.")
.to_string(),
"!29fhd83h92h0:example.com"
);
}
#[test]
fn generate_random_valid_room_id() {
let room_id = RoomId::new("example.com")
.expect("Failed to generate RoomId.")
.to_string();
assert!(room_id.to_string().starts_with('!'));
assert_eq!(room_id.len(), 31);
}
#[test]
fn generate_random_invalid_room_id() {
assert!(RoomId::new("").is_err());
}
#[test]
fn serialize_valid_room_id() {
assert_eq!(
to_string(
&RoomId::try_from("!29fhd83h92h0:example.com").expect("Failed to create RoomId.")
)
.expect("Failed to convert RoomId to JSON."),
r#""!29fhd83h92h0:example.com""#
);
}
#[test]
fn deserialize_valid_room_id() {
assert_eq!(
from_str::<RoomId>(r#""!29fhd83h92h0:example.com""#)
.expect("Failed to convert JSON to RoomId"),
RoomId::try_from("!29fhd83h92h0:example.com").expect("Failed to create RoomId.")
);
}
#[test]
fn valid_room_id_with_explicit_standard_port() {
assert_eq!(
RoomId::try_from("!29fhd83h92h0:example.com:443")
.expect("Failed to create RoomId.")
.to_string(),
"!29fhd83h92h0:example.com"
);
}
#[test]
fn valid_room_id_with_non_standard_port() {
assert_eq!(
RoomId::try_from("!29fhd83h92h0:example.com:5000")
.expect("Failed to create RoomId.")
.to_string(),
"!29fhd83h92h0:example.com:5000"
);
}
#[test]
fn missing_room_id_sigil() {
assert_eq!(
RoomId::try_from("carl:example.com").err().unwrap(),
Error::MissingSigil
);
}
#[test]
fn missing_room_id_delimiter() {
assert_eq!(
RoomId::try_from("!29fhd83h92h0").err().unwrap(),
Error::MissingDelimiter
);
}
#[test]
fn invalid_room_id_host() {
assert_eq!(
RoomId::try_from("!29fhd83h92h0:-").err().unwrap(),
Error::InvalidHost
);
}
#[test]
fn invalid_room_id_port() {
assert_eq!(
RoomId::try_from("!29fhd83h92h0:example.com:notaport")
.err()
.unwrap(),
Error::InvalidHost
);
}
#[test]
fn valid_room_id_or_alias_id_with_a_room_alias_id() {
assert_eq!(
RoomIdOrAliasId::try_from("#ruma:example.com")
.expect("Failed to create RoomAliasId.")
.to_string(),
"#ruma:example.com"
);
}
#[test]
fn valid_room_id_or_alias_id_with_a_room_id() {
assert_eq!(
RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com")
.expect("Failed to create RoomId.")
.to_string(),
"!29fhd83h92h0:example.com"
);
}
#[test]
fn missing_sigil_for_room_id_or_alias_id() {
assert_eq!(
RoomIdOrAliasId::try_from("ruma:example.com").err().unwrap(),
Error::MissingSigil
);
}
#[test]
fn serialize_valid_room_id_or_alias_id_with_a_room_alias_id() {
assert_eq!(
to_string(
&RoomIdOrAliasId::try_from("#ruma:example.com")
.expect("Failed to create RoomAliasId.")
)
.expect("Failed to convert RoomAliasId to JSON."),
r##""#ruma:example.com""##
);
}
#[test]
fn serialize_valid_room_id_or_alias_id_with_a_room_id() {
assert_eq!(
to_string(
&RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com")
.expect("Failed to create RoomId.")
)
.expect("Failed to convert RoomId to JSON."),
r#""!29fhd83h92h0:example.com""#
);
}
#[test]
fn deserialize_valid_room_id_or_alias_id_with_a_room_alias_id() {
assert_eq!(
from_str::<RoomIdOrAliasId>(r##""#ruma:example.com""##)
.expect("Failed to convert JSON to RoomAliasId"),
RoomIdOrAliasId::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.")
);
}
#[test]
fn deserialize_valid_room_id_or_alias_id_with_a_room_id() {
assert_eq!(
from_str::<RoomIdOrAliasId>(r##""!29fhd83h92h0:example.com""##)
.expect("Failed to convert JSON to RoomId"),
RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com")
.expect("Failed to create RoomAliasId.")
);
}
#[test]
fn valid_user_id() {
assert_eq!(
UserId::try_from("@carl:example.com")
.expect("Failed to create UserId.")
.to_string(),
"@carl:example.com"
);
}
#[test]
fn downcase_user_id() {
assert_eq!(
UserId::try_from("@CARL:example.com")
.expect("Failed to create UserId.")
.to_string(),
"@carl:example.com"
);
}
#[test]
fn generate_random_valid_user_id() {
let user_id = UserId::new("example.com")
.expect("Failed to generate UserId.")
.to_string();
assert!(user_id.to_string().starts_with('@'));
assert_eq!(user_id.len(), 25);
}
#[test]
fn generate_random_invalid_user_id() {
assert!(UserId::new("").is_err());
}
#[test]
fn serialize_valid_user_id() {
assert_eq!(
to_string(&UserId::try_from("@carl:example.com").expect("Failed to create UserId."))
.expect("Failed to convert UserId to JSON."),
r#""@carl:example.com""#
);
}
#[test]
fn deserialize_valid_user_id() {
assert_eq!(
from_str::<UserId>(r#""@carl:example.com""#).expect("Failed to convert JSON to UserId"),
UserId::try_from("@carl:example.com").expect("Failed to create UserId.")
);
}
#[test]
fn valid_user_id_with_explicit_standard_port() {
assert_eq!(
UserId::try_from("@carl:example.com:443")
.expect("Failed to create UserId.")
.to_string(),
"@carl:example.com"
);
}
#[test]
fn valid_user_id_with_non_standard_port() {
assert_eq!(
UserId::try_from("@carl:example.com:5000")
.expect("Failed to create UserId.")
.to_string(),
"@carl:example.com:5000"
);
}
#[test]
fn invalid_characters_in_user_id_localpart() {
assert_eq!(
UserId::try_from("@%%%:example.com").err().unwrap(),
Error::InvalidCharacters
);
}
#[test]
fn missing_user_id_sigil() {
assert_eq!(
UserId::try_from("carl:example.com").err().unwrap(),
Error::MissingSigil
);
}
#[test]
fn missing_user_id_delimiter() {
assert_eq!(
UserId::try_from("@carl").err().unwrap(),
Error::MissingDelimiter
);
}
#[test]
fn invalid_user_id_host() {
assert_eq!(
UserId::try_from("@carl:-").err().unwrap(),
Error::InvalidHost
);
}
#[test]
fn invalid_user_id_port() {
assert_eq!(
UserId::try_from("@carl:example.com:notaport")
.err()
.unwrap(),
Error::InvalidHost
);
}
}