use unicode_segmentation::UnicodeSegmentation;
use validator::ValidateEmail;
#[derive(serde::Deserialize)]
pub struct ConfirmParameters {
pub subscription_token: String,
}
pub struct ConfirmedSubscriber {
pub email: SubscriberEmail,
}
pub struct NewsletterIssue {
pub title: String,
pub html_content: String,
}
#[derive(Debug)]
pub struct SubscriberName(String);
#[derive(Debug)]
pub struct SubscriberEmail(String);
#[derive(serde::Deserialize)]
pub struct SubscribeFormData {
pub email: String,
pub name: String,
}
#[derive(serde::Deserialize, validator::Validate)]
pub struct NewsletterFormData {
#[validate(length(min = 2))]
pub title: String,
#[validate(length(min = 2))]
pub html_content: String,
#[validate(length(min = 22))]
pub idempotency_key: String,
}
#[derive(Debug)]
pub struct NewSubscriber {
pub email: SubscriberEmail,
pub name: SubscriberName,
}
impl TryFrom<SubscribeFormData> for NewSubscriber {
type Error = String;
fn try_from(value: SubscribeFormData) -> Result<Self, Self::Error> {
let name = SubscriberName::parse(value.name)?;
let email = SubscriberEmail::parse(value.email)?;
Ok(Self { email, name })
}
}
impl SubscriberEmail {
pub fn parse(s: String) -> Result<SubscriberEmail, String> {
if s.validate_email() {
Ok(Self(s))
} else {
Err(format!("{} is not a valid email.", s))
}
}
}
impl AsRef<str> for SubscriberEmail {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for SubscriberEmail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl SubscriberName {
pub fn parse(s: String) -> Result<SubscriberName, String> {
let is_empty_or_whitespace = s.trim().is_empty();
let is_too_long = s.graphemes(true).count() > 256;
let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}'];
let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
if is_empty_or_whitespace || is_too_long || contains_forbidden_characters {
Err(format!("{} is not a valid subscriber name.", s))
} else {
Ok(Self(s))
}
}
}
impl AsRef<str> for SubscriberName {
fn as_ref(&self) -> &str {
&self.0
}
}