#[cfg(any(feature = "app-server-types", feature = "authnz-server-types"))]
use impulse_server_kit::salvo;
#[cfg(any(feature = "app-server-types", feature = "authnz-server-types"))]
use salvo::oapi::ToSchema;
use serde::{Deserialize, Serialize};
use crate::{MResult, ServerError};
#[cfg_attr(any(feature = "app-server-types", feature = "authnz-server-types"), derive(ToSchema))]
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Email(String);
impl Email {
fn validate(email: &str) -> MResult<()> {
let email_regex =
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())?;
if email_regex.is_match(email) && !email.contains("..") && !email.starts_with('.') {
Ok(())
} else {
Err(ServerError::from_public("Invalid email!").with_400())
}
}
pub fn new(email: impl ToString) -> MResult<Self> {
let email = email.to_string();
Email::validate(&email)?;
Ok(Self(email))
}
pub fn domain(&self) -> MResult<&str> {
self
.0
.split('@')
.next_back()
.ok_or(ServerError::from_private_str("Can't get email domain!").with_500())
}
}
impl std::fmt::Display for Email {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.as_str())
}
}
impl AsRef<str> for Email {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl Serialize for Email {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for Email {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(EmailVisitor)
}
}
struct EmailVisitor;
impl<'de> serde::de::Visitor<'de> for EmailVisitor {
type Value = Email;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid email address")
}
fn visit_str<E>(self, value: &str) -> Result<Email, E>
where
E: serde::de::Error,
{
if Email::validate(value).is_ok() {
Ok(Email(value.to_string()))
} else {
Err(E::custom(format!("invalid email address: {value}")))
}
}
}
#[derive(Deserialize, Serialize, PartialEq, Eq, Hash, Clone, Debug)]
#[cfg_attr(any(feature = "app-server-types", feature = "authnz-server-types"), derive(ToSchema))]
#[serde(rename_all = "snake_case", tag = "type")]
#[allow(missing_docs)]
pub enum Id {
Nickname { nickname: String },
Email { email: Email },
}
impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Id {
pub fn nickname(nickname: impl ToString) -> Self {
Self::Nickname {
nickname: nickname.to_string(),
}
}
pub fn email(email: impl ToString) -> MResult<Self> {
Ok(Self::Email { email: Email::new(email)? })
}
pub fn as_str(&self) -> &str {
match &self {
Self::Nickname { nickname } => nickname.as_str(),
Self::Email { email } => email.0.as_str(),
}
}
pub fn is_email(&self) -> bool {
matches!(self, Id::Email { .. })
}
}
impl AsRef<str> for Id {
fn as_ref(&self) -> &str {
self.as_str()
}
}