1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#![warn(rust_2018_idioms)]
#![deny(
missing_copy_implementations,
missing_debug_implementations,
missing_docs
)]
#![allow(clippy::use_self)]
#[cfg(feature = "diesel")]
#[cfg_attr(feature = "diesel", macro_use)]
extern crate diesel;
use std::{
convert::TryFrom,
fmt::{Formatter, Result as FmtResult},
};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use serde::de::{self, Deserialize as _, Deserializer, Unexpected};
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;
const MAX_BYTES: usize = 255;
const MIN_CHARS: usize = 4;
const SIGIL_BYTES: usize = 1;
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(id: &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(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))
}
fn deserialize_id<'de, D, T>(deserializer: D, expected_str: &str) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: for<'a> TryFrom<&'a str>,
{
String::deserialize(deserializer).and_then(|v| {
T::try_from(&v).map_err(|_| de::Error::invalid_value(Unexpected::Str(&v), &expected_str))
})
}