]>
Commit | Line | Data |
---|---|---|
5a64f325 | 1 | use std::borrow::Cow; |
1a46283b | 2 | use std::cell::RefCell; |
12da5121 | 3 | use std::collections::{BTreeSet, HashSet}; |
5a64f325 WB |
4 | use std::fmt; |
5 | use std::mem; | |
6 | ||
7 | use anyhow::format_err; | |
8 | use serde::de::{self, Deserialize, Unexpected}; | |
9 | ||
10 | use super::Schema; | |
11 | use crate::schema::ParameterError; | |
12 | ||
13 | struct VerifyState { | |
14 | schema: Option<&'static Schema>, | |
15 | path: String, | |
16 | } | |
17 | ||
18 | thread_local! { | |
1a46283b WB |
19 | static VERIFY_SCHEMA: RefCell<Option<VerifyState>> = RefCell::new(None); |
20 | static ERRORS: RefCell<Vec<(String, anyhow::Error)>> = RefCell::new(Vec::new()); | |
5a64f325 WB |
21 | } |
22 | ||
23 | pub(crate) struct SchemaGuard(Option<VerifyState>); | |
24 | ||
25 | impl Drop for SchemaGuard { | |
26 | fn drop(&mut self) { | |
1a46283b | 27 | VERIFY_SCHEMA.with(|schema| { |
5a64f325 | 28 | if self.0.is_none() { |
1a46283b | 29 | ERRORS.with(|errors| errors.borrow_mut().clear()) |
5a64f325 | 30 | } |
1a46283b | 31 | *schema.borrow_mut() = self.0.take(); |
5a64f325 WB |
32 | }); |
33 | } | |
34 | } | |
35 | ||
36 | impl SchemaGuard { | |
37 | /// If this is the "final" guard, take out the errors: | |
38 | fn errors(self) -> Option<Vec<(String, anyhow::Error)>> { | |
39 | if self.0.is_none() { | |
c702638b WB |
40 | let errors = ERRORS.with(|e| mem::take(&mut *e.borrow_mut())); |
41 | (!errors.is_empty()).then_some(errors) | |
5a64f325 WB |
42 | } else { |
43 | None | |
44 | } | |
45 | } | |
46 | } | |
47 | ||
48 | pub(crate) fn push_schema(schema: Option<&'static Schema>, path: Option<&str>) -> SchemaGuard { | |
49 | SchemaGuard(VERIFY_SCHEMA.with(|s| { | |
1a46283b | 50 | let prev = s.borrow_mut().take(); |
5a64f325 WB |
51 | let path = match (path, &prev) { |
52 | (Some(path), Some(prev)) => join_path(&prev.path, path), | |
53 | (Some(path), None) => path.to_owned(), | |
54 | (None, Some(prev)) => prev.path.clone(), | |
55 | (None, None) => String::new(), | |
56 | }; | |
57 | ||
1a46283b | 58 | *s.borrow_mut() = Some(VerifyState { schema, path }); |
5a64f325 WB |
59 | |
60 | prev | |
61 | })) | |
62 | } | |
63 | ||
64 | fn get_path() -> Option<String> { | |
1a46283b | 65 | VERIFY_SCHEMA.with(|s| s.borrow().as_ref().map(|state| state.path.clone())) |
5a64f325 WB |
66 | } |
67 | ||
68 | fn get_schema() -> Option<&'static Schema> { | |
1a46283b | 69 | VERIFY_SCHEMA.with(|s| s.borrow().as_ref().and_then(|state| state.schema)) |
5a64f325 WB |
70 | } |
71 | ||
72 | pub(crate) fn is_verifying() -> bool { | |
1a46283b | 73 | VERIFY_SCHEMA.with(|s| s.borrow().as_ref().is_some()) |
5a64f325 WB |
74 | } |
75 | ||
76 | fn join_path(a: &str, b: &str) -> String { | |
77 | if a.is_empty() { | |
78 | b.to_string() | |
79 | } else { | |
80 | format!("{}/{}", a, b) | |
81 | } | |
82 | } | |
83 | ||
84 | fn push_errstr_path(err_path: &str, err: &str) { | |
85 | if let Some(path) = get_path() { | |
86 | push_err_do(join_path(&path, err_path), format_err!("{}", err)); | |
87 | } | |
88 | } | |
89 | ||
90 | fn push_err(err: impl fmt::Display) { | |
91 | if let Some(path) = get_path() { | |
92 | push_err_do(path, format_err!("{}", err)); | |
93 | } | |
94 | } | |
95 | ||
96 | fn push_err_do(path: String, err: anyhow::Error) { | |
1a46283b | 97 | ERRORS.with(move |errors| errors.borrow_mut().push((path, err))) |
5a64f325 WB |
98 | } |
99 | ||
100 | /// Helper to collect multiple deserialization errors for better reporting. | |
101 | /// | |
102 | /// This is similar to [`IgnoredAny`] in that it implements [`Deserialize`] | |
103 | /// but does not actually deserialize to anything, however, when a deserialization error occurs, | |
104 | /// it'll try to continue and collect further errors. | |
105 | /// | |
106 | /// This only makes sense with the [`SchemaDeserializer`](super::SchemaDeserializer). | |
107 | pub struct Verifier; | |
108 | ||
109 | impl<'de> Deserialize<'de> for Verifier { | |
110 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | |
111 | where | |
112 | D: de::Deserializer<'de>, | |
113 | { | |
114 | if let Some(schema) = get_schema() { | |
115 | let visitor = Visitor(schema); | |
116 | match schema { | |
117 | Schema::Boolean(_) => deserializer.deserialize_bool(visitor), | |
118 | Schema::Integer(_) => deserializer.deserialize_i64(visitor), | |
119 | Schema::Number(_) => deserializer.deserialize_f64(visitor), | |
120 | Schema::String(_) => deserializer.deserialize_str(visitor), | |
121 | Schema::Object(_) => deserializer.deserialize_map(visitor), | |
122 | Schema::AllOf(_) => deserializer.deserialize_map(visitor), | |
d48a1508 | 123 | Schema::OneOf(_) => deserializer.deserialize_map(visitor), |
5a64f325 WB |
124 | Schema::Array(_) => deserializer.deserialize_seq(visitor), |
125 | Schema::Null => deserializer.deserialize_unit(visitor), | |
126 | } | |
127 | } else { | |
128 | Ok(Verifier) | |
129 | } | |
130 | } | |
131 | } | |
132 | ||
133 | pub fn verify(schema: &'static Schema, value: &str) -> Result<(), anyhow::Error> { | |
134 | let guard = push_schema(Some(schema), None); | |
135 | Verifier::deserialize(super::SchemaDeserializer::new(value, schema))?; | |
136 | ||
137 | if let Some(errors) = guard.errors() { | |
138 | Err(ParameterError::from_list(errors).into()) | |
139 | } else { | |
140 | Ok(()) | |
141 | } | |
142 | } | |
143 | ||
144 | struct Visitor(&'static Schema); | |
145 | ||
146 | impl<'de> de::Visitor<'de> for Visitor { | |
147 | type Value = Verifier; | |
148 | ||
149 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
150 | match self.0 { | |
151 | Schema::Boolean(_) => f.write_str("boolean"), | |
152 | Schema::Integer(_) => f.write_str("integer"), | |
153 | Schema::Number(_) => f.write_str("number"), | |
154 | Schema::String(_) => f.write_str("string"), | |
155 | Schema::Object(_) => f.write_str("object"), | |
156 | Schema::AllOf(_) => f.write_str("allOf"), | |
d48a1508 | 157 | Schema::OneOf(_) => f.write_str("oneOf"), |
5a64f325 WB |
158 | Schema::Array(_) => f.write_str("Array"), |
159 | Schema::Null => f.write_str("null"), | |
160 | } | |
161 | } | |
162 | ||
163 | fn visit_bool<E: de::Error>(self, v: bool) -> Result<Self::Value, E> { | |
164 | match self.0 { | |
165 | Schema::Boolean(_) => (), | |
166 | _ => return Err(E::invalid_type(Unexpected::Bool(v), &self)), | |
167 | } | |
168 | Ok(Verifier) | |
169 | } | |
170 | ||
171 | fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> { | |
172 | match self.0 { | |
173 | Schema::Integer(schema) => match schema.check_constraints(v as isize) { | |
174 | Ok(()) => Ok(Verifier), | |
175 | Err(err) => Err(E::custom(err)), | |
176 | }, | |
177 | _ => Err(E::invalid_type(Unexpected::Signed(v), &self)), | |
178 | } | |
179 | } | |
180 | ||
181 | fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> { | |
182 | match self.0 { | |
183 | Schema::Integer(schema) => match schema.check_constraints(v as isize) { | |
184 | Ok(()) => Ok(Verifier), | |
185 | Err(err) => Err(E::custom(err)), | |
186 | }, | |
187 | _ => Err(E::invalid_type(Unexpected::Unsigned(v), &self)), | |
188 | } | |
189 | } | |
190 | ||
191 | fn visit_f64<E: de::Error>(self, v: f64) -> Result<Self::Value, E> { | |
192 | match self.0 { | |
193 | Schema::Number(schema) => match schema.check_constraints(v) { | |
194 | Ok(()) => Ok(Verifier), | |
195 | Err(err) => Err(E::custom(err)), | |
196 | }, | |
197 | _ => Err(E::invalid_type(Unexpected::Float(v), &self)), | |
198 | } | |
199 | } | |
200 | ||
201 | fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> { | |
202 | use de::Error; | |
203 | ||
204 | let schema = match self.0 { | |
205 | Schema::Array(schema) => schema, | |
206 | _ => return Err(A::Error::invalid_type(Unexpected::Seq, &self)), | |
207 | }; | |
208 | ||
209 | let _guard = push_schema(Some(schema.items), None); | |
210 | ||
211 | let mut count = 0; | |
212 | loop { | |
213 | match seq.next_element::<Verifier>() { | |
214 | Ok(Some(_)) => count += 1, | |
215 | Ok(None) => break, | |
216 | Err(err) => push_err(err), | |
217 | } | |
218 | } | |
219 | ||
220 | schema.check_length(count).map_err(de::Error::custom)?; | |
221 | ||
222 | Ok(Verifier) | |
223 | } | |
224 | ||
225 | fn visit_map<A: de::MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> { | |
226 | use de::Error; | |
227 | ||
228 | let schema: &'static dyn crate::schema::ObjectSchemaType = match self.0 { | |
229 | Schema::Object(schema) => schema, | |
230 | Schema::AllOf(schema) => schema, | |
d48a1508 | 231 | Schema::OneOf(schema) => schema, |
5a64f325 WB |
232 | _ => return Err(A::Error::invalid_type(Unexpected::Map, &self)), |
233 | }; | |
234 | ||
12da5121 WB |
235 | // The tests need this to be in a predictable order, so HashSet won't work as it uses a |
236 | // randomized default state. | |
237 | let mut required_keys = BTreeSet::<&'static str>::new(); | |
5a64f325 WB |
238 | for (key, optional, _schema) in schema.properties() { |
239 | if !optional { | |
240 | required_keys.insert(key); | |
241 | } | |
242 | } | |
243 | ||
244 | let mut other_keys = HashSet::<String>::new(); | |
245 | loop { | |
246 | let key: Cow<'de, str> = match map.next_key()? { | |
247 | Some(key) => key, | |
248 | None => break, | |
249 | }; | |
250 | ||
251 | let _guard = match schema.lookup(&key) { | |
252 | Some((optional, schema)) => { | |
253 | if !optional { | |
254 | // required keys are only tracked in the required_keys hashset | |
255 | if !required_keys.remove(key.as_ref()) { | |
256 | // duplicate key | |
257 | push_errstr_path(&key, "duplicate key"); | |
258 | } | |
259 | } else { | |
260 | // optional keys | |
261 | if !other_keys.insert(key.clone().into_owned()) { | |
262 | push_errstr_path(&key, "duplicate key"); | |
263 | } | |
264 | } | |
265 | ||
266 | push_schema(Some(schema), Some(&key)) | |
267 | } | |
268 | None => { | |
269 | if !schema.additional_properties() { | |
270 | push_errstr_path(&key, "schema does not allow additional properties"); | |
271 | } else if !other_keys.insert(key.clone().into_owned()) { | |
272 | push_errstr_path(&key, "duplicate key"); | |
273 | } | |
274 | ||
275 | push_schema(None, Some(&key)) | |
276 | } | |
277 | }; | |
278 | ||
279 | match map.next_value::<Verifier>() { | |
280 | Ok(Verifier) => (), | |
281 | Err(err) => push_err(err), | |
282 | } | |
283 | } | |
284 | ||
285 | for key in required_keys { | |
286 | push_errstr_path(key, "property is missing and it is not optional"); | |
287 | } | |
288 | ||
289 | Ok(Verifier) | |
290 | } | |
291 | ||
292 | fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> { | |
293 | let schema = match self.0 { | |
294 | Schema::String(schema) => schema, | |
295 | _ => return Err(E::invalid_type(Unexpected::Str(value), &self)), | |
296 | }; | |
297 | ||
58b29dfb | 298 | #[allow(clippy::let_unit_value)] |
5a64f325 WB |
299 | let _: () = schema.check_constraints(value).map_err(E::custom)?; |
300 | ||
301 | Ok(Verifier) | |
302 | } | |
303 | } |