1use crate::error::{Error, ErrorKind};
2use crate::{Meta, Validator};
3use tanzim_value::{Value, ValueType};
4
5#[derive(Default)]
11pub struct List {
12 meta: Meta,
13 min_len: Option<usize>,
14 max_len: Option<usize>,
15 unique: bool,
16 items: Option<Box<dyn Validator>>,
17}
18
19impl List {
20 pub fn with_meta(mut self, meta: Meta) -> Self {
22 self.meta = meta;
23 self
24 }
25
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 pub fn min_len(mut self, min: usize) -> Self {
31 self.min_len = Some(min);
32 self
33 }
34
35 pub fn max_len(mut self, max: usize) -> Self {
36 self.max_len = Some(max);
37 self
38 }
39
40 pub fn unique(mut self) -> Self {
41 self.unique = true;
42 self
43 }
44
45 pub fn items(mut self, validator: impl Into<Box<dyn Validator>>) -> Self {
47 self.items = Some(validator.into());
48 self
49 }
50}
51
52crate::impl_meta_methods!(List);
53
54impl Validator for List {
55 fn meta(&self) -> &Meta {
56 &self.meta
57 }
58
59 fn meta_mut(&mut self) -> &mut Meta {
60 &mut self.meta
61 }
62
63 fn check(&self, value: &mut Value) -> Result<(), Error> {
64 match value {
65 Value::List(_) => {}
66 Value::Map(map) if map.is_empty() => *value = Value::new_list(),
67 _ => {
68 return Err(Error::new(ErrorKind::Type {
69 expected: ValueType::List,
70 found: value.type_name(),
71 }));
72 }
73 }
74
75 let items = match value.list_mut() {
76 Some(items) => items,
77 None => unreachable!("value coerced to a list above"),
78 };
79
80 let length = items.len();
81 if let Some(min) = self.min_len
82 && length < min
83 {
84 return Err(Error::new(ErrorKind::TooShort { len: length, min }));
85 }
86 if let Some(max) = self.max_len
87 && length > max
88 {
89 return Err(Error::new(ErrorKind::TooLong { len: length, max }));
90 }
91
92 if let Some(validator) = &self.items {
93 for (index, item) in items.iter_mut().enumerate() {
94 match validator.validate(item.value_mut()) {
95 Ok(()) => {}
96 Err(error) => return Err(error.under_index(index, item.location())),
97 }
98 }
99 }
100
101 if self.unique {
102 let mut seen: Vec<&Value> = Vec::new();
103 for (index, item) in items.iter().enumerate() {
104 for previous in &seen {
105 if **previous == *item.value() {
106 return Err(Error::new(ErrorKind::Duplicate { index })
107 .with_location(item.location()));
108 }
109 }
110 seen.push(item.value());
111 }
112 }
113
114 Ok(())
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::Integer;
122 use tanzim_value::{LocatedValue, Location};
123
124 fn item(value: Value) -> LocatedValue {
125 LocatedValue::new(value, Location::at("file", "test", Some(1), Some(1), None))
126 }
127
128 #[test]
129 fn empty_map_becomes_empty_list() {
130 let mut value = Value::new_map();
131 List::new().validate(&mut value).unwrap();
132 assert_eq!(value, Value::new_list());
133 }
134
135 #[test]
136 fn enforces_length_bounds() {
137 let mut value = Value::List(vec![item(Value::Int(1))]);
138 let error = List::new().min_len(2).validate(&mut value).unwrap_err();
139 assert!(matches!(error.kind, ErrorKind::TooShort { .. }));
140 }
141
142 #[test]
143 fn detects_duplicates() {
144 let mut value = Value::List(vec![item(Value::Int(1)), item(Value::Int(1))]);
145 let error = List::new().unique().validate(&mut value).unwrap_err();
146 assert!(matches!(error.kind, ErrorKind::Duplicate { index: 1 }));
147 }
148
149 #[test]
150 fn item_validator_reports_index_path() {
151 let mut value = Value::List(vec![item(Value::Int(1)), item(Value::String("x".into()))]);
152 let error = List::new()
153 .items(Integer::new())
154 .validate(&mut value)
155 .unwrap_err();
156 assert_eq!(error.path.len(), 1);
157 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
158 }
159
160 #[test]
161 fn item_coercion_persists() {
162 let mut value = Value::List(vec![item(Value::String("5".into()))]);
163 List::new()
164 .items(Integer::new())
165 .validate(&mut value)
166 .unwrap();
167 assert_eq!(*value.as_list().unwrap()[0].value(), Value::Int(5));
168 }
169}