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
99crate::impl_meta_methods!(StaticMap);
100
101impl Validator for StaticMap {
102 fn meta(&self) -> &Meta {
103 &self.meta
104 }
105
106 fn meta_mut(&mut self) -> &mut Meta {
107 &mut self.meta
108 }
109
110 fn check(&self, value: &mut Value) -> Result<(), Error> {
111 let map = match value.map_mut() {
112 Some(map) => map,
113 None => {
114 return Err(Error::new(ErrorKind::Type {
115 expected: ValueType::Map,
116 found: value.type_name(),
117 }));
118 }
119 };
120
121 for field in &self.fields {
122 if field.required && !map.contains_key(&field.key) {
123 return Err(Error::new(ErrorKind::MissingKey {
124 key: field.key.clone(),
125 }));
126 }
127 }
128
129 for field in &self.fields {
130 if let Some(validator) = &field.validator
131 && let Some(entry) = map.get_mut(&field.key)
132 {
133 match validator.validate(entry.value_mut()) {
134 Ok(()) => {}
135 Err(error) => return Err(error.under_key(&field.key, entry.location())),
136 }
137 }
138 }
139
140 if self.deny_unknown {
141 for (key, entry) in map.entries() {
142 let mut declared = false;
143 for field in &self.fields {
144 if &field.key == key {
145 declared = true;
146 break;
147 }
148 }
149 if !declared {
150 return Err(Error::new(ErrorKind::UnknownKey { key: key.clone() })
151 .with_location(entry.location()));
152 }
153 }
154 }
155
156 Ok(())
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::{Integer, Str};
164 use tanzim_value::{LocatedValue, Location, Map};
165
166 fn entry(value: Value) -> LocatedValue {
167 LocatedValue::new(value, Location::at("file", "test", Some(1), Some(1), None))
168 }
169
170 fn map_of(pairs: &[(&str, Value)]) -> Value {
171 let mut map = Map::new();
172 for (key, value) in pairs {
173 map.insert((*key).to_string(), entry(value.clone()));
174 }
175 Value::Map(map)
176 }
177
178 #[test]
179 fn missing_required_key_fails() {
180 let schema = StaticMap::new().required("host", Str::new());
181 let mut value = map_of(&[]);
182 let error = schema.validate(&mut value).unwrap_err();
183 assert!(matches!(error.kind, ErrorKind::MissingKey { .. }));
184 }
185
186 #[test]
187 fn optional_absent_is_ok() {
188 let schema = StaticMap::new().optional("port", Integer::new());
189 let mut value = map_of(&[]);
190 assert!(schema.validate(&mut value).is_ok());
191 }
192
193 #[test]
194 fn value_validator_reports_key_path() {
195 let schema = StaticMap::new().required("port", Integer::new());
196 let mut value = map_of(&[("port", Value::String("x".into()))]);
197 let error = schema.validate(&mut value).unwrap_err();
198 assert_eq!(error.path.len(), 1);
199 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
200 }
201
202 #[test]
203 fn unknown_key_denied_by_default() {
204 let schema = StaticMap::new().required("host", Str::new());
205 let mut value = map_of(&[
206 ("host", Value::String("h".into())),
207 ("extra", Value::Int(1)),
208 ]);
209 let error = schema.validate(&mut value).unwrap_err();
210 assert!(matches!(error.kind, ErrorKind::UnknownKey { .. }));
211 }
212
213 #[test]
214 fn unknown_key_allowed_when_opted_in() {
215 let schema = StaticMap::new()
216 .required("host", Str::new())
217 .allow_unknown();
218 let mut value = map_of(&[
219 ("host", Value::String("h".into())),
220 ("extra", Value::Int(1)),
221 ]);
222 assert!(schema.validate(&mut value).is_ok());
223 }
224}