ruwuma/src/lib.rs
2019-06-03 12:13:11 -07:00

132 lines
3.5 KiB
Rust

//! Crate **ruma_identifiers** contains types for [Matrix](https://matrix.org/) identifiers
//! for events, rooms, room aliases, room versions, and users.
#![deny(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
warnings
)]
#![warn(
clippy::empty_line_after_outer_attr,
clippy::expl_impl_clone_on_copy,
clippy::if_not_else,
clippy::items_after_statements,
clippy::match_same_arms,
clippy::mem_forget,
clippy::missing_docs_in_private_items,
clippy::multiple_inherent_impl,
clippy::mut_mut,
clippy::needless_borrow,
clippy::needless_continue,
clippy::single_match_else,
clippy::unicode_not_nfc,
clippy::use_self,
clippy::used_underscore_binding,
clippy::wrong_pub_self_convention,
clippy::wrong_self_convention
)]
#[cfg(feature = "diesel")]
#[cfg_attr(feature = "diesel", macro_use)]
extern crate diesel;
use std::fmt::{Formatter, Result as FmtResult};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use url::Url;
pub use url::Host;
#[doc(inline)]
pub use crate::device_id::DeviceId;
pub use crate::{
error::Error, event_id::EventId, room_alias_id::RoomAliasId, room_id::RoomId,
room_id_or_room_alias_id::RoomIdOrAliasId, room_version_id::RoomVersionId, user_id::UserId,
};
pub mod device_id;
#[cfg(feature = "diesel")]
mod diesel_integration;
mod error;
mod event_id;
mod room_alias_id;
mod room_id;
mod room_id_or_room_alias_id;
mod room_version_id;
mod user_id;
/// All identifiers must be 255 bytes or less.
const MAX_BYTES: usize = 255;
/// The minimum number of characters an ID can be.
///
/// This is an optimization and not required by the spec. The shortest possible valid ID is a sigil
/// + a single character local ID + a colon + a single character hostname.
const MIN_CHARS: usize = 4;
/// The number of bytes in a valid sigil.
const SIGIL_BYTES: usize = 1;
/// `Display` implementation shared by identifier types.
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)
}
}
/// Generates a random identifier localpart.
fn generate_localpart(length: usize) -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.collect()
}
/// Checks if an identifier is within the acceptable byte lengths.
fn validate_id(id: &str) -> Result<(), Error> {
if id.len() > MAX_BYTES {
return Err(Error::MaximumLengthExceeded);
}
if id.len() < MIN_CHARS {
return Err(Error::MinimumLengthNotSatisfied);
}
Ok(())
}
/// Parses the localpart, host, and port from a string identifier.
fn parse_id(required_sigil: char, id: &str) -> Result<(&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))
}