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