use crate::field::{FieldError, FieldResult, FormField, Widget};
use regex::Regex;
use std::sync::LazyLock;
const EMAIL_PATTERN: &str = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
static EMAIL_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(EMAIL_PATTERN).expect("Email regex pattern is valid"));
#[derive(Debug, Clone)]
pub struct EmailField {
pub name: String,
pub label: Option<String>,
pub required: bool,
pub help_text: Option<String>,
pub widget: Widget,
pub initial: Option<serde_json::Value>,
pub max_length: Option<usize>,
pub min_length: Option<usize>,
}
impl EmailField {
pub fn new(name: String) -> Self {
Self {
name,
label: None,
required: false,
help_text: None,
widget: Widget::EmailInput,
initial: None,
max_length: Some(320), min_length: None,
}
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn with_max_length(mut self, max_length: usize) -> Self {
self.max_length = Some(max_length);
self
}
pub fn with_min_length(mut self, min_length: usize) -> Self {
self.min_length = Some(min_length);
self
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn with_help_text(mut self, help_text: impl Into<String>) -> Self {
self.help_text = Some(help_text.into());
self
}
pub fn with_initial(mut self, initial: impl Into<String>) -> Self {
self.initial = Some(serde_json::json!(initial.into()));
self
}
fn validate_email(email: &str) -> bool {
EMAIL_REGEX.is_match(email)
}
}
impl FormField for EmailField {
fn name(&self) -> &str {
&self.name
}
fn label(&self) -> Option<&str> {
self.label.as_deref()
}
fn required(&self) -> bool {
self.required
}
fn help_text(&self) -> Option<&str> {
self.help_text.as_deref()
}
fn widget(&self) -> &Widget {
&self.widget
}
fn initial(&self) -> Option<&serde_json::Value> {
self.initial.as_ref()
}
fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
match value {
None if self.required => Err(FieldError::Required(self.name.clone())),
None => Ok(serde_json::Value::String(String::new())),
Some(v) => {
let s = v
.as_str()
.ok_or_else(|| FieldError::Validation("Expected string".to_string()))?;
let s = s.trim();
if s.is_empty() {
if self.required {
return Err(FieldError::Required(self.name.clone()));
}
return Ok(serde_json::Value::String(String::new()));
}
let char_count = s.chars().count();
if let Some(max) = self.max_length
&& char_count > max
{
return Err(FieldError::Validation(format!(
"Ensure this value has at most {} characters (it has {})",
max, char_count
)));
}
if let Some(min) = self.min_length
&& char_count < min
{
return Err(FieldError::Validation(format!(
"Ensure this value has at least {} characters (it has {})",
min, char_count
)));
}
if !Self::validate_email(s) {
return Err(FieldError::Validation(
"Enter a valid email address".to_string(),
));
}
Ok(serde_json::Value::String(s.to_string()))
}
}
}
}