use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{self, Visitor},
};
use super::{IdentityError as Err, InboxId, canonical_identity_string, is_valid_identity_string};
use crate::prelude::Address;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UserId {
inbox: Option<InboxId>,
id: String,
}
impl From<Address> for UserId {
fn from(address: Address) -> Self {
address.user().clone()
}
}
impl TryFrom<&str> for UserId {
type Error = Err;
fn try_from(value: &str) -> Result<Self, Err> {
UserId::parse(value)
}
}
impl TryFrom<String> for UserId {
type Error = Err;
fn try_from(value: String) -> Result<Self, Err> {
UserId::parse(value)
}
}
impl UserId {
pub fn parse(input: impl AsRef<str>) -> Result<Self, Err> {
let input = input.as_ref();
if let Some((inbox_str, user_str)) = input.split_once('#')
&& !inbox_str.is_empty()
{
let inbox = InboxId::parse(inbox_str)?;
let id = Self::parse_user_id(user_str)?;
Ok(UserId {
inbox: Some(inbox),
id,
})
} else {
let id = Self::parse_user_id(input.trim_start_matches("#"))?;
Ok(UserId { inbox: None, id })
}
}
fn parse_user_id(input: &str) -> Result<String, Err> {
if input.is_empty() {
return Err(Err::InvalidUser);
}
if !is_valid_identity_string(input) {
return Err(Err::InvalidIdentityString);
}
Ok(canonical_identity_string(input))
}
pub fn replace(&mut self, new_value: impl AsRef<str>) -> Result<(), Err> {
*self = Self::parse(new_value)?;
Ok(())
}
pub fn inbox(&self) -> Option<&InboxId> {
self.inbox.as_ref()
}
pub fn canonical(&self) -> String {
if let Some(inbox) = &self.inbox {
format!("{}#{}", inbox.canonical(), self.id)
} else {
format!("#{}", self.id)
}
}
}
impl std::fmt::Display for UserId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.canonical())
}
}
impl Serialize for UserId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.canonical())
}
}
impl<'de> Deserialize<'de> for UserId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct UserIdVisitor;
impl<'de> Visitor<'de> for UserIdVisitor {
type Value = UserId;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a canonical Relay Mail address string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
UserId::parse(value)
.map_err(|e| de::Error::custom(format!("invalid address `{}`: {}", value, e)))
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_str(&value)
}
}
deserializer.deserialize_str(UserIdVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_and_canonicalizes() {
assert_eq!(
UserId::parse("Alice.Smith").unwrap().canonical(),
"#alice.smith"
);
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidUser")]
fn rejects_empty() {
let _ = UserId::parse("").unwrap();
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
fn rejects_invalid_chars() {
let _ = UserId::parse("Invalid User!").unwrap();
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
fn reject_untrimmed() {
let _ = UserId::parse(" trim_me ").unwrap();
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
fn rejects_non_ascii() {
let _ = UserId::parse("调试输出").unwrap();
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
fn rejects_homoglyphs() {
let _ = UserId::parse("аlice").unwrap(); }
}