reinhardt_forms/fields/
email_field.rs1use crate::field::{FieldError, FieldResult, FormField, Widget};
4use regex::Regex;
5
6#[derive(Debug, Clone)]
8pub struct EmailField {
9 pub name: String,
10 pub label: Option<String>,
11 pub required: bool,
12 pub help_text: Option<String>,
13 pub widget: Widget,
14 pub initial: Option<serde_json::Value>,
15 pub max_length: Option<usize>,
16 pub min_length: Option<usize>,
17}
18
19impl EmailField {
20 pub fn new(name: String) -> Self {
33 Self {
34 name,
35 label: None,
36 required: false,
37 help_text: None,
38 widget: Widget::EmailInput,
39 initial: None,
40 max_length: Some(320), min_length: None,
42 }
43 }
44
45 pub fn required(mut self) -> Self {
56 self.required = true;
57 self
58 }
59
60 pub fn with_max_length(mut self, max_length: usize) -> Self {
71 self.max_length = Some(max_length);
72 self
73 }
74
75 pub fn with_min_length(mut self, min_length: usize) -> Self {
86 self.min_length = Some(min_length);
87 self
88 }
89
90 pub fn with_label(mut self, label: impl Into<String>) -> Self {
101 self.label = Some(label.into());
102 self
103 }
104
105 pub fn with_help_text(mut self, help_text: impl Into<String>) -> Self {
116 self.help_text = Some(help_text.into());
117 self
118 }
119
120 pub fn with_initial(mut self, initial: impl Into<String>) -> Self {
131 self.initial = Some(serde_json::json!(initial.into()));
132 self
133 }
134
135 fn validate_email(email: &str) -> bool {
137 let email_regex = Regex::new(
140 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])?)*$"
141 ).unwrap();
142
143 email_regex.is_match(email)
144 }
145}
146
147impl FormField for EmailField {
150 fn name(&self) -> &str {
151 &self.name
152 }
153
154 fn label(&self) -> Option<&str> {
155 self.label.as_deref()
156 }
157
158 fn required(&self) -> bool {
159 self.required
160 }
161
162 fn help_text(&self) -> Option<&str> {
163 self.help_text.as_deref()
164 }
165
166 fn widget(&self) -> &Widget {
167 &self.widget
168 }
169
170 fn initial(&self) -> Option<&serde_json::Value> {
171 self.initial.as_ref()
172 }
173
174 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
175 match value {
176 None if self.required => Err(FieldError::Required(self.name.clone())),
177 None => Ok(serde_json::Value::String(String::new())),
178 Some(v) => {
179 let s = v
180 .as_str()
181 .ok_or_else(|| FieldError::Validation("Expected string".to_string()))?;
182
183 let s = s.trim();
185
186 if s.is_empty() {
188 if self.required {
189 return Err(FieldError::Required(self.name.clone()));
190 }
191 return Ok(serde_json::Value::String(String::new()));
192 }
193
194 let char_count = s.chars().count();
197 if let Some(max) = self.max_length
198 && char_count > max
199 {
200 return Err(FieldError::Validation(format!(
201 "Ensure this value has at most {} characters (it has {})",
202 max, char_count
203 )));
204 }
205
206 if let Some(min) = self.min_length
207 && char_count < min
208 {
209 return Err(FieldError::Validation(format!(
210 "Ensure this value has at least {} characters (it has {})",
211 min, char_count
212 )));
213 }
214
215 if !Self::validate_email(s) {
217 return Err(FieldError::Validation(
218 "Enter a valid email address".to_string(),
219 ));
220 }
221
222 Ok(serde_json::Value::String(s.to_string()))
223 }
224 }
225 }
226}