Skip to main content

reinhardt_rest/metadata/
fields.rs

1//! Field information and builder for metadata
2
3use super::types::{ChoiceInfo, FieldType};
4use super::validators::FieldValidator;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Field metadata information
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct FieldInfo {
11	/// The data type of this field.
12	#[serde(rename = "type")]
13	pub field_type: FieldType,
14	/// Whether this field is required for input.
15	pub required: bool,
16	/// Whether this field is read-only.
17	#[serde(skip_serializing_if = "Option::is_none")]
18	pub read_only: Option<bool>,
19	/// A human-readable label for the field.
20	#[serde(skip_serializing_if = "Option::is_none")]
21	pub label: Option<String>,
22	/// Additional help text describing the field.
23	#[serde(skip_serializing_if = "Option::is_none")]
24	pub help_text: Option<String>,
25	/// The minimum allowed length for string values.
26	#[serde(skip_serializing_if = "Option::is_none")]
27	pub min_length: Option<usize>,
28	/// The maximum allowed length for string values.
29	#[serde(skip_serializing_if = "Option::is_none")]
30	pub max_length: Option<usize>,
31	/// The minimum allowed value for numeric fields.
32	#[serde(skip_serializing_if = "Option::is_none")]
33	pub min_value: Option<f64>,
34	/// The maximum allowed value for numeric fields.
35	#[serde(skip_serializing_if = "Option::is_none")]
36	pub max_value: Option<f64>,
37	/// Available choices for choice-type fields.
38	#[serde(skip_serializing_if = "Option::is_none")]
39	pub choices: Option<Vec<ChoiceInfo>>,
40	/// Child field info for list-type fields.
41	#[serde(skip_serializing_if = "Option::is_none")]
42	pub child: Option<Box<FieldInfo>>,
43	/// Child field info for nested object fields.
44	#[serde(skip_serializing_if = "Option::is_none")]
45	pub children: Option<HashMap<String, FieldInfo>>,
46	/// Custom validators applied to this field.
47	#[serde(skip_serializing_if = "Option::is_none")]
48	pub validators: Option<Vec<FieldValidator>>,
49	/// The default value for this field when not provided.
50	#[serde(skip_serializing_if = "Option::is_none")]
51	pub default_value: Option<serde_json::Value>,
52}
53
54/// Builder for field information
55pub struct FieldInfoBuilder {
56	field_type: FieldType,
57	required: bool,
58	read_only: Option<bool>,
59	label: Option<String>,
60	help_text: Option<String>,
61	min_length: Option<usize>,
62	max_length: Option<usize>,
63	min_value: Option<f64>,
64	max_value: Option<f64>,
65	choices: Option<Vec<ChoiceInfo>>,
66	child: Option<Box<FieldInfo>>,
67	children: Option<HashMap<String, FieldInfo>>,
68	validators: Option<Vec<FieldValidator>>,
69	default_value: Option<serde_json::Value>,
70}
71
72impl FieldInfoBuilder {
73	/// Creates a new `FieldInfoBuilder` with the specified field type
74	///
75	/// # Examples
76	///
77	/// ```
78	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
79	///
80	/// let builder = FieldInfoBuilder::new(FieldType::String);
81	/// let field = builder.build();
82	/// assert_eq!(field.field_type, FieldType::String);
83	/// assert!(!field.required);
84	/// ```
85	pub fn new(field_type: FieldType) -> Self {
86		Self {
87			field_type,
88			required: false,
89			read_only: None,
90			label: None,
91			help_text: None,
92			min_length: None,
93			max_length: None,
94			min_value: None,
95			max_value: None,
96			choices: None,
97			child: None,
98			children: None,
99			validators: None,
100			default_value: None,
101		}
102	}
103	/// Sets whether the field is required
104	///
105	/// # Examples
106	///
107	/// ```
108	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
109	///
110	/// let field = FieldInfoBuilder::new(FieldType::String)
111	///     .required(true)
112	///     .build();
113	/// assert!(field.required);
114	/// ```
115	pub fn required(mut self, required: bool) -> Self {
116		self.required = required;
117		self
118	}
119	/// Sets whether the field is read-only
120	///
121	/// # Examples
122	///
123	/// ```
124	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
125	///
126	/// let field = FieldInfoBuilder::new(FieldType::Integer)
127	///     .read_only(true)
128	///     .build();
129	/// assert_eq!(field.read_only, Some(true));
130	/// ```
131	pub fn read_only(mut self, read_only: bool) -> Self {
132		self.read_only = Some(read_only);
133		self
134	}
135	/// Sets the human-readable label for the field
136	///
137	/// # Examples
138	///
139	/// ```
140	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
141	///
142	/// let field = FieldInfoBuilder::new(FieldType::String)
143	///     .label("Email Address")
144	///     .build();
145	/// assert_eq!(field.label, Some("Email Address".to_string()));
146	/// ```
147	pub fn label(mut self, label: impl Into<String>) -> Self {
148		self.label = Some(label.into());
149		self
150	}
151	/// Sets help text that provides additional information about the field
152	///
153	/// # Examples
154	///
155	/// ```
156	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
157	///
158	/// let field = FieldInfoBuilder::new(FieldType::Email)
159	///     .help_text("Enter a valid email address")
160	///     .build();
161	/// assert_eq!(field.help_text, Some("Enter a valid email address".to_string()));
162	/// ```
163	pub fn help_text(mut self, help_text: impl Into<String>) -> Self {
164		self.help_text = Some(help_text.into());
165		self
166	}
167	/// Sets the minimum length constraint for string fields
168	///
169	/// # Examples
170	///
171	/// ```
172	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
173	///
174	/// let field = FieldInfoBuilder::new(FieldType::String)
175	///     .min_length(3)
176	///     .build();
177	/// assert_eq!(field.min_length, Some(3));
178	/// ```
179	pub fn min_length(mut self, min_length: usize) -> Self {
180		self.min_length = Some(min_length);
181		self
182	}
183	/// Sets the maximum length constraint for string fields
184	///
185	/// # Examples
186	///
187	/// ```
188	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
189	///
190	/// let field = FieldInfoBuilder::new(FieldType::String)
191	///     .max_length(100)
192	///     .build();
193	/// assert_eq!(field.max_length, Some(100));
194	/// ```
195	pub fn max_length(mut self, max_length: usize) -> Self {
196		self.max_length = Some(max_length);
197		self
198	}
199	/// Sets the minimum value constraint for numeric fields
200	///
201	/// # Examples
202	///
203	/// ```
204	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
205	///
206	/// let field = FieldInfoBuilder::new(FieldType::Integer)
207	///     .min_value(1.0)
208	///     .build();
209	/// assert_eq!(field.min_value, Some(1.0));
210	/// ```
211	pub fn min_value(mut self, min_value: f64) -> Self {
212		self.min_value = Some(min_value);
213		self
214	}
215	/// Sets the maximum value constraint for numeric fields
216	///
217	/// # Examples
218	///
219	/// ```
220	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
221	///
222	/// let field = FieldInfoBuilder::new(FieldType::Float)
223	///     .max_value(100.0)
224	///     .build();
225	/// assert_eq!(field.max_value, Some(100.0));
226	/// ```
227	pub fn max_value(mut self, max_value: f64) -> Self {
228		self.max_value = Some(max_value);
229		self
230	}
231	/// Sets the available choices for choice fields
232	///
233	/// # Examples
234	///
235	/// ```
236	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType, ChoiceInfo};
237	///
238	/// let choices = vec![
239	///     ChoiceInfo {
240	///         value: "small".to_string(),
241	///         display_name: "Small".to_string(),
242	///     },
243	///     ChoiceInfo {
244	///         value: "large".to_string(),
245	///         display_name: "Large".to_string(),
246	///     },
247	/// ];
248	///
249	/// let field = FieldInfoBuilder::new(FieldType::Choice)
250	///     .choices(choices)
251	///     .build();
252	/// assert_eq!(field.choices.as_ref().unwrap().len(), 2);
253	/// ```
254	pub fn choices(mut self, choices: Vec<ChoiceInfo>) -> Self {
255		self.choices = Some(choices);
256		self
257	}
258	/// Sets the child field for list fields, describing the type of elements in the list
259	///
260	/// # Examples
261	///
262	/// ```
263	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
264	///
265	/// let child_field = FieldInfoBuilder::new(FieldType::String)
266	///     .required(true)
267	///     .build();
268	///
269	/// let list_field = FieldInfoBuilder::new(FieldType::List)
270	///     .child(child_field)
271	///     .build();
272	///
273	/// assert!(list_field.child.is_some());
274	/// assert_eq!(list_field.child.unwrap().field_type, FieldType::String);
275	/// ```
276	pub fn child(mut self, child: FieldInfo) -> Self {
277		self.child = Some(Box::new(child));
278		self
279	}
280	/// Sets the children fields for nested object fields, describing the structure of nested objects
281	///
282	/// # Examples
283	///
284	/// ```
285	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
286	/// use std::collections::HashMap;
287	///
288	/// let mut children = HashMap::new();
289	/// children.insert(
290	///     "name".to_string(),
291	///     FieldInfoBuilder::new(FieldType::String).required(true).build()
292	/// );
293	/// children.insert(
294	///     "age".to_string(),
295	///     FieldInfoBuilder::new(FieldType::Integer).required(false).build()
296	/// );
297	///
298	/// let nested_field = FieldInfoBuilder::new(FieldType::NestedObject)
299	///     .children(children)
300	///     .build();
301	///
302	/// assert!(nested_field.children.is_some());
303	/// assert_eq!(nested_field.children.as_ref().unwrap().len(), 2);
304	/// ```
305	pub fn children(mut self, children: HashMap<String, FieldInfo>) -> Self {
306		self.children = Some(children);
307		self
308	}
309
310	/// Adds a custom validator to the field
311	///
312	/// # Examples
313	///
314	/// ```
315	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType, FieldValidator};
316	///
317	/// let validator = FieldValidator {
318	///     validator_type: "email".to_string(),
319	///     options: None,
320	///     message: Some("Invalid email format".to_string()),
321	/// };
322	///
323	/// let field = FieldInfoBuilder::new(FieldType::Email)
324	///     .required(true)
325	///     .add_validator(validator)
326	///     .build();
327	///
328	/// assert!(field.validators.is_some());
329	/// assert_eq!(field.validators.as_ref().unwrap().len(), 1);
330	/// assert_eq!(field.validators.as_ref().unwrap()[0].validator_type, "email");
331	/// ```
332	pub fn add_validator(mut self, validator: FieldValidator) -> Self {
333		if let Some(ref mut validators) = self.validators {
334			validators.push(validator);
335		} else {
336			self.validators = Some(vec![validator]);
337		}
338		self
339	}
340
341	/// Adds multiple validators to the field
342	///
343	/// # Examples
344	///
345	/// ```
346	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType, FieldValidator};
347	///
348	/// let validators = vec![
349	///     FieldValidator {
350	///         validator_type: "min_length".to_string(),
351	///         options: Some(serde_json::json!({"min": 3})),
352	///         message: Some("Too short".to_string()),
353	///     },
354	///     FieldValidator {
355	///         validator_type: "max_length".to_string(),
356	///         options: Some(serde_json::json!({"max": 50})),
357	///         message: Some("Too long".to_string()),
358	///     },
359	/// ];
360	///
361	/// let field = FieldInfoBuilder::new(FieldType::String)
362	///     .validators(validators)
363	///     .build();
364	///
365	/// assert!(field.validators.is_some());
366	/// assert_eq!(field.validators.as_ref().unwrap().len(), 2);
367	/// ```
368	pub fn validators(mut self, validators: Vec<FieldValidator>) -> Self {
369		self.validators = Some(validators);
370		self
371	}
372
373	/// Sets the default value for the field
374	///
375	/// # Examples
376	///
377	/// ```
378	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
379	///
380	/// let field = FieldInfoBuilder::new(FieldType::String)
381	///     .required(false)
382	///     .default_value(serde_json::json!("default"))
383	///     .build();
384	///
385	/// assert!(field.default_value.is_some());
386	/// assert_eq!(field.default_value, Some(serde_json::json!("default")));
387	/// ```
388	pub fn default_value(mut self, default_value: serde_json::Value) -> Self {
389		self.default_value = Some(default_value);
390		self
391	}
392	/// Builds the final `FieldInfo` from the builder
393	///
394	/// # Examples
395	///
396	/// ```
397	/// use reinhardt_rest::metadata::{FieldInfoBuilder, FieldType};
398	///
399	/// let field = FieldInfoBuilder::new(FieldType::String)
400	///     .required(true)
401	///     .label("Username")
402	///     .min_length(3)
403	///     .max_length(20)
404	///     .build();
405	///
406	/// assert_eq!(field.field_type, FieldType::String);
407	/// assert!(field.required);
408	/// assert_eq!(field.label, Some("Username".to_string()));
409	/// assert_eq!(field.min_length, Some(3));
410	/// assert_eq!(field.max_length, Some(20));
411	/// ```
412	pub fn build(self) -> FieldInfo {
413		FieldInfo {
414			field_type: self.field_type,
415			required: self.required,
416			read_only: self.read_only,
417			label: self.label,
418			help_text: self.help_text,
419			min_length: self.min_length,
420			max_length: self.max_length,
421			min_value: self.min_value,
422			max_value: self.max_value,
423			choices: self.choices,
424			child: self.child,
425			children: self.children,
426			validators: self.validators,
427			default_value: self.default_value,
428		}
429	}
430}
431
432#[cfg(test)]
433mod tests {
434	use super::*;
435	use crate::metadata::FieldValidator;
436
437	#[tokio::test]
438	async fn test_field_info_builder() {
439		let field = FieldInfoBuilder::new(FieldType::String)
440			.required(true)
441			.label("Username")
442			.help_text("Enter your username")
443			.min_length(3)
444			.max_length(50)
445			.build();
446
447		assert_eq!(field.field_type, FieldType::String);
448		assert!(field.required);
449		assert_eq!(field.label, Some("Username".to_string()));
450		assert_eq!(field.help_text, Some("Enter your username".to_string()));
451		assert_eq!(field.min_length, Some(3));
452		assert_eq!(field.max_length, Some(50));
453	}
454
455	#[tokio::test]
456	async fn test_choice_field() {
457		let choices = vec![
458			ChoiceInfo {
459				value: "active".to_string(),
460				display_name: "Active".to_string(),
461			},
462			ChoiceInfo {
463				value: "inactive".to_string(),
464				display_name: "Inactive".to_string(),
465			},
466		];
467
468		let field = FieldInfoBuilder::new(FieldType::Choice)
469			.required(true)
470			.label("Status")
471			.choices(choices.clone())
472			.build();
473
474		assert_eq!(field.field_type, FieldType::Choice);
475		assert!(field.required);
476		assert_eq!(field.choices.as_ref().unwrap().len(), 2);
477	}
478
479	// DRF test: test_list_serializer_metadata_returns_info_about_fields_of_child_serializer
480	#[test]
481	fn test_list_field_with_child() {
482		let child_field = FieldInfoBuilder::new(FieldType::Integer)
483			.required(true)
484			.read_only(false)
485			.build();
486
487		let list_field = FieldInfoBuilder::new(FieldType::List)
488			.required(true)
489			.read_only(false)
490			.label("List field")
491			.child(child_field)
492			.build();
493
494		assert_eq!(list_field.field_type, FieldType::List);
495		assert!(list_field.child.is_some());
496		let child = list_field.child.as_ref().unwrap();
497		assert_eq!(child.field_type, FieldType::Integer);
498	}
499
500	// DRF test: test_dont_show_hidden_fields
501	// In Rust, we handle this by simply not adding hidden fields to the field map
502	#[test]
503	fn test_hidden_fields_not_included() {
504		let mut fields = HashMap::new();
505
506		// Only add visible fields
507		fields.insert(
508			"integer_field".to_string(),
509			FieldInfoBuilder::new(FieldType::Integer)
510				.required(true)
511				.max_value(10.0)
512				.build(),
513		);
514
515		// hidden_field is intentionally not added
516
517		assert!(fields.contains_key("integer_field"));
518		assert!(!fields.contains_key("hidden_field"));
519		assert_eq!(fields.len(), 1);
520	}
521
522	#[test]
523	fn test_field_with_single_validator() {
524		let validator = FieldValidator {
525			validator_type: "email".to_string(),
526			options: None,
527			message: Some("Invalid email format".to_string()),
528		};
529
530		let field = FieldInfoBuilder::new(FieldType::Email)
531			.required(true)
532			.add_validator(validator)
533			.build();
534
535		assert!(field.validators.is_some());
536		let validators = field.validators.as_ref().unwrap();
537		assert_eq!(validators.len(), 1);
538		assert_eq!(validators[0].validator_type, "email");
539		assert_eq!(
540			validators[0].message,
541			Some("Invalid email format".to_string())
542		);
543	}
544
545	#[test]
546	fn test_field_with_multiple_validators() {
547		let validators = vec![
548			FieldValidator {
549				validator_type: "min_length".to_string(),
550				options: Some(serde_json::json!({"min": 3})),
551				message: Some("Too short".to_string()),
552			},
553			FieldValidator {
554				validator_type: "max_length".to_string(),
555				options: Some(serde_json::json!({"max": 50})),
556				message: Some("Too long".to_string()),
557			},
558			FieldValidator {
559				validator_type: "regex".to_string(),
560				options: Some(serde_json::json!({"pattern": "^[a-zA-Z0-9_]+$"})),
561				message: Some("Invalid characters".to_string()),
562			},
563		];
564
565		let field = FieldInfoBuilder::new(FieldType::String)
566			.validators(validators)
567			.build();
568
569		assert!(field.validators.is_some());
570		let field_validators = field.validators.as_ref().unwrap();
571		assert_eq!(field_validators.len(), 3);
572		assert_eq!(field_validators[0].validator_type, "min_length");
573		assert_eq!(field_validators[1].validator_type, "max_length");
574		assert_eq!(field_validators[2].validator_type, "regex");
575	}
576
577	#[test]
578	fn test_field_without_validators() {
579		let field = FieldInfoBuilder::new(FieldType::String)
580			.required(true)
581			.label("Username")
582			.build();
583
584		assert!(field.validators.is_none());
585	}
586
587	#[test]
588	fn test_field_with_default_value_string() {
589		let field = FieldInfoBuilder::new(FieldType::String)
590			.required(false)
591			.default_value(serde_json::json!("default text"))
592			.build();
593
594		assert!(field.default_value.is_some());
595		assert_eq!(field.default_value, Some(serde_json::json!("default text")));
596	}
597
598	#[test]
599	fn test_field_with_default_value_number() {
600		let field = FieldInfoBuilder::new(FieldType::Integer)
601			.required(false)
602			.default_value(serde_json::json!(42))
603			.build();
604
605		assert!(field.default_value.is_some());
606		assert_eq!(field.default_value, Some(serde_json::json!(42)));
607	}
608
609	#[test]
610	fn test_field_with_default_value_boolean() {
611		let field = FieldInfoBuilder::new(FieldType::Boolean)
612			.required(false)
613			.default_value(serde_json::json!(true))
614			.build();
615
616		assert!(field.default_value.is_some());
617		assert_eq!(field.default_value, Some(serde_json::json!(true)));
618	}
619
620	#[test]
621	fn test_field_with_default_value_object() {
622		let default_obj = serde_json::json!({
623			"name": "John Doe",
624			"age": 30
625		});
626
627		let field = FieldInfoBuilder::new(FieldType::NestedObject)
628			.required(false)
629			.default_value(default_obj.clone())
630			.build();
631
632		assert!(field.default_value.is_some());
633		assert_eq!(field.default_value, Some(default_obj));
634	}
635
636	#[test]
637	fn test_field_with_default_value_array() {
638		let default_array = serde_json::json!(["item1", "item2", "item3"]);
639
640		let field = FieldInfoBuilder::new(FieldType::List)
641			.required(false)
642			.default_value(default_array.clone())
643			.build();
644
645		assert!(field.default_value.is_some());
646		assert_eq!(field.default_value, Some(default_array));
647	}
648
649	#[test]
650	fn test_field_without_default_value() {
651		let field = FieldInfoBuilder::new(FieldType::String)
652			.required(true)
653			.label("Username")
654			.build();
655
656		assert!(field.default_value.is_none());
657	}
658
659	#[test]
660	fn test_default_value_serialization() {
661		let field = FieldInfoBuilder::new(FieldType::String)
662			.default_value(serde_json::json!("default"))
663			.build();
664
665		let json = serde_json::to_string(&field).unwrap();
666		assert!(json.contains("default_value"));
667		assert!(json.contains("default"));
668	}
669
670	#[test]
671	fn test_default_value_not_serialized_when_none() {
672		let field = FieldInfoBuilder::new(FieldType::String).build();
673
674		let json = serde_json::to_string(&field).unwrap();
675		assert!(!json.contains("default_value"));
676	}
677}