authnz_common/types/users/
id.rs1#[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)]
13pub 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 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 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)]
99pub 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 pub fn nickname(nickname: impl ToString) -> Self {
114 Self::Nickname {
115 nickname: nickname.to_string(),
116 }
117 }
118
119 pub fn email(email: impl ToString) -> MResult<Self> {
121 Ok(Self::Email { email: Email::new(email)? })
122 }
123
124 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 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}