Skip to main content

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}