Skip to main content

reinhardt_forms/fields/
integer_field.rs

1//! Integer field
2
3use crate::field::{FieldError, FieldResult, FormField, Widget};
4
5/// Integer field with range validation
6#[derive(Debug, Clone)]
7pub struct IntegerField {
8	/// The field name used as the form data key.
9	pub name: String,
10	/// Optional human-readable label for display.
11	pub label: Option<String>,
12	/// Whether this field must be filled in.
13	pub required: bool,
14	/// Optional help text displayed alongside the field.
15	pub help_text: Option<String>,
16	/// The widget type used for rendering this field.
17	pub widget: Widget,
18	/// Optional initial (default) value for the field.
19	pub initial: Option<serde_json::Value>,
20	/// Maximum allowed value.
21	pub max_value: Option<i64>,
22	/// Minimum allowed value.
23	pub min_value: Option<i64>,
24}
25
26impl IntegerField {
27	/// Create a new IntegerField with the given name
28	///
29	/// # Examples
30	///
31	/// ```
32	/// use reinhardt_forms::fields::IntegerField;
33	///
34	/// let field = IntegerField::new("age".to_string());
35	/// assert_eq!(field.name, "age");
36	/// assert!(!field.required);
37	/// assert_eq!(field.min_value, None);
38	/// ```
39	pub fn new(name: String) -> Self {
40		Self {
41			name,
42			label: None,
43			required: false,
44			help_text: None,
45			widget: Widget::NumberInput,
46			initial: None,
47			max_value: None,
48			min_value: None,
49		}
50	}
51	/// Set the field as required
52	///
53	/// # Examples
54	///
55	/// ```
56	/// use reinhardt_forms::fields::IntegerField;
57	///
58	/// let field = IntegerField::new("age".to_string()).required();
59	/// assert!(field.required);
60	/// ```
61	pub fn required(mut self) -> Self {
62		self.required = true;
63		self
64	}
65	/// Set the minimum value for the field
66	///
67	/// # Examples
68	///
69	/// ```
70	/// use reinhardt_forms::fields::IntegerField;
71	///
72	/// let field = IntegerField::new("age".to_string()).with_min_value(0);
73	/// assert_eq!(field.min_value, Some(0));
74	/// ```
75	pub fn with_min_value(mut self, min_value: i64) -> Self {
76		self.min_value = Some(min_value);
77		self
78	}
79	/// Set the maximum value for the field
80	///
81	/// # Examples
82	///
83	/// ```
84	/// use reinhardt_forms::fields::IntegerField;
85	///
86	/// let field = IntegerField::new("count".to_string()).with_max_value(100);
87	/// assert_eq!(field.max_value, Some(100));
88	/// ```
89	pub fn with_max_value(mut self, max_value: i64) -> Self {
90		self.max_value = Some(max_value);
91		self
92	}
93
94	/// Set the label for the field
95	///
96	/// # Examples
97	///
98	/// ```
99	/// use reinhardt_forms::fields::IntegerField;
100	///
101	/// let field = IntegerField::new("age".to_string()).with_label("Age");
102	/// assert_eq!(field.label, Some("Age".to_string()));
103	/// ```
104	pub fn with_label(mut self, label: impl Into<String>) -> Self {
105		self.label = Some(label.into());
106		self
107	}
108
109	/// Set the help text for the field
110	///
111	/// # Examples
112	///
113	/// ```
114	/// use reinhardt_forms::fields::IntegerField;
115	///
116	/// let field = IntegerField::new("age".to_string()).with_help_text("Enter your age");
117	/// assert_eq!(field.help_text, Some("Enter your age".to_string()));
118	/// ```
119	pub fn with_help_text(mut self, help_text: impl Into<String>) -> Self {
120		self.help_text = Some(help_text.into());
121		self
122	}
123
124	/// Set the initial value for the field
125	///
126	/// # Examples
127	///
128	/// ```
129	/// use reinhardt_forms::fields::IntegerField;
130	///
131	/// let field = IntegerField::new("quantity".to_string()).with_initial(1);
132	/// assert_eq!(field.initial, Some(serde_json::json!(1)));
133	/// ```
134	pub fn with_initial(mut self, initial: i64) -> Self {
135		self.initial = Some(serde_json::json!(initial));
136		self
137	}
138}
139
140// Note: Default trait is not implemented because IntegerField requires a name
141
142impl FormField for IntegerField {
143	fn name(&self) -> &str {
144		&self.name
145	}
146
147	fn label(&self) -> Option<&str> {
148		self.label.as_deref()
149	}
150
151	fn required(&self) -> bool {
152		self.required
153	}
154
155	fn help_text(&self) -> Option<&str> {
156		self.help_text.as_deref()
157	}
158
159	fn widget(&self) -> &Widget {
160		&self.widget
161	}
162
163	fn initial(&self) -> Option<&serde_json::Value> {
164		self.initial.as_ref()
165	}
166
167	fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
168		match value {
169			None if self.required => Err(FieldError::Required(self.name.clone())),
170			None => Ok(serde_json::Value::Null),
171			Some(v) => {
172				// Parse integer from either number or string
173				let num = if let Some(n) = v.as_i64() {
174					n
175				} else if let Some(s) = v.as_str() {
176					// Trim whitespace
177					let s = s.trim();
178
179					// Return None/error for empty string
180					if s.is_empty() {
181						if self.required {
182							return Err(FieldError::Required(self.name.clone()));
183						}
184						return Ok(serde_json::Value::Null);
185					}
186
187					// Parse string to integer
188					s.parse::<i64>()
189						.map_err(|_| FieldError::Validation("Enter a whole number".to_string()))?
190				} else {
191					return Err(FieldError::Validation(
192						"Expected integer or string".to_string(),
193					));
194				};
195
196				// Validate range
197				if let Some(max) = self.max_value
198					&& num > max
199				{
200					return Err(FieldError::Validation(format!(
201						"Ensure this value is less than or equal to {}",
202						max
203					)));
204				}
205
206				if let Some(min) = self.min_value
207					&& num < min
208				{
209					return Err(FieldError::Validation(format!(
210						"Ensure this value is greater than or equal to {}",
211						min
212					)));
213				}
214
215				Ok(serde_json::Value::Number(num.into()))
216			}
217		}
218	}
219}