reinhardt_forms/fields/
email_field.rs1use crate::field::{FieldError, FieldResult, FormField, Widget};
4use regex::Regex;
5use std::sync::LazyLock;
6
7const 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])?)*$";
9
10static EMAIL_REGEX: LazyLock<Regex> =
12 LazyLock::new(|| Regex::new(EMAIL_PATTERN).expect("Email regex pattern is valid"));
13
14#[derive(Debug, Clone)]
16pub struct EmailField {
17 pub name: String,
19 pub label: Option<String>,
21 pub required: bool,
23 pub help_text: Option<String>,
25 pub widget: Widget,
27 pub initial: Option<serde_json::Value>,
29 pub max_length: Option<usize>,
31 pub min_length: Option<usize>,
33}
34
35impl EmailField {
36 pub fn new(name: String) -> Self {
49 Self {
50 name,
51 label: None,
52 required: false,
53 help_text: None,
54 widget: Widget::EmailInput,
55 initial: None,
56 max_length: Some(320), min_length: None,
58 }
59 }
60
61 pub fn required(mut self) -> Self {
72 self.required = true;
73 self
74 }
75
76 pub fn with_max_length(mut self, max_length: usize) -> Self {
87 self.max_length = Some(max_length);
88 self
89 }
90
91 pub fn with_min_length(mut self, min_length: usize) -> Self {
102 self.min_length = Some(min_length);
103 self
104 }
105
106 pub fn with_label(mut self, label: impl Into<String>) -> Self {
117 self.label = Some(label.into());
118 self
119 }
120
121 pub fn with_help_text(mut self, help_text: impl Into<String>) -> Self {
132 self.help_text = Some(help_text.into());
133 self
134 }
135
136 pub fn with_initial(mut self, initial: impl Into<String>) -> Self {
147 self.initial = Some(serde_json::json!(initial.into()));
148 self
149 }
150
151 fn validate_email(email: &str) -> bool {
153 EMAIL_REGEX.is_match(email)
154 }
155}
156
157impl FormField for EmailField {
160 fn name(&self) -> &str {
161 &self.name
162 }
163
164 fn label(&self) -> Option<&str> {
165 self.label.as_deref()
166 }
167
168 fn required(&self) -> bool {
169 self.required
170 }
171
172 fn help_text(&self) -> Option<&str> {
173 self.help_text.as_deref()
174 }
175
176 fn widget(&self) -> &Widget {
177 &self.widget
178 }
179
180 fn initial(&self) -> Option<&serde_json::Value> {
181 self.initial.as_ref()
182 }
183
184 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
185 match value {
186 None if self.required => Err(FieldError::Required(self.name.clone())),
187 None => Ok(serde_json::Value::String(String::new())),
188 Some(v) => {
189 let s = v
190 .as_str()
191 .ok_or_else(|| FieldError::Validation("Expected string".to_string()))?;
192
193 let s = s.trim();
195
196 if s.is_empty() {
198 if self.required {
199 return Err(FieldError::Required(self.name.clone()));
200 }
201 return Ok(serde_json::Value::String(String::new()));
202 }
203
204 let char_count = s.chars().count();
207 if let Some(max) = self.max_length
208 && char_count > max
209 {
210 return Err(FieldError::Validation(format!(
211 "Ensure this value has at most {} characters (it has {})",
212 max, char_count
213 )));
214 }
215
216 if let Some(min) = self.min_length
217 && char_count < min
218 {
219 return Err(FieldError::Validation(format!(
220 "Ensure this value has at least {} characters (it has {})",
221 min, char_count
222 )));
223 }
224
225 if !Self::validate_email(s) {
227 return Err(FieldError::Validation(
228 "Enter a valid email address".to_string(),
229 ));
230 }
231
232 Ok(serde_json::Value::String(s.to_string()))
233 }
234 }
235 }
236}