Move each type into its own module.
This commit is contained in:
parent
1f7ac74adb
commit
44de3bcb4d
41
src/diesel_integration.rs
Normal file
41
src/diesel_integration.rs
Normal file
@ -0,0 +1,41 @@
|
||||
//! Implements traits from Diesel, allowing identifiers to be used as database fields.
|
||||
|
||||
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)?;
|
||||
Self::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);
|
50
src/error.rs
Normal file
50
src/error.rs
Normal file
@ -0,0 +1,50 @@
|
||||
//! Error conditions.
|
||||
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
use url::ParseError;
|
||||
|
||||
/// An error encountered when trying to parse an invalid ID string.
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Error {
|
||||
/// The ID's localpart contains invalid characters.
|
||||
///
|
||||
/// Only relevant for user IDs.
|
||||
InvalidCharacters,
|
||||
/// The domain part of the the ID string is not a valid IP address or DNS name.
|
||||
InvalidHost,
|
||||
/// The ID exceeds 255 bytes.
|
||||
MaximumLengthExceeded,
|
||||
/// The ID is less than 4 characters.
|
||||
MinimumLengthNotSatisfied,
|
||||
/// The ID is missing the colon delimiter between localpart and server name.
|
||||
MissingDelimiter,
|
||||
/// The ID is missing the leading sigil.
|
||||
MissingSigil,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
let message = 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",
|
||||
};
|
||||
|
||||
write!(f, "{}", message)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for Error {}
|
||||
|
||||
impl From<ParseError> for Error {
|
||||
fn from(_: ParseError) -> Self {
|
||||
Error::InvalidHost
|
||||
}
|
||||
}
|
248
src/event_id.rs
Normal file
248
src/event_id.rs
Normal file
@ -0,0 +1,248 @@
|
||||
//! Matrix event identifiers.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
#[cfg(feature = "diesel")]
|
||||
use diesel::sql_types::Text;
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Unexpected, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use url::Host;
|
||||
|
||||
use crate::{display, error::Error, generate_localpart, parse_id};
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use ruma_identifiers::EventId;
|
||||
/// assert_eq!(
|
||||
/// EventId::try_from("$h29iv0s8:example.com").unwrap().to_string(),
|
||||
/// "$h29iv0s8:example.com"
|
||||
/// );
|
||||
/// ```
|
||||
#[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 {
|
||||
/// The hostname of the homeserver.
|
||||
hostname: Host,
|
||||
/// The event's unique ID.
|
||||
opaque_id: String,
|
||||
/// The network port of the homeserver.
|
||||
port: u16,
|
||||
}
|
||||
|
||||
/// A serde visitor for `EventId`.
|
||||
struct EventIdVisitor;
|
||||
|
||||
impl EventId {
|
||||
/// Attempts to generate an `EventId` for the given origin server with a localpart consisting
|
||||
/// of 18 random ASCII characters.
|
||||
///
|
||||
/// Fails if the given origin server name cannot be parsed as a valid host.
|
||||
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(Self {
|
||||
hostname: host,
|
||||
opaque_id: opaque_id.to_string(),
|
||||
port,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a `Host` for the event ID, containing the server name (minus the port) of the
|
||||
/// originating homeserver.
|
||||
///
|
||||
/// The host can be either a domain name, an IPv4 address, or an IPv6 address.
|
||||
pub fn hostname(&self) -> &Host {
|
||||
&self.hostname
|
||||
}
|
||||
|
||||
/// Returns the event's opaque ID.
|
||||
pub fn opaque_id(&self) -> &str {
|
||||
&self.opaque_id
|
||||
}
|
||||
|
||||
/// Returns the port the originating homeserver can be accessed on.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EventId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
display(f, '$', &self.opaque_id, &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<'de> Deserialize<'de> for EventId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(EventIdVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for EventId {
|
||||
type Error = Error;
|
||||
|
||||
/// Attempts to create a new Matrix event ID from a string representation.
|
||||
///
|
||||
/// The string must include the leading $ sigil, the opaque ID, a literal colon, and a valid
|
||||
/// server name.
|
||||
fn try_from(event_id: &'a str) -> Result<Self, Self::Error> {
|
||||
let (opaque_id, host, port) = parse_id('$', event_id)?;
|
||||
|
||||
Ok(Self {
|
||||
hostname: host,
|
||||
opaque_id: opaque_id.to_owned(),
|
||||
port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use serde_json::{from_str, to_string};
|
||||
|
||||
use super::EventId;
|
||||
use crate::error::Error;
|
||||
|
||||
#[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
|
||||
);
|
||||
}
|
||||
}
|
1211
src/lib.rs
1211
src/lib.rs
File diff suppressed because it is too large
Load Diff
230
src/room_alias_id.rs
Normal file
230
src/room_alias_id.rs
Normal file
@ -0,0 +1,230 @@
|
||||
//! Matrix room alias identifiers.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
#[cfg(feature = "diesel")]
|
||||
use diesel::sql_types::Text;
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Unexpected, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use url::Host;
|
||||
|
||||
use crate::{display, error::Error, parse_id};
|
||||
|
||||
/// A Matrix room alias ID.
|
||||
///
|
||||
/// A `RoomAliasId` is converted from a string slice, and can be converted back into a string as
|
||||
/// needed.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use ruma_identifiers::RoomAliasId;
|
||||
/// assert_eq!(
|
||||
/// RoomAliasId::try_from("#ruma:example.com").unwrap().to_string(),
|
||||
/// "#ruma:example.com"
|
||||
/// );
|
||||
/// ```
|
||||
#[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 {
|
||||
/// The alias for the room.
|
||||
alias: String,
|
||||
/// The hostname of the homeserver.
|
||||
hostname: Host,
|
||||
/// The network port of the homeserver.
|
||||
port: u16,
|
||||
}
|
||||
|
||||
/// A serde visitor for `RoomAliasId`.
|
||||
struct RoomAliasIdVisitor;
|
||||
|
||||
impl RoomAliasId {
|
||||
/// Returns a `Host` for the room alias ID, containing the server name (minus the port) of
|
||||
/// the originating homeserver.
|
||||
///
|
||||
/// The host can be either a domain name, an IPv4 address, or an IPv6 address.
|
||||
pub fn hostname(&self) -> &Host {
|
||||
&self.hostname
|
||||
}
|
||||
|
||||
/// Returns the room's alias.
|
||||
pub fn alias(&self) -> &str {
|
||||
&self.alias
|
||||
}
|
||||
|
||||
/// Returns the port the originating homeserver can be accessed on.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RoomAliasId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
display(f, '#', &self.alias, &self.hostname, self.port)
|
||||
}
|
||||
}
|
||||
|
||||
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<'de> Deserialize<'de> for RoomAliasId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(RoomAliasIdVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for RoomAliasId {
|
||||
type Error = Error;
|
||||
|
||||
/// 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 valid
|
||||
/// server name.
|
||||
fn try_from(room_id: &'a str) -> Result<Self, Error> {
|
||||
let (alias, host, port) = parse_id('#', room_id)?;
|
||||
|
||||
Ok(Self {
|
||||
alias: alias.to_owned(),
|
||||
hostname: host,
|
||||
port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use serde_json::{from_str, to_string};
|
||||
|
||||
use super::RoomAliasId;
|
||||
use crate::error::Error;
|
||||
|
||||
#[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
|
||||
);
|
||||
}
|
||||
}
|
248
src/room_id.rs
Normal file
248
src/room_id.rs
Normal file
@ -0,0 +1,248 @@
|
||||
//! Matrix room identifiers.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
#[cfg(feature = "diesel")]
|
||||
use diesel::sql_types::Text;
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Unexpected, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use url::Host;
|
||||
|
||||
use crate::{display, error::Error, generate_localpart, parse_id};
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use ruma_identifiers::RoomId;
|
||||
/// assert_eq!(
|
||||
/// RoomId::try_from("!n8f893n9:example.com").unwrap().to_string(),
|
||||
/// "!n8f893n9:example.com"
|
||||
/// );
|
||||
/// ```
|
||||
#[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 {
|
||||
/// The hostname of the homeserver.
|
||||
hostname: Host,
|
||||
/// The room's unique ID.
|
||||
opaque_id: String,
|
||||
/// The network port of the homeserver.
|
||||
port: u16,
|
||||
}
|
||||
|
||||
/// A serde visitor for `RoomId`.
|
||||
struct RoomIdVisitor;
|
||||
|
||||
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 origin server name cannot be parsed as a valid host.
|
||||
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(Self {
|
||||
hostname: host,
|
||||
opaque_id: opaque_id.to_string(),
|
||||
port,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a `Host` for the room ID, containing the server name (minus the port) of the
|
||||
/// originating homeserver.
|
||||
///
|
||||
/// The host can be either a domain name, an IPv4 address, or an IPv6 address.
|
||||
pub fn hostname(&self) -> &Host {
|
||||
&self.hostname
|
||||
}
|
||||
|
||||
/// Returns the event's opaque ID.
|
||||
pub fn opaque_id(&self) -> &str {
|
||||
&self.opaque_id
|
||||
}
|
||||
|
||||
/// Returns the port the originating homeserver can be accessed on.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RoomId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
display(f, '!', &self.opaque_id, &self.hostname, self.port)
|
||||
}
|
||||
}
|
||||
|
||||
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<'de> Deserialize<'de> for RoomId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(RoomIdVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for RoomId {
|
||||
type Error = Error;
|
||||
|
||||
/// Attempts to create a new Matrix room ID from a string representation.
|
||||
///
|
||||
/// The string must include the leading ! sigil, the opaque ID, a literal colon, and a valid
|
||||
/// server name.
|
||||
fn try_from(room_id: &'a str) -> Result<Self, Error> {
|
||||
let (opaque_id, host, port) = parse_id('!', room_id)?;
|
||||
|
||||
Ok(Self {
|
||||
hostname: host,
|
||||
opaque_id: opaque_id.to_owned(),
|
||||
port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use serde_json::{from_str, to_string};
|
||||
|
||||
use super::RoomId;
|
||||
use crate::error::Error;
|
||||
|
||||
#[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
|
||||
);
|
||||
}
|
||||
}
|
219
src/room_id_or_room_alias_id.rs
Normal file
219
src/room_id_or_room_alias_id.rs
Normal file
@ -0,0 +1,219 @@
|
||||
//! Matrix identifiers for places where a room ID or room alias ID are used interchangeably.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
#[cfg(feature = "diesel")]
|
||||
use diesel::sql_types::Text;
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Unexpected, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
|
||||
use crate::{display, error::Error, room_alias_id::RoomAliasId, room_id::RoomId, validate_id};
|
||||
|
||||
/// A Matrix room ID or a Matrix room alias ID.
|
||||
///
|
||||
/// `RoomIdOrAliasId` is useful for APIs that accept either kind of room identifier. It is converted
|
||||
/// 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.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use ruma_identifiers::RoomIdOrAliasId;
|
||||
/// assert_eq!(
|
||||
/// RoomIdOrAliasId::try_from("#ruma:example.com").unwrap().to_string(),
|
||||
/// "#ruma:example.com"
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// RoomIdOrAliasId::try_from("!n8f893n9:example.com").unwrap().to_string(),
|
||||
/// "!n8f893n9:example.com"
|
||||
/// );
|
||||
/// ```
|
||||
#[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 {
|
||||
/// A Matrix room alias ID.
|
||||
RoomAliasId(RoomAliasId),
|
||||
/// A Matrix room ID.
|
||||
RoomId(RoomId),
|
||||
}
|
||||
|
||||
/// A serde visitor for `RoomIdOrAliasId`.
|
||||
struct RoomIdOrAliasIdVisitor;
|
||||
|
||||
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 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<'de> Deserialize<'de> for RoomIdOrAliasId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(RoomIdOrAliasIdVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for RoomIdOrAliasId {
|
||||
type Error = Error;
|
||||
|
||||
/// Attempts to create a new Matrix room ID or a room alias ID from a string representation.
|
||||
///
|
||||
/// The string must either
|
||||
/// include the leading ! sigil, the opaque ID, a literal colon, and a valid server name or
|
||||
/// include the leading # sigil, the alias, a literal colon, and a valid server name.
|
||||
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<'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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use serde_json::{from_str, to_string};
|
||||
|
||||
use super::RoomIdOrAliasId;
|
||||
use crate::error::Error;
|
||||
|
||||
#[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.")
|
||||
);
|
||||
}
|
||||
}
|
275
src/user_id.rs
Normal file
275
src/user_id.rs
Normal file
@ -0,0 +1,275 @@
|
||||
//! Matrix user identifiers.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
#[cfg(feature = "diesel")]
|
||||
use diesel::sql_types::Text;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Unexpected, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use url::Host;
|
||||
|
||||
use crate::{display, error::Error, generate_localpart, parse_id};
|
||||
|
||||
lazy_static! {
|
||||
static ref USER_LOCALPART_PATTERN: Regex =
|
||||
Regex::new(r"\A[a-z0-9._=-]+\z").expect("Failed to create user localpart regex.");
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use ruma_identifiers::UserId;
|
||||
/// assert_eq!(
|
||||
/// UserId::try_from("@carl:example.com").unwrap().to_string(),
|
||||
/// "@carl:example.com"
|
||||
/// );
|
||||
/// ```
|
||||
#[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 {
|
||||
/// The hostname of the homeserver.
|
||||
hostname: Host,
|
||||
/// The user's unique ID.
|
||||
localpart: String,
|
||||
/// The network port of the homeserver.
|
||||
port: u16,
|
||||
}
|
||||
|
||||
/// A serde visitor for `UserId`.
|
||||
struct UserIdVisitor;
|
||||
|
||||
impl UserId {
|
||||
/// Attempts to generate a `UserId` for the given origin server with a localpart consisting of
|
||||
/// 12 random ASCII characters.
|
||||
///
|
||||
/// Fails if the given origin server name cannot be parsed as a valid host.
|
||||
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(Self {
|
||||
hostname: host,
|
||||
localpart: localpart.to_string(),
|
||||
port,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a `Host` for the user ID, containing the server name (minus the port) of the
|
||||
/// originating homeserver.
|
||||
///
|
||||
/// The host can be either a domain name, an IPv4 address, or an IPv6 address.
|
||||
pub fn hostname(&self) -> &Host {
|
||||
&self.hostname
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
pub fn localpart(&self) -> &str {
|
||||
&self.localpart
|
||||
}
|
||||
|
||||
/// Returns the port the originating homeserver can be accessed on.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UserId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
display(f, '@', &self.localpart, &self.hostname, self.port)
|
||||
}
|
||||
}
|
||||
|
||||
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 UserId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(UserIdVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for UserId {
|
||||
type Error = Error;
|
||||
|
||||
/// 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 valid
|
||||
/// server name.
|
||||
fn try_from(user_id: &'a str) -> Result<Self, 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(Self {
|
||||
hostname: host,
|
||||
port,
|
||||
localpart: downcased_localpart.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use serde_json::{from_str, to_string};
|
||||
|
||||
use super::UserId;
|
||||
use crate::error::Error;
|
||||
|
||||
#[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
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user