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