Skip to main content

authnz_common/types/users/
id.rs

1//! Identication types module.
2
3#[cfg(any(feature = "app-server-types", feature = "authnz-server-types"))]
4use impulse_server_kit::salvo;
5#[cfg(any(feature = "app-server-types", feature = "authnz-server-types"))]
6use salvo::oapi::ToSchema;
7use serde::{Deserialize, Serialize};
8
9use crate::{MResult, ServerError};
10
11#[cfg_attr(any(feature = "app-server-types", feature = "authnz-server-types"), derive(ToSchema))]
12#[derive(PartialEq, Eq, Hash, Clone, Debug)]
13/// Email.
14pub struct Email(String);
15
16impl Email {
17  fn validate(email: &str) -> MResult<()> {
18    let email_regex =
19      regex::Regex::new(r"^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,10})$").map_err(|e| ServerError::from_private(e).with_500())?;
20    if email_regex.is_match(email) && !email.contains("..") && !email.starts_with('.') {
21      Ok(())
22    } else {
23      Err(ServerError::from_public("Invalid email!").with_400())
24    }
25  }
26
27  /// Constructs new email from string.
28  pub fn new(email: impl ToString) -> MResult<Self> {
29    let email = email.to_string();
30    Email::validate(&email)?;
31    Ok(Self(email))
32  }
33
34  /// Returns email domain.
35  pub fn domain(&self) -> MResult<&str> {
36    self
37      .0
38      .split('@')
39      .next_back()
40      .ok_or(ServerError::from_private_str("Can't get email domain!").with_500())
41  }
42}
43
44impl std::fmt::Display for Email {
45  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46    f.write_str(self.0.as_str())
47  }
48}
49
50impl AsRef<str> for Email {
51  fn as_ref(&self) -> &str {
52    self.0.as_str()
53  }
54}
55
56impl Serialize for Email {
57  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58  where
59    S: serde::Serializer,
60  {
61    serializer.serialize_str(&self.0)
62  }
63}
64
65impl<'de> Deserialize<'de> for Email {
66  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67  where
68    D: serde::Deserializer<'de>,
69  {
70    deserializer.deserialize_str(EmailVisitor)
71  }
72}
73
74struct EmailVisitor;
75
76impl<'de> serde::de::Visitor<'de> for EmailVisitor {
77  type Value = Email;
78
79  fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
80    formatter.write_str("a valid email address")
81  }
82
83  fn visit_str<E>(self, value: &str) -> Result<Email, E>
84  where
85    E: serde::de::Error,
86  {
87    if Email::validate(value).is_ok() {
88      Ok(Email(value.to_string()))
89    } else {
90      Err(E::custom(format!("invalid email address: {value}")))
91    }
92  }
93}
94
95#[derive(Deserialize, Serialize, PartialEq, Eq, Hash, Clone, Debug)]
96#[cfg_attr(any(feature = "app-server-types", feature = "authnz-server-types"), derive(ToSchema))]
97#[serde(rename_all = "snake_case", tag = "type")]
98#[allow(missing_docs)]
99/// User identifier.
100pub enum Id {
101  Nickname { nickname: String },
102  Email { email: Email },
103}
104
105impl std::fmt::Display for Id {
106  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107    f.write_str(self.as_str())
108  }
109}
110
111impl Id {
112  /// Nickname ID.
113  pub fn nickname(nickname: impl ToString) -> Self {
114    Self::Nickname {
115      nickname: nickname.to_string(),
116    }
117  }
118
119  /// Email ID.
120  pub fn email(email: impl ToString) -> MResult<Self> {
121    Ok(Self::Email { email: Email::new(email)? })
122  }
123
124  /// Returns the string representation of identifier.
125  pub fn as_str(&self) -> &str {
126    match &self {
127      Self::Nickname { nickname } => nickname.as_str(),
128      Self::Email { email } => email.0.as_str(),
129    }
130  }
131
132  /// Is ID an email?
133  pub fn is_email(&self) -> bool {
134    matches!(self, Id::Email { .. })
135  }
136}
137
138impl AsRef<str> for Id {
139  fn as_ref(&self) -> &str {
140    self.as_str()
141  }
142}