Skip to main content

reinhardt_forms/fields/
json_field.rs

1//! JSONField implementation for handling JSON data in forms
2
3use crate::Widget;
4use crate::field::{FieldError, FieldResult, FormField};
5use serde_json::{self, Value};
6
7/// A field for JSON data
8///
9/// Validates that the input is valid JSON and optionally enforces a schema.
10///
11/// # Examples
12///
13/// ```
14/// use reinhardt_forms::fields::JSONField;
15/// use reinhardt_forms::Field;
16/// use serde_json::json;
17///
18/// let field = JSONField::new("data");
19///
20/// // Valid JSON object
21/// let result = field.clean(Some(&json!(r#"{"name": "John", "age": 30}"#)));
22/// assert!(result.is_ok());
23///
24/// // Valid JSON array
25/// let result = field.clean(Some(&json!(r#"[1, 2, 3]"#)));
26/// assert!(result.is_ok());
27///
28/// // Invalid JSON
29/// let result = field.clean(Some(&json!(r#"{invalid}"#)));
30/// assert!(result.is_err());
31/// ```
32/// Default maximum nesting depth for JSON deserialization
33const DEFAULT_MAX_DEPTH: usize = 64;
34
35/// A form field for JSON data with optional schema validation.
36#[derive(Debug, Clone)]
37pub struct JSONField {
38	/// The field name used as the form data key.
39	pub name: String,
40	/// Whether this field must be filled in.
41	pub required: bool,
42	/// Custom error messages keyed by error type.
43	pub error_messages: std::collections::HashMap<String, String>,
44	/// The widget type used for rendering this field.
45	pub widget: Widget,
46	/// Help text displayed alongside the field.
47	pub help_text: String,
48	/// Optional initial (default) value for the field.
49	pub initial: Option<Value>,
50	/// Whether to validate JSON is an object (not array, string, etc.)
51	pub require_object: bool,
52	/// Whether to validate JSON is an array
53	pub require_array: bool,
54	/// Required keys for JSON objects
55	pub required_keys: Vec<String>,
56	/// Maximum nesting depth for JSON deserialization to prevent stack overflow
57	pub max_depth: usize,
58}
59
60impl JSONField {
61	/// Create a new JSONField
62	///
63	/// # Examples
64	///
65	/// ```
66	/// use reinhardt_forms::fields::JSONField;
67	///
68	/// let field = JSONField::new("config");
69	/// assert_eq!(field.name, "config");
70	/// assert!(field.required);
71	/// ```
72	pub fn new(name: impl Into<String>) -> Self {
73		let mut error_messages = std::collections::HashMap::new();
74		error_messages.insert(
75			"required".to_string(),
76			"This field is required.".to_string(),
77		);
78		error_messages.insert("invalid".to_string(), "Enter valid JSON.".to_string());
79		error_messages.insert("invalid_type".to_string(), "Invalid JSON type.".to_string());
80		error_messages.insert(
81			"missing_keys".to_string(),
82			"Missing required keys.".to_string(),
83		);
84
85		Self {
86			name: name.into(),
87			required: true,
88			error_messages,
89			widget: Widget::TextArea,
90			help_text: String::new(),
91			initial: None,
92			require_object: false,
93			require_array: false,
94			required_keys: Vec::new(),
95			max_depth: DEFAULT_MAX_DEPTH,
96		}
97	}
98	/// Sets whether this field is required.
99	pub fn required(mut self, required: bool) -> Self {
100		self.required = required;
101		self
102	}
103	/// Sets the help text displayed alongside the field.
104	pub fn help_text(mut self, text: impl Into<String>) -> Self {
105		self.help_text = text.into();
106		self
107	}
108	/// Sets the initial (default) JSON value.
109	pub fn initial(mut self, value: Value) -> Self {
110		self.initial = Some(value);
111		self
112	}
113	/// Requires the JSON value to be an object.
114	pub fn require_object(mut self) -> Self {
115		self.require_object = true;
116		self
117	}
118	/// Requires the JSON value to be an array.
119	pub fn require_array(mut self) -> Self {
120		self.require_array = true;
121		self
122	}
123	/// Sets the list of keys that must be present in JSON objects.
124	pub fn required_keys(mut self, keys: Vec<String>) -> Self {
125		self.required_keys = keys;
126		self
127	}
128	/// Set the maximum nesting depth for JSON deserialization.
129	///
130	/// This prevents stack overflow attacks from deeply nested JSON payloads.
131	/// Default is 64.
132	pub fn max_depth(mut self, depth: usize) -> Self {
133		self.max_depth = depth;
134		self
135	}
136	/// Overrides the error message for a specific error type.
137	pub fn error_message(
138		mut self,
139		error_type: impl Into<String>,
140		message: impl Into<String>,
141	) -> Self {
142		self.error_messages
143			.insert(error_type.into(), message.into());
144		self
145	}
146
147	/// Check if the parsed JSON exceeds the maximum nesting depth.
148	fn check_depth(value: &Value, max_depth: usize) -> bool {
149		Self::depth_check_recursive(value, 0, max_depth)
150	}
151
152	fn depth_check_recursive(value: &Value, current: usize, max: usize) -> bool {
153		if current > max {
154			return false;
155		}
156		match value {
157			Value::Array(arr) => arr
158				.iter()
159				.all(|v| Self::depth_check_recursive(v, current + 1, max)),
160			Value::Object(map) => map
161				.values()
162				.all(|v| Self::depth_check_recursive(v, current + 1, max)),
163			_ => true,
164		}
165	}
166
167	/// Validate that required keys are present in JSON object
168	fn validate_required_keys(&self, obj: &serde_json::Map<String, Value>) -> FieldResult<()> {
169		if self.required_keys.is_empty() {
170			return Ok(());
171		}
172
173		let missing_keys: Vec<&String> = self
174			.required_keys
175			.iter()
176			.filter(|key| !obj.contains_key(*key))
177			.collect();
178
179		if !missing_keys.is_empty() {
180			let error_msg = self
181				.error_messages
182				.get("missing_keys")
183				.cloned()
184				.unwrap_or_else(|| "Missing required keys.".to_string());
185			return Err(FieldError::validation(None, &error_msg));
186		}
187
188		Ok(())
189	}
190}
191
192impl FormField for JSONField {
193	fn name(&self) -> &str {
194		&self.name
195	}
196
197	fn label(&self) -> Option<&str> {
198		None
199	}
200
201	fn widget(&self) -> &Widget {
202		&self.widget
203	}
204
205	fn required(&self) -> bool {
206		self.required
207	}
208
209	fn initial(&self) -> Option<&Value> {
210		self.initial.as_ref()
211	}
212
213	fn help_text(&self) -> Option<&str> {
214		if self.help_text.is_empty() {
215			None
216		} else {
217			Some(&self.help_text)
218		}
219	}
220
221	fn clean(&self, value: Option<&Value>) -> FieldResult<Value> {
222		// Handle None/null
223		if value.is_none() || value == Some(&Value::Null) {
224			if self.required {
225				let error_msg = self
226					.error_messages
227					.get("required")
228					.cloned()
229					.unwrap_or_else(|| "This field is required.".to_string());
230				return Err(FieldError::validation(None, &error_msg));
231			}
232			return Ok(Value::Null);
233		}
234
235		let json_str = match value.unwrap() {
236			Value::String(s) => s.as_str(),
237			_ => {
238				let error_msg = self
239					.error_messages
240					.get("invalid")
241					.cloned()
242					.unwrap_or_else(|| "Enter valid JSON.".to_string());
243				return Err(FieldError::validation(None, &error_msg));
244			}
245		};
246
247		// Empty string handling
248		if json_str.trim().is_empty() {
249			if self.required {
250				let error_msg = self
251					.error_messages
252					.get("required")
253					.cloned()
254					.unwrap_or_else(|| "This field is required.".to_string());
255				return Err(FieldError::validation(None, &error_msg));
256			}
257			return Ok(Value::Null);
258		}
259
260		// Parse JSON
261		let parsed: Value = match serde_json::from_str(json_str) {
262			Ok(v) => v,
263			Err(_) => {
264				let error_msg = self
265					.error_messages
266					.get("invalid")
267					.cloned()
268					.unwrap_or_else(|| "Enter valid JSON.".to_string());
269				return Err(FieldError::validation(None, &error_msg));
270			}
271		};
272
273		// Check nesting depth to prevent stack overflow from deeply nested payloads
274		if !Self::check_depth(&parsed, self.max_depth) {
275			return Err(FieldError::validation(
276				None,
277				"JSON structure is too deeply nested.",
278			));
279		}
280
281		// Validate type constraints
282		if self.require_object && !parsed.is_object() {
283			let error_msg = self
284				.error_messages
285				.get("invalid_type")
286				.cloned()
287				.unwrap_or_else(|| "JSON must be an object.".to_string());
288			return Err(FieldError::validation(None, &error_msg));
289		}
290
291		if self.require_array && !parsed.is_array() {
292			let error_msg = self
293				.error_messages
294				.get("invalid_type")
295				.cloned()
296				.unwrap_or_else(|| "JSON must be an array.".to_string());
297			return Err(FieldError::validation(None, &error_msg));
298		}
299
300		// Validate required keys for objects
301		if let Value::Object(ref obj) = parsed {
302			self.validate_required_keys(obj)?;
303		}
304
305		Ok(parsed)
306	}
307
308	fn has_changed(&self, initial: Option<&Value>, data: Option<&Value>) -> bool {
309		match (initial, data) {
310			(None, None) => false,
311			(Some(_), None) | (None, Some(_)) => true,
312			(Some(a), Some(b)) => {
313				// Normalize both values by parsing and re-serializing
314				// This handles different whitespace, key ordering, etc.
315				let a_normalized = serde_json::to_string(a).unwrap_or_default();
316				let b_normalized = serde_json::to_string(b).unwrap_or_default();
317				a_normalized != b_normalized
318			}
319		}
320	}
321}
322
323#[cfg(test)]
324mod tests {
325	use super::*;
326	use serde_json::json;
327
328	#[test]
329	fn test_json_field_valid_object() {
330		let field = JSONField::new("data");
331		let result = field.clean(Some(&json!(r#"{"name": "John", "age": 30}"#)));
332		let value = result.unwrap();
333		assert!(value.is_object());
334	}
335
336	#[test]
337	fn test_json_field_valid_array() {
338		let field = JSONField::new("data");
339		let result = field.clean(Some(&json!(r#"[1, 2, 3, 4, 5]"#)));
340		let value = result.unwrap();
341		assert!(value.is_array());
342	}
343
344	#[test]
345	fn test_json_field_invalid() {
346		let field = JSONField::new("data");
347		let result = field.clean(Some(&json!(r#"{invalid json}"#)));
348		assert!(result.is_err());
349	}
350
351	#[test]
352	fn test_json_field_required() {
353		let field = JSONField::new("data").required(true);
354		let result = field.clean(None);
355		assert!(result.is_err());
356	}
357
358	#[test]
359	fn test_json_field_not_required() {
360		let field = JSONField::new("data").required(false);
361		let result = field.clean(None);
362		assert!(result.is_ok());
363		assert_eq!(result.unwrap(), Value::Null);
364	}
365
366	#[test]
367	fn test_json_field_require_object() {
368		let field = JSONField::new("data").require_object();
369
370		// Valid object
371		let result = field.clean(Some(&json!(r#"{"key": "value"}"#)));
372		assert!(result.is_ok());
373
374		// Invalid - array
375		let result = field.clean(Some(&json!(r#"[1, 2, 3]"#)));
376		assert!(result.is_err());
377	}
378
379	#[test]
380	fn test_json_field_require_array() {
381		let field = JSONField::new("data").require_array();
382
383		// Valid array
384		let result = field.clean(Some(&json!(r#"[1, 2, 3]"#)));
385		assert!(result.is_ok());
386
387		// Invalid - object
388		let result = field.clean(Some(&json!(r#"{"key": "value"}"#)));
389		assert!(result.is_err());
390	}
391
392	#[test]
393	fn test_json_field_required_keys() {
394		let field = JSONField::new("data")
395			.require_object()
396			.required_keys(vec!["name".to_string(), "age".to_string()]);
397
398		// Valid - has all required keys
399		let result = field.clean(Some(&json!(
400			r#"{"name": "John", "age": 30, "city": "NYC"}"#
401		)));
402		assert!(result.is_ok());
403
404		// Invalid - missing "age" key
405		let result = field.clean(Some(&json!(r#"{"name": "John"}"#)));
406		assert!(result.is_err());
407	}
408
409	#[test]
410	fn test_json_field_has_changed() {
411		let field = JSONField::new("data");
412
413		// Same values
414		assert!(!field.has_changed(
415			Some(&json!({"name": "John"})),
416			Some(&json!({"name": "John"}))
417		));
418
419		// Different values
420		assert!(field.has_changed(
421			Some(&json!({"name": "John"})),
422			Some(&json!({"name": "Jane"}))
423		));
424
425		// None vs Some
426		assert!(field.has_changed(None, Some(&json!({"name": "John"}))));
427	}
428}