reinhardt_forms/bound_field.rs
1use crate::field::{FormField, Widget};
2
3/// BoundField represents a field bound to form data
4pub struct BoundField<'a> {
5 // Allow dead_code: field stored for form-level operations and rendering context
6 #[allow(dead_code)]
7 form_name: String,
8 field: &'a dyn FormField,
9 data: Option<&'a serde_json::Value>,
10 errors: &'a [String],
11 prefix: &'a str,
12}
13
14impl<'a> BoundField<'a> {
15 /// Documentation for `new`
16 ///
17 /// # Examples
18 ///
19 /// ```
20 /// use reinhardt_forms::{BoundField, CharField, FormField};
21 ///
22 /// let field: Box<dyn FormField> = Box::new(CharField::new("name".to_string()));
23 /// let data = serde_json::json!("John");
24 /// let errors = vec![];
25 ///
26 /// let bound = BoundField::new("my_form".to_string(), field.as_ref(), Some(&data), &errors, "");
27 /// assert_eq!(bound.name(), "name");
28 /// assert_eq!(bound.value(), Some(&data));
29 /// ```
30 pub fn new(
31 form_name: String,
32 field: &'a dyn FormField,
33 data: Option<&'a serde_json::Value>,
34 errors: &'a [String],
35 prefix: &'a str,
36 ) -> Self {
37 Self {
38 form_name,
39 field,
40 data,
41 errors,
42 prefix,
43 }
44 }
45 /// Get the field name
46 ///
47 /// # Examples
48 ///
49 /// ```
50 /// use reinhardt_forms::{BoundField, CharField, FormField};
51 ///
52 /// let field: Box<dyn FormField> = Box::new(CharField::new("email".to_string()));
53 /// let bound = BoundField::new("form".to_string(), field.as_ref(), None, &[], "");
54 /// assert_eq!(bound.name(), "email");
55 /// ```
56 pub fn name(&self) -> &str {
57 self.field.name()
58 }
59 /// Get the HTML name attribute (with prefix)
60 ///
61 /// # Examples
62 ///
63 /// ```
64 /// use reinhardt_forms::{BoundField, CharField, FormField};
65 ///
66 /// let field: Box<dyn FormField> = Box::new(CharField::new("email".to_string()));
67 ///
68 /// // Without prefix
69 /// let bound = BoundField::new("form".to_string(), field.as_ref(), None, &[], "");
70 /// assert_eq!(bound.html_name(), "email");
71 ///
72 /// // With prefix
73 /// let bound_prefixed = BoundField::new("form".to_string(), field.as_ref(), None, &[], "user");
74 /// assert_eq!(bound_prefixed.html_name(), "user-email");
75 /// ```
76 pub fn html_name(&self) -> String {
77 if self.prefix.is_empty() {
78 self.field.name().to_string()
79 } else {
80 format!("{}-{}", self.prefix, self.field.name())
81 }
82 }
83 /// Get the HTML id attribute
84 ///
85 /// # Examples
86 ///
87 /// ```
88 /// use reinhardt_forms::{BoundField, CharField, FormField};
89 ///
90 /// let field: Box<dyn FormField> = Box::new(CharField::new("username".to_string()));
91 /// let bound = BoundField::new("form".to_string(), field.as_ref(), None, &[], "profile");
92 ///
93 /// assert_eq!(bound.id_for_label(), "id_profile-username");
94 /// ```
95 pub fn id_for_label(&self) -> String {
96 format!("id_{}", self.html_name())
97 }
98 /// Get the field label
99 ///
100 /// # Examples
101 ///
102 /// ```
103 /// use reinhardt_forms::{BoundField, CharField, FormField};
104 ///
105 /// let mut field = CharField::new("name".to_string());
106 /// field.label = Some("Full Name".to_string());
107 /// let field_box: Box<dyn FormField> = Box::new(field);
108 ///
109 /// let bound = BoundField::new("form".to_string(), field_box.as_ref(), None, &[], "");
110 /// assert_eq!(bound.label(), Some("Full Name"));
111 /// ```
112 pub fn label(&self) -> Option<&str> {
113 self.field.label()
114 }
115 /// Get the field value
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// use reinhardt_forms::{BoundField, CharField, FormField};
121 ///
122 /// let field: Box<dyn FormField> = Box::new(CharField::new("name".to_string()));
123 /// let data = serde_json::json!("Alice");
124 ///
125 /// let bound = BoundField::new("form".to_string(), field.as_ref(), Some(&data), &[], "");
126 /// assert_eq!(bound.value(), Some(&data));
127 /// ```
128 pub fn value(&self) -> Option<&serde_json::Value> {
129 self.data.or_else(|| self.field.initial())
130 }
131 /// Get field errors
132 ///
133 /// # Examples
134 ///
135 /// ```
136 /// use reinhardt_forms::{BoundField, CharField, FormField};
137 ///
138 /// let field: Box<dyn FormField> = Box::new(CharField::new("email".to_string()));
139 /// let errors = vec!["Invalid email format".to_string(), "Email is required".to_string()];
140 ///
141 /// let bound = BoundField::new("form".to_string(), field.as_ref(), None, &errors, "");
142 /// assert_eq!(bound.errors().len(), 2);
143 /// assert_eq!(bound.errors()[0], "Invalid email format");
144 /// ```
145 pub fn errors(&self) -> &[String] {
146 self.errors
147 }
148 /// Check if field has errors
149 ///
150 /// # Examples
151 ///
152 /// ```
153 /// use reinhardt_forms::{BoundField, CharField, FormField};
154 ///
155 /// let field: Box<dyn FormField> = Box::new(CharField::new("username".to_string()));
156 ///
157 /// // Without errors
158 /// let bound_ok = BoundField::new("form".to_string(), field.as_ref(), None, &[], "");
159 /// assert!(!bound_ok.has_errors());
160 ///
161 /// // With errors
162 /// let errors = vec!["Username is required".to_string()];
163 /// let bound_err = BoundField::new("form".to_string(), field.as_ref(), None, &errors, "");
164 /// assert!(bound_err.has_errors());
165 /// ```
166 pub fn has_errors(&self) -> bool {
167 !self.errors.is_empty()
168 }
169 /// Get the widget
170 ///
171 /// # Examples
172 ///
173 /// ```
174 /// use reinhardt_forms::{BoundField, CharField, EmailField, FormField, Widget};
175 ///
176 /// let field: Box<dyn FormField> = Box::new(CharField::new("name".to_string()));
177 /// let bound = BoundField::new("form".to_string(), field.as_ref(), None, &[], "");
178 /// assert!(matches!(bound.widget(), Widget::TextInput));
179 ///
180 /// let email_field: Box<dyn FormField> = Box::new(EmailField::new("email".to_string()));
181 /// let email_bound = BoundField::new("form".to_string(), email_field.as_ref(), None, &[], "");
182 /// assert!(matches!(email_bound.widget(), Widget::EmailInput));
183 /// ```
184 pub fn widget(&self) -> &Widget {
185 self.field.widget()
186 }
187 /// Get help text
188 ///
189 /// # Examples
190 ///
191 /// ```
192 /// use reinhardt_forms::{BoundField, CharField, FormField};
193 ///
194 /// let mut field = CharField::new("password".to_string());
195 /// field.help_text = Some("Must be at least 8 characters".to_string());
196 /// let field_box: Box<dyn FormField> = Box::new(field);
197 ///
198 /// let bound = BoundField::new("form".to_string(), field_box.as_ref(), None, &[], "");
199 /// assert_eq!(bound.help_text(), Some("Must be at least 8 characters"));
200 /// ```
201 pub fn help_text(&self) -> Option<&str> {
202 self.field.help_text()
203 }
204 /// Check if field is required
205 ///
206 /// # Examples
207 ///
208 /// ```
209 /// use reinhardt_forms::{BoundField, CharField, FormField};
210 ///
211 /// let mut field = CharField::new("name".to_string());
212 /// field.required = true;
213 /// let field_box: Box<dyn FormField> = Box::new(field);
214 ///
215 /// let bound = BoundField::new("form".to_string(), field_box.as_ref(), None, &[], "");
216 /// assert!(bound.is_required());
217 ///
218 /// let mut optional_field = CharField::new("nickname".to_string());
219 /// optional_field.required = false;
220 /// let optional_box: Box<dyn FormField> = Box::new(optional_field);
221 ///
222 /// let optional_bound = BoundField::new("form".to_string(), optional_box.as_ref(), None, &[], "");
223 /// assert!(!optional_bound.is_required());
224 /// ```
225 pub fn is_required(&self) -> bool {
226 self.field.required()
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::fields::CharField;
234
235 #[test]
236 fn test_bound_field_basic() {
237 let field: Box<dyn FormField> = Box::new(CharField::new("name".to_string()));
238 let data = serde_json::json!("John Doe");
239 let errors = vec![];
240
241 let bound = BoundField::new(
242 "test_form".to_string(),
243 field.as_ref(),
244 Some(&data),
245 &errors,
246 "",
247 );
248
249 assert_eq!(bound.name(), "name");
250 assert_eq!(bound.html_name(), "name");
251 assert_eq!(bound.id_for_label(), "id_name");
252 assert_eq!(bound.value(), Some(&data));
253 assert!(!bound.has_errors());
254 }
255
256 #[test]
257 fn test_bound_field_with_prefix() {
258 let field: Box<dyn FormField> = Box::new(CharField::new("name".to_string()));
259 let data = serde_json::json!("John Doe");
260 let errors = vec![];
261
262 let bound = BoundField::new(
263 "test_form".to_string(),
264 field.as_ref(),
265 Some(&data),
266 &errors,
267 "profile",
268 );
269
270 assert_eq!(bound.html_name(), "profile-name");
271 assert_eq!(bound.id_for_label(), "id_profile-name");
272 }
273
274 #[test]
275 fn test_bound_field_with_errors() {
276 let field: Box<dyn FormField> = Box::new(CharField::new("name".to_string()));
277 let data = serde_json::json!("");
278 let errors = vec!["This field is required.".to_string()];
279
280 let bound = BoundField::new(
281 "test_form".to_string(),
282 field.as_ref(),
283 Some(&data),
284 &errors,
285 "",
286 );
287
288 assert!(bound.has_errors());
289 assert_eq!(bound.errors().len(), 1);
290 }
291}