tanzim_validate/
static_map.rs1use crate::error::{Error, ErrorKind};
2use crate::{Meta, Validator};
3use tanzim_value::{Value, ValueType};
4
5struct Field {
6 key: String,
7 required: bool,
8 validator: Option<Box<dyn Validator>>,
9}
10
11#[derive(Default)]
17pub struct StaticMap {
18 meta: Meta,
19 fields: Vec<Field>,
20 deny_unknown: bool,
21}
22
23impl StaticMap {
24 pub fn with_meta(mut self, meta: Meta) -> Self {
26 self.meta = meta;
27 self
28 }
29
30 pub fn new() -> Self {
31 Self {
32 meta: Meta::default(),
33 fields: Vec::new(),
34 deny_unknown: true,
35 }
36 }
37
38 pub fn required(
40 mut self,
41 key: impl Into<String>,
42 validator: impl Into<Box<dyn Validator>>,
43 ) -> Self {
44 self.fields.push(Field {
45 key: key.into(),
46 required: true,
47 validator: Some(validator.into()),
48 });
49 self
50 }
51
52 pub fn optional(
54 mut self,
55 key: impl Into<String>,
56 validator: impl Into<Box<dyn Validator>>,
57 ) -> Self {
58 self.fields.push(Field {
59 key: key.into(),
60 required: false,
61 validator: Some(validator.into()),
62 });
63 self
64 }
65
66 pub fn required_any(mut self, key: impl Into<String>) -> Self {
68 self.fields.push(Field {
69 key: key.into(),
70 required: true,
71 validator: None,
72 });
73 self
74 }
75
76 pub fn optional_any(mut self, key: impl Into<String>) -> Self {
78 self.fields.push(Field {
79 key: key.into(),
80 required: false,
81 validator: None,
82 });
83 self
84 }
85
86 pub fn deny_unknown(mut self) -> Self {
88 self.deny_unknown = true;
89 self
90 }
91
92 pub fn allow_unknown(mut self) -> Self {
94 self.deny_unknown = false;
95 self
96 }
97}
98
99impl Validator for StaticMap {
100 fn meta(&self) -> &Meta {
101 &self.meta
102 }
103
104 fn meta_mut(&mut self) -> &mut Meta {
105 &mut self.meta
106 }
107
108 fn check(&self, value: &mut Value) -> Result<(), Error> {
109 let map = match value.map_mut() {
110 Some(map) => map,
111 None => {
112 return Err(Error::new(ErrorKind::Type {
113 expected: ValueType::Map,
114 found: value.type_name(),
115 }));
116 }
117 };
118
119 for field in &self.fields {
120 if field.required && !map.contains_key(&field.key) {
121 return Err(Error::new(ErrorKind::MissingKey {
122 key: field.key.clone(),
123 }));
124 }
125 }
126
127 for field in &self.fields {
128 if let Some(validator) = &field.validator
129 && let Some(entry) = map.get_mut(&field.key)
130 {
131 match validator.validate(entry.value_mut()) {
132 Ok(()) => {}
133 Err(error) => return Err(error.under_key(&field.key, entry.location())),
134 }
135 }
136 }
137
138 if self.deny_unknown {
139 for (key, entry) in map.entries() {
140 let mut declared = false;
141 for field in &self.fields {
142 if &field.key == key {
143 declared = true;
144 break;
145 }
146 }
147 if !declared {
148 return Err(Error::new(ErrorKind::UnknownKey { key: key.clone() })
149 .with_location(entry.location()));
150 }
151 }
152 }
153
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::{Integer, Str};
162 use tanzim_value::{LocatedValue, Location, Map};
163
164 fn entry(value: Value) -> LocatedValue {
165 LocatedValue::new(value, Location::at("file", "test", Some(1), Some(1), None))
166 }
167
168 fn map_of(pairs: &[(&str, Value)]) -> Value {
169 let mut map = Map::new();
170 for (key, value) in pairs {
171 map.insert((*key).to_string(), entry(value.clone()));
172 }
173 Value::Map(map)
174 }
175
176 #[test]
177 fn missing_required_key_fails() {
178 let schema = StaticMap::new().required("host", Str::new());
179 let mut value = map_of(&[]);
180 let error = schema.validate(&mut value).unwrap_err();
181 assert!(matches!(error.kind, ErrorKind::MissingKey { .. }));
182 }
183
184 #[test]
185 fn optional_absent_is_ok() {
186 let schema = StaticMap::new().optional("port", Integer::new());
187 let mut value = map_of(&[]);
188 assert!(schema.validate(&mut value).is_ok());
189 }
190
191 #[test]
192 fn value_validator_reports_key_path() {
193 let schema = StaticMap::new().required("port", Integer::new());
194 let mut value = map_of(&[("port", Value::String("x".into()))]);
195 let error = schema.validate(&mut value).unwrap_err();
196 assert_eq!(error.path.len(), 1);
197 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
198 }
199
200 #[test]
201 fn unknown_key_denied_by_default() {
202 let schema = StaticMap::new().required("host", Str::new());
203 let mut value = map_of(&[
204 ("host", Value::String("h".into())),
205 ("extra", Value::Int(1)),
206 ]);
207 let error = schema.validate(&mut value).unwrap_err();
208 assert!(matches!(error.kind, ErrorKind::UnknownKey { .. }));
209 }
210
211 #[test]
212 fn unknown_key_allowed_when_opted_in() {
213 let schema = StaticMap::new()
214 .required("host", Str::new())
215 .allow_unknown();
216 let mut value = map_of(&[
217 ("host", Value::String("h".into())),
218 ("extra", Value::Int(1)),
219 ]);
220 assert!(schema.validate(&mut value).is_ok());
221 }
222}