use std::fmt::Debug;
use std::str::FromStr;
#[cfg(feature = "mysql")]
use cot::db::impl_mysql::MySqlValueRef;
#[cfg(feature = "postgres")]
use cot::db::impl_postgres::PostgresValueRef;
#[cfg(feature = "sqlite")]
use cot::db::impl_sqlite::SqliteValueRef;
use email_address::EmailAddress;
#[cfg(feature = "db")]
use crate::db::{ColumnType, DatabaseField, DbValue, FromDbValue, SqlxValueRef, ToDbValue};
const MAX_EMAIL_LENGTH: u32 = 254;
#[derive(Clone)]
pub struct Password(String);
impl Debug for Password {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Password").field(&"**********").finish()
}
}
impl Password {
#[must_use]
pub fn new<T: Into<String>>(password: T) -> Self {
Self(password.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl From<&Password> for Password {
fn from(password: &Password) -> Self {
password.clone()
}
}
impl From<&str> for Password {
fn from(password: &str) -> Self {
Self::new(password)
}
}
impl From<String> for Password {
fn from(password: String) -> Self {
Self::new(password)
}
}
#[derive(Clone, Debug)]
pub struct Email(EmailAddress);
impl Email {
pub fn new<S: AsRef<str>>(email: S) -> Result<Email, email_address::Error> {
EmailAddress::from_str(email.as_ref()).map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[must_use]
pub fn domain(&self) -> &str {
self.0.domain()
}
#[must_use]
pub fn to_uri(&self) -> String {
self.0.to_uri()
}
#[must_use]
pub fn to_display(&self, display_name: &str) -> String {
self.0.to_display(display_name)
}
#[must_use]
pub fn email(&self) -> String {
self.0.email()
}
#[must_use]
pub fn local_part(&self) -> &str {
self.0.local_part()
}
#[must_use]
pub fn display_part(&self) -> &str {
self.0.display_part()
}
}
impl FromStr for Email {
type Err = email_address::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Email::new(s)
}
}
impl TryFrom<&str> for Email {
type Error = email_address::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Email::new(value)
}
}
#[cfg(feature = "db")]
impl TryFrom<String> for Email {
type Error = email_address::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Email::new(value)
}
}
#[cfg(feature = "db")]
impl ToDbValue for Email {
fn to_db_value(&self) -> DbValue {
self.0.clone().email().into()
}
}
#[cfg(feature = "db")]
impl FromDbValue for Email {
#[cfg(feature = "sqlite")]
fn from_sqlite(value: SqliteValueRef<'_>) -> cot::db::Result<Self>
where
Self: Sized,
{
Email::new(value.get::<String>()?).map_err(cot::db::DatabaseError::value_decode)
}
#[cfg(feature = "postgres")]
fn from_postgres(value: PostgresValueRef<'_>) -> cot::db::Result<Self>
where
Self: Sized,
{
Email::new(value.get::<String>()?).map_err(cot::db::DatabaseError::value_decode)
}
#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> cot::db::Result<Self>
where
Self: Sized,
{
Email::new(value.get::<String>()?).map_err(cot::db::DatabaseError::value_decode)
}
}
#[cfg(feature = "db")]
impl DatabaseField for Email {
const TYPE: ColumnType = ColumnType::String(MAX_EMAIL_LENGTH);
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use super::*;
#[test]
fn password_debug() {
let password = Password::new("password");
assert_eq!(format!("{password:?}"), "Password(\"**********\")");
}
#[test]
fn password_str() {
let password = Password::new("password");
assert_eq!(password.as_str(), "password");
assert_eq!(password.into_string(), "password");
}
#[test]
fn test_valid_email_creation() {
let email = Email::new("user@example.com").unwrap();
assert_eq!(email.as_str(), "user@example.com");
assert_eq!(email.domain(), "example.com");
}
#[test]
fn test_invalid_email_creation() {
let result = Email::new("invalid");
assert!(result.is_err());
}
#[test]
fn test_from_str_trait() {
let email: Email = "user@example.com".parse().unwrap();
assert_eq!(email.as_str(), "user@example.com");
}
#[test]
fn test_try_from_trait() {
let email = Email::try_from("user@example.com").unwrap();
assert_eq!(email.as_str(), "user@example.com");
}
#[cfg(feature = "db")]
mod db_tests {
use super::*;
use crate::db::ToDbValue;
#[test]
fn test_to_db_value() {
let email = Email::new("user@example.com").unwrap();
let db_value = email.to_db_value();
let email_str = email.as_str();
let db_value_str = format!("{db_value:?}");
assert!(db_value_str.contains(email_str));
}
#[test]
fn test_to_db_value_is_normalized() {
let with_display = Email::new("John Doe <user@example.com>").unwrap();
let bare = Email::new("user@example.com").unwrap();
let db1 = with_display.to_db_value();
let db2 = bare.to_db_value();
assert_eq!(db1, db2);
}
}
}