tanzim_validate/
dynamic_map.rs1use crate::error::{Error, ErrorKind};
2use crate::{Meta, Validator};
3use tanzim_value::{Value, ValueType};
4
5#[derive(Default)]
11pub struct DynamicMap {
12 meta: Meta,
13 min_len: Option<usize>,
14 max_len: Option<usize>,
15 values: Option<Box<dyn Validator>>,
16}
17
18impl DynamicMap {
19 pub fn with_meta(mut self, meta: Meta) -> Self {
21 self.meta = meta;
22 self
23 }
24
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn min_len(mut self, min: usize) -> Self {
30 self.min_len = Some(min);
31 self
32 }
33
34 pub fn max_len(mut self, max: usize) -> Self {
35 self.max_len = Some(max);
36 self
37 }
38
39 pub fn values(mut self, validator: impl Into<Box<dyn Validator>>) -> Self {
41 self.values = Some(validator.into());
42 self
43 }
44}
45
46impl Validator for DynamicMap {
47 fn meta(&self) -> &Meta {
48 &self.meta
49 }
50
51 fn meta_mut(&mut self) -> &mut Meta {
52 &mut self.meta
53 }
54
55 fn check(&self, value: &mut Value) -> Result<(), Error> {
56 match value {
57 Value::Map(_) => {}
58 Value::List(list) if list.is_empty() => *value = Value::new_map(),
59 _ => {
60 return Err(Error::new(ErrorKind::Type {
61 expected: ValueType::Map,
62 found: value.type_name(),
63 }));
64 }
65 }
66
67 let map = match value.map_mut() {
68 Some(map) => map,
69 None => unreachable!("value coerced to a map above"),
70 };
71
72 let length = map.len();
73 if let Some(min) = self.min_len
74 && length < min
75 {
76 return Err(Error::new(ErrorKind::TooShort { len: length, min }));
77 }
78 if let Some(max) = self.max_len
79 && length > max
80 {
81 return Err(Error::new(ErrorKind::TooLong { len: length, max }));
82 }
83
84 if let Some(validator) = &self.values {
85 for (key, entry) in map.entries_mut() {
86 match validator.validate(entry.value_mut()) {
87 Ok(()) => {}
88 Err(error) => return Err(error.under_key(key, entry.location())),
89 }
90 }
91 }
92
93 Ok(())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::Integer;
101 use tanzim_value::{LocatedValue, Location, Map};
102
103 fn entry(value: Value) -> LocatedValue {
104 LocatedValue::new(value, Location::at("file", "test", Some(1), Some(1), None))
105 }
106
107 #[test]
108 fn empty_list_becomes_empty_map() {
109 let mut value = Value::new_list();
110 DynamicMap::new().validate(&mut value).unwrap();
111 assert_eq!(value, Value::new_map());
112 }
113
114 #[test]
115 fn enforces_count_bounds() {
116 let mut map = Map::new();
117 map.insert("a".into(), entry(Value::Int(1)));
118 let mut value = Value::Map(map);
119 let error = DynamicMap::new()
120 .min_len(2)
121 .validate(&mut value)
122 .unwrap_err();
123 assert!(matches!(error.kind, ErrorKind::TooShort { .. }));
124 }
125
126 #[test]
127 fn value_validator_reports_key_path() {
128 let mut map = Map::new();
129 map.insert("a".into(), entry(Value::String("x".into())));
130 let mut value = Value::Map(map);
131 let error = DynamicMap::new()
132 .values(Integer::new())
133 .validate(&mut value)
134 .unwrap_err();
135 assert_eq!(error.path.len(), 1);
136 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
137 }
138}