ruma-identifiers

This commit is contained in:
Jimmy Cuadra 2016-07-17 22:10:17 -07:00
commit 9ad1e0fc69
6 changed files with 348 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

130
Cargo.lock generated Normal file
View File

@ -0,0 +1,130 @@
[root]
name = "ruma-identifiers"
version = "0.1.0"
dependencies = [
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "aho-corasick"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "idna"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "matches"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "thread-id"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-bidi"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-normalization"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "url"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf8-ranges"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
authors = ["Jimmy Cuadra <jimmy@jimmycuadra.com>"]
description = "Opaque identifiers for Matrix."
documentation = "http://ruma.github.io/ruma-identifiers/ruma_identifiers"
homepage = "https://github.com/ruma/ruma-identifiers"
keywords = ["matrix", "matrix.org", "chat", "messaging", "ruma"]
license = "MIT"
name = "ruma-identifiers"
readme = "README.md"
repository = "https://github.com/ruma/ruma-identifiers"
version = "0.1.0"
[dependencies]
lazy_static = "0.2.1"
regex = "0.1.73"
url = "1.1.1"

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2016 Jimmy Cuadra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# ruma-identifiers
**ruma-identifiers** contains types for [Matrix](https://matrix.org/) opaque identifiers, such as user IDs, room IDs, and room aliases.
## License
[MIT](http://opensource.org/licenses/MIT)

174
src/lib.rs Normal file
View File

@ -0,0 +1,174 @@
//! Crate **ruma_identifiers** contains types for [Matrix](https://matrix.org/) opaque identifiers,
//! such as user IDs, room IDs, and room aliases.
#![deny(missing_docs)]
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate url;
use std::fmt::{Display, Formatter, Result as FmtResult};
use regex::Regex;
use url::{Host, ParseError, Url};
lazy_static! {
static ref USER_ID_PATTERN: Regex =
Regex::new(r"\A@(?P<localpart>[a-z0-9._=-]+):(?P<host>.+)\z")
.expect("Failed to compile user ID regex.");
}
/// An error encountered when trying to parse an invalid user ID string.
#[derive(Debug)]
pub enum Error {
/// The user ID string did not match the "@<localpart>:<domain>" format, or used invalid
/// characters in its localpart.
InvalidFormat,
/// The domain part of the user ID string was not a valid IP address or DNS name.
InvalidHost(ParseError),
}
/// A Matrix user ID.
///
/// A `UserId` is created from a string slice, and can be converted back into a string as needed:
///
/// ```
/// # use ruma_identifiers::UserId;
/// assert_eq!(UserId::new("@carl:example.com").unwrap().to_string(), "@carl:example.com");
/// ```
#[derive(Debug)]
pub struct UserId {
hostname: Host,
localpart: String,
port: u16,
}
impl UserId {
/// 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.
pub fn new(user_id: &str) -> Result<UserId, Error> {
let captures = match USER_ID_PATTERN.captures(user_id) {
Some(captures) => captures,
None => return Err(Error::InvalidFormat),
};
let raw_host = captures.name("host").expect("Failed to extract hostname from regex.");
let url_string = format!("https://{}", raw_host);
let url = try!(Url::parse(&url_string));
let host = match url.host() {
Some(host) => host,
None => return Err(Error::InvalidFormat),
};
let port = url.port().unwrap_or(443);
Ok(UserId {
hostname: host.to_owned(),
port: port,
localpart: captures
.name("localpart")
.expect("Failed to extract localpart from regex.")
.to_string(),
})
}
/// Returns a `url::Host` for the user ID, containing the server name (minus the port) of the
/// user's homeserver.
///
/// This 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 user's homeserver can be accessed on.
pub fn port(&self) -> u16 {
self.port
}
}
impl From<ParseError> for Error {
fn from(error: ParseError) -> Error {
Error::InvalidHost(error)
}
}
impl Display for UserId {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
if self.port == 443 {
write!(f, "@{}:{}", self.localpart, self.hostname)
} else {
write!(f, "@{}:{}:{}", self.localpart, self.hostname, self.port)
}
}
}
#[cfg(test)]
mod tests {
use super::UserId;
#[test]
fn valid_user_id() {
assert_eq!(
UserId::new("@carl:example.com")
.expect("Failed to create UserId.")
.to_string(),
"@carl:example.com"
);
}
#[test]
fn valid_user_id_with_explicit_standard_port() {
assert_eq!(
UserId::new("@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::new("@carl:example.com:5000")
.expect("Failed to create UserId.")
.to_string(),
"@carl:example.com:5000"
);
}
#[test]
fn invalid_characters_in_localpart() {
assert!(UserId::new("@CARL:example.com").is_err());
}
#[test]
fn missing_sigil() {
assert!(UserId::new("carl:example.com").is_err());
}
#[test]
fn missing_domain() {
assert!(UserId::new("carl").is_err());
}
#[test]
fn invalid_host() {
assert!(UserId::new("@carl:-").is_err());
}
#[test]
fn invalid_port() {
assert!(UserId::new("@carl:example.com:notaport").is_err());
}
}