1use url;
2use std::collections;
3use serde_json::{Value};
4use phf;
5use std::ops;
6
7use super::helpers;
8use super::scope;
9use super::keywords;
10use super::validators;
11
12#[derive(Debug)]
13pub struct WalkContext<'a> {
14 pub url: &'a url::Url,
15 pub fragment: Vec<String>,
16 pub scopes: &'a mut collections::HashMap<String, Vec<String>>
17}
18
19impl<'a> WalkContext<'a> {
20 pub fn escaped_fragment(&self) -> String {
21 helpers::connect(self.fragment.iter().map(|s| s.as_ref()).collect::<Vec<&str>>().as_ref())
22 }
23}
24
25#[derive(Debug)]
26#[allow(missing_copy_implementations)]
27pub enum SchemaError {
28 WrongId,
29 IdConflicts,
30 NotAnObject,
31 UrlParseError(url::ParseError),
32 UnknownKey(String),
33 Malformed {
34 path: String,
35 detail: String
36 }
37}
38
39#[derive(Debug)]
40pub struct ScopedSchema<'a> {
41 scope: &'a scope::Scope,
42 schema: &'a Schema
43}
44
45impl<'a> ops::Deref for ScopedSchema<'a> {
46 type Target = Schema;
47
48 fn deref(&self) -> &Schema {
49 &self.schema
50 }
51}
52
53impl<'a> ScopedSchema<'a> {
54 pub fn new(scope: &'a scope::Scope, schema: &'a Schema) -> ScopedSchema<'a> {
55 ScopedSchema {
56 scope: scope,
57 schema: schema
58 }
59 }
60
61 pub fn validate(&self, data: &Value) -> validators::ValidationState {
62 return self.schema.validate_in_scope(data, "", self.scope);
63 }
64
65 pub fn validate_in(&self, data: &Value, path: &str) -> validators::ValidationState {
66 return self.schema.validate_in_scope(data, path, self.scope);
67 }
68}
69
70#[derive(Debug)]
71#[allow(dead_code)]
72pub struct Schema {
73 pub id: Option<url::Url>,
74 schema: Option<url::Url>,
75 original: Value,
76 tree: collections::BTreeMap<String, Schema>,
77 validators: validators::Validators,
78 scopes: collections::HashMap<String, Vec<String>>
79}
80
81include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
82
83pub struct CompilationSettings<'a> {
84 pub keywords: &'a keywords::KeywordMap,
85 pub ban_unknown_keywords: bool
86}
87
88impl<'a> CompilationSettings<'a> {
89 pub fn new(keywords: &'a keywords::KeywordMap, ban_unknown_keywords: bool) -> CompilationSettings<'a> {
90 CompilationSettings {
91 keywords: keywords,
92 ban_unknown_keywords: ban_unknown_keywords,
93 }
94 }
95}
96
97impl Schema {
98 fn compile(def: Value, external_id: Option<url::Url>, settings: CompilationSettings) -> Result<Schema, SchemaError> {
99 if !def.is_object() {
100 return Err(SchemaError::NotAnObject)
101 }
102
103 let id = if external_id.is_some() {
104 external_id.unwrap()
105 } else {
106 try!(helpers::parse_url_key("id", &def)).clone().unwrap_or_else(|| helpers::generate_id())
107 };
108
109 let schema = try!(helpers::parse_url_key("$schema", &def));
110
111 let (tree, mut scopes) = {
112 let mut tree = collections::BTreeMap::new();
113 let obj = def.as_object().unwrap();
114
115 let mut scopes = collections::HashMap::new();
116
117 for (key, value) in obj.iter() {
118 if !value.is_object() && !value.is_array() { continue; }
119 if FINAL_KEYS.contains(&key[..]) { continue; }
120
121 let mut context = WalkContext {
122 url: &id,
123 fragment: vec![key.clone()],
124 scopes: &mut scopes
125 };
126
127 let scheme = try!(Schema::compile_sub(
128 value.clone(),
129 &mut context,
130 &settings,
131 !NON_SCHEMA_KEYS.contains(&key[..])
132 ));
133
134 tree.insert(helpers::encode(key), scheme);
135 }
136
137 (tree, scopes)
138 };
139
140 let validators = try!(Schema::compile_keywords(&def, &WalkContext {
141 url: &id,
142 fragment: vec![],
143 scopes: &mut scopes,
144 }, &settings));
145
146 let schema = Schema {
147 id: Some(id),
148 schema: schema,
149 original: def,
150 tree: tree,
151 validators: validators,
152 scopes: scopes
153 };
154
155 Ok(schema)
156 }
157
158 fn compile_keywords(def: &Value, context: &WalkContext, settings: &CompilationSettings) -> Result<validators::Validators, SchemaError> {
159 let mut validators = vec![];
160 let mut keys: collections::HashSet<&str> = def.as_object().unwrap().keys().map(|key| key.as_ref()).collect();
161 let mut not_consumed = collections::HashSet::new();
162
163 loop {
164 let key = keys.iter().next().cloned();
165 if key.is_some() {
166 let key = key.unwrap();
167 match settings.keywords.get(&key) {
168 Some(keyword) => {
169 keyword.consume(&mut keys);
170
171 let is_exclusive_keyword = keyword.keyword.is_exclusive();
172
173 match try!(keyword.keyword.compile(def, context)) {
174 Some(validator) => {
175 if is_exclusive_keyword {
176 validators = vec![validator];
177 } else {
178 validators.push(validator);
179 }
180 },
181 None => ()
182 };
183
184 if is_exclusive_keyword {
185 break;
186 }
187 },
188 None => {
189 keys.remove(&key);
190 if settings.ban_unknown_keywords {
191 not_consumed.insert(key);
192 }
193 }
194 }
195 } else {
196 break;
197 }
198 }
199
200 if settings.ban_unknown_keywords && not_consumed.len() > 0 {
201 for key in not_consumed.iter() {
202 if !ALLOW_NON_CONSUMED_KEYS.contains(&key[..]) {
203 return Err(SchemaError::UnknownKey(key.to_string()))
204 }
205 }
206 }
207
208 Ok(validators)
209 }
210
211 fn compile_sub(def: Value, context: &mut WalkContext, keywords: &CompilationSettings, is_schema: bool) -> Result<Schema, SchemaError> {
212
213 let mut id = None;
214 let mut schema = None;
215
216 if is_schema {
217 id = try!(helpers::parse_url_key_with_base("id", &def, context.url));
218 schema = try!(helpers::parse_url_key("$schema", &def));
219 }
220
221 let tree = {
222 let mut tree = collections::BTreeMap::new();
223
224 if def.is_object() {
225 let obj = def.as_object().unwrap();
226 let parent_key = &context.fragment[context.fragment.len() - 1];
227
228 for (key, value) in obj.iter() {
229 if !value.is_object() && !value.is_array() { continue; }
230 if !PROPERTY_KEYS.contains(&parent_key[..]) && FINAL_KEYS.contains(&key[..]) { continue; }
231
232 let mut current_fragment = context.fragment.clone();
233 current_fragment.push(key.clone());
234
235 let is_schema = PROPERTY_KEYS.contains(&parent_key[..]) || !NON_SCHEMA_KEYS.contains(&key[..]);
236
237 let mut context = WalkContext {
238 url: id.as_ref().unwrap_or(context.url),
239 fragment: current_fragment,
240 scopes: context.scopes
241 };
242
243 let scheme = try!(Schema::compile_sub(
244 value.clone(),
245 &mut context,
246 keywords,
247 is_schema
248 ));
249
250 tree.insert(helpers::encode(key), scheme);
251 }
252 } else if def.is_array() {
253 let array = def.as_array().unwrap();
254
255 for (idx, value) in array.iter().enumerate() {
256 if !value.is_object() && !value.is_array() { continue; }
257
258 let mut current_fragment = context.fragment.clone();
259 current_fragment.push(idx.to_string().clone());
260
261 let mut context = WalkContext {
262 url: id.as_ref().unwrap_or(context.url),
263 fragment: current_fragment,
264 scopes: context.scopes
265 };
266
267 let scheme = try!(Schema::compile_sub(
268 value.clone(),
269 &mut context,
270 keywords,
271 true
272 ));
273
274 tree.insert(idx.to_string().clone(), scheme);
275 }
276 }
277
278 tree
279 };
280
281 if id.is_some() {
282 context.scopes.insert(id.clone().unwrap().into_string(), context.fragment.clone());
283 }
284
285 let validators = if is_schema && def.is_object() {
286 try!(Schema::compile_keywords(&def, context, keywords))
287 } else {
288 vec![]
289 };
290
291 let schema = Schema {
292 id: id,
293 schema: schema,
294 original: def,
295 tree: tree,
296 validators: validators,
297 scopes: collections::HashMap::new()
298 };
299
300 Ok(schema)
301 }
302
303 pub fn resolve(&self, id: &str) -> Option<&Schema> {
304 let path = self.scopes.get(id);
305 path.map(|path| {
306 let mut schema = self;
307 for item in path.iter() {
308 schema = schema.tree.get(item).unwrap()
309 }
310 schema
311 })
312 }
313
314 pub fn resolve_fragment(&self, fragment: &str) -> Option<&Schema> {
315 assert!(fragment.starts_with("/"), "Can't resolve id fragments");
316
317 let parts = fragment[1..].split("/");
318 let mut schema = self;
319 for part in parts {
320 match schema.tree.get(part) {
321 Some(sch) => schema = sch,
322 None => return None
323 }
324 }
325
326 Some(schema)
327 }
328}
329
330impl Schema {
331 fn validate_in_scope(&self, data: &Value, path: &str, scope: &scope::Scope) -> validators::ValidationState {
332 let mut state = validators::ValidationState::new();
333
334 for validator in self.validators.iter() {
335 state.append(validator.validate(data, path, scope))
336 }
337
338 state
339 }
340}
341
342pub fn compile(def: Value, external_id: Option<url::Url>, settings: CompilationSettings) -> Result<Schema, SchemaError> {
343 Schema::compile(def, external_id, settings)
344}
345
346#[test]
347fn schema_doesnt_compile_not_object() {
348 assert!(Schema::compile(Value::Bool(true), None, CompilationSettings::new(&keywords::default(), true)).is_err());
349}