ruwuma/ruma-identifiers/src/room_version_id.rs
Jonas Platte c5db6d56ad
Remove RoomVersionId::custom constructor
It could be used to create invalid room versions like an empty one or
one with more than 32 code points.
2020-06-23 12:40:01 +02:00

447 lines
13 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Matrix room version identifiers.
use std::{
cmp::Ordering,
convert::TryFrom,
fmt::{self, Display, Formatter},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::error::Error;
/// Room version identifiers cannot be more than 32 code points.
const MAX_CODE_POINTS: usize = 32;
/// A Matrix room version ID.
///
/// A `RoomVersionId` can be or converted or deserialized from a string slice, and can be converted
/// or serialized back into a string as needed.
///
/// It is discouraged to use this type directly instead use one of the aliases (`RoomVersionId`
/// and `RoomVersionIdRef`) in the crate root.
///
/// ```
/// # use std::convert::TryFrom;
/// # use ruma_identifiers::RoomVersionId;
/// assert_eq!(RoomVersionId::try_from("1").unwrap().as_ref(), "1");
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RoomVersionId<T>(InnerRoomVersionId<T>);
/// Possibile values for room version, distinguishing between official Matrix versions and custom
/// versions.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum InnerRoomVersionId<T> {
/// A version 1 room.
Version1,
/// A version 2 room.
Version2,
/// A version 3 room.
Version3,
/// A version 4 room.
Version4,
/// A version 5 room.
Version5,
/// A version 6 room.
Version6,
/// A custom room version.
Custom(T),
}
impl<T> RoomVersionId<T> {
/// Creates a version 1 room ID.
pub fn version_1() -> Self {
Self(InnerRoomVersionId::Version1)
}
/// Creates a version 2 room ID.
pub fn version_2() -> Self {
Self(InnerRoomVersionId::Version2)
}
/// Creates a version 3 room ID.
pub fn version_3() -> Self {
Self(InnerRoomVersionId::Version3)
}
/// Creates a version 4 room ID.
pub fn version_4() -> Self {
Self(InnerRoomVersionId::Version4)
}
/// Creates a version 5 room ID.
pub fn version_5() -> Self {
Self(InnerRoomVersionId::Version5)
}
/// Creates a version 6 room ID.
pub fn version_6() -> Self {
Self(InnerRoomVersionId::Version6)
}
/// Whether or not this room version is an official one specified by the Matrix protocol.
pub fn is_official(&self) -> bool {
!self.is_custom()
}
/// Whether or not this is a custom room version.
pub fn is_custom(&self) -> bool {
match self.0 {
InnerRoomVersionId::Custom(_) => true,
_ => false,
}
}
/// Whether or not this is a version 1 room.
pub fn is_version_1(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version1)
}
/// Whether or not this is a version 2 room.
pub fn is_version_2(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version2)
}
/// Whether or not this is a version 3 room.
pub fn is_version_3(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version3)
}
/// Whether or not this is a version 4 room.
pub fn is_version_4(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version4)
}
/// Whether or not this is a version 5 room.
pub fn is_version_5(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version5)
}
/// Whether or not this is a version 6 room.
pub fn is_version_6(&self) -> bool {
matches!(self.0, InnerRoomVersionId::Version5)
}
}
impl From<RoomVersionId<Box<str>>> for String {
fn from(id: RoomVersionId<Box<str>>) -> Self {
match id.0 {
InnerRoomVersionId::Version1 => "1".to_owned(),
InnerRoomVersionId::Version2 => "2".to_owned(),
InnerRoomVersionId::Version3 => "3".to_owned(),
InnerRoomVersionId::Version4 => "4".to_owned(),
InnerRoomVersionId::Version5 => "5".to_owned(),
InnerRoomVersionId::Version6 => "6".to_owned(),
InnerRoomVersionId::Custom(version) => version.into(),
}
}
}
impl<T: AsRef<str>> AsRef<str> for RoomVersionId<T> {
fn as_ref(&self) -> &str {
match &self.0 {
InnerRoomVersionId::Version1 => "1",
InnerRoomVersionId::Version2 => "2",
InnerRoomVersionId::Version3 => "3",
InnerRoomVersionId::Version4 => "4",
InnerRoomVersionId::Version5 => "5",
InnerRoomVersionId::Version6 => "6",
InnerRoomVersionId::Custom(version) => version.as_ref(),
}
}
}
impl<T: AsRef<str>> Display for RoomVersionId<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl<T: PartialEq + AsRef<str>> PartialOrd for RoomVersionId<T> {
fn partial_cmp(&self, other: &RoomVersionId<T>) -> Option<Ordering> {
self.as_ref().partial_cmp(other.as_ref())
}
}
impl<T: Eq + AsRef<str>> Ord for RoomVersionId<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.as_ref().cmp(other.as_ref())
}
}
#[cfg(feature = "serde")]
impl<T: AsRef<str>> Serialize for RoomVersionId<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_ref())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for RoomVersionId<Box<str>> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
crate::deserialize_id(deserializer, "a Matrix room version ID as a string")
}
}
/// Attempts to create a new Matrix room version ID from a string representation.
fn try_from<S, T>(room_version_id: S) -> Result<RoomVersionId<T>, Error>
where
S: AsRef<str> + Into<T>,
{
let version = match room_version_id.as_ref() {
"1" => RoomVersionId(InnerRoomVersionId::Version1),
"2" => RoomVersionId(InnerRoomVersionId::Version2),
"3" => RoomVersionId(InnerRoomVersionId::Version3),
"4" => RoomVersionId(InnerRoomVersionId::Version4),
"5" => RoomVersionId(InnerRoomVersionId::Version5),
custom => {
if custom.is_empty() {
return Err(Error::MinimumLengthNotSatisfied);
} else if custom.chars().count() > MAX_CODE_POINTS {
return Err(Error::MaximumLengthExceeded);
} else {
RoomVersionId(InnerRoomVersionId::Custom(room_version_id.into()))
}
}
};
Ok(version)
}
impl<'a> TryFrom<&'a str> for RoomVersionId<&'a str> {
type Error = crate::error::Error;
fn try_from(s: &'a str) -> Result<Self, Error> {
try_from(s)
}
}
impl TryFrom<&str> for RoomVersionId<Box<str>> {
type Error = crate::error::Error;
fn try_from(s: &str) -> Result<Self, Error> {
try_from(s)
}
}
impl TryFrom<String> for RoomVersionId<Box<str>> {
type Error = crate::error::Error;
fn try_from(s: String) -> Result<Self, Error> {
try_from(s)
}
}
impl<T: AsRef<str>> PartialEq<&str> for RoomVersionId<T> {
fn eq(&self, other: &&str) -> bool {
self.as_ref() == *other
}
}
impl<T: AsRef<str>> PartialEq<RoomVersionId<T>> for &str {
fn eq(&self, other: &RoomVersionId<T>) -> bool {
*self == other.as_ref()
}
}
impl<T: AsRef<str>> PartialEq<String> for RoomVersionId<T> {
fn eq(&self, other: &String) -> bool {
self.as_ref() == other
}
}
impl<T: AsRef<str>> PartialEq<RoomVersionId<T>> for String {
fn eq(&self, other: &RoomVersionId<T>) -> bool {
self == other.as_ref()
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
#[cfg(feature = "serde")]
use serde_json::{from_str, to_string};
use crate::error::Error;
type RoomVersionId = super::RoomVersionId<Box<str>>;
#[test]
fn valid_version_1_room_version_id() {
assert_eq!(
RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.").as_ref(),
"1"
);
}
#[test]
fn valid_version_2_room_version_id() {
assert_eq!(
RoomVersionId::try_from("2").expect("Failed to create RoomVersionId.").as_ref(),
"2"
);
}
#[test]
fn valid_version_3_room_version_id() {
assert_eq!(
RoomVersionId::try_from("3").expect("Failed to create RoomVersionId.").as_ref(),
"3"
);
}
#[test]
fn valid_version_4_room_version_id() {
assert_eq!(
RoomVersionId::try_from("4").expect("Failed to create RoomVersionId.").as_ref(),
"4"
);
}
#[test]
fn valid_version_5_room_version_id() {
assert_eq!(
RoomVersionId::try_from("5").expect("Failed to create RoomVersionId.").as_ref(),
"5"
);
}
#[test]
fn valid_custom_room_version_id() {
assert_eq!(
RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.").as_ref(),
"io.ruma.1"
);
}
#[test]
fn empty_room_version_id() {
assert_eq!(RoomVersionId::try_from(""), Err(Error::MinimumLengthNotSatisfied));
}
#[test]
fn over_max_code_point_room_version_id() {
assert_eq!(
RoomVersionId::try_from("0123456789012345678901234567890123456789"),
Err(Error::MaximumLengthExceeded)
);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_official_room_id() {
assert_eq!(
to_string(&RoomVersionId::try_from("1").expect("Failed to create RoomVersionId."))
.expect("Failed to convert RoomVersionId to JSON."),
r#""1""#
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_official_room_id() {
let deserialized =
from_str::<RoomVersionId>(r#""1""#).expect("Failed to convert RoomVersionId to JSON.");
assert!(deserialized.is_version_1());
assert!(deserialized.is_official());
assert_eq!(
deserialized,
RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.")
);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_custom_room_id() {
assert_eq!(
to_string(
&RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.")
)
.expect("Failed to convert RoomVersionId to JSON."),
r#""io.ruma.1""#
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_custom_room_id() {
let deserialized = from_str::<RoomVersionId>(r#""io.ruma.1""#)
.expect("Failed to convert RoomVersionId to JSON.");
assert!(deserialized.is_custom());
assert_eq!(
deserialized,
RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.")
);
}
#[test]
fn constructors() {
assert!(RoomVersionId::version_1().is_version_1());
assert!(RoomVersionId::version_2().is_version_2());
assert!(RoomVersionId::version_3().is_version_3());
assert!(RoomVersionId::version_4().is_version_4());
assert!(RoomVersionId::version_5().is_version_5());
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn predicate_methods() {
let version_1 = RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.");
let version_2 = RoomVersionId::try_from("2").expect("Failed to create RoomVersionId.");
let version_3 = RoomVersionId::try_from("3").expect("Failed to create RoomVersionId.");
let version_4 = RoomVersionId::try_from("4").expect("Failed to create RoomVersionId.");
let version_5 = RoomVersionId::try_from("5").expect("Failed to create RoomVersionId.");
let custom = RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.");
assert!(version_1.is_version_1());
assert!(version_2.is_version_2());
assert!(version_3.is_version_3());
assert!(version_4.is_version_4());
assert!(version_5.is_version_5());
assert!(!version_1.is_version_2());
assert!(!version_1.is_version_3());
assert!(!version_1.is_version_4());
assert!(!version_1.is_version_5());
assert!(version_1.is_official());
assert!(version_2.is_official());
assert!(version_3.is_official());
assert!(version_4.is_official());
assert!(version_5.is_official());
assert!(!version_1.is_custom());
assert!(!version_2.is_custom());
assert!(!version_3.is_custom());
assert!(!version_4.is_custom());
assert!(!version_5.is_custom());
assert!(custom.is_custom());
assert!(!custom.is_official());
assert!(!custom.is_version_1());
assert!(!custom.is_version_2());
assert!(!custom.is_version_3());
assert!(!custom.is_version_4());
assert!(!custom.is_version_5());
}
}