1use serde_json::Value;
2use std::collections;
3
4use super::helpers;
5use super::keywords;
6use super::schema;
7use super::SchemaVersion;
8
9#[allow(dead_code)]
10#[derive(Debug)]
11pub struct Scope {
12 keywords: keywords::KeywordMap,
13 schemes: collections::HashMap<String, schema::Schema>,
14 pub(crate) supply_defaults: bool,
15 schema_version: SchemaVersion,
16}
17
18#[allow(dead_code)]
19impl Scope {
20 pub fn new() -> Scope {
21 let mut scope = Scope::without_formats(SchemaVersion::Draft7);
22 scope.add_keyword(vec!["format"], keywords::format::Format::new());
23 scope
24 }
25
26 pub fn without_formats(version: SchemaVersion) -> Scope {
27 Scope {
28 keywords: keywords::default(),
29 schemes: collections::HashMap::new(),
30 supply_defaults: false,
31 schema_version: version,
32 }
33 }
34
35 pub fn with_formats<F>(build_formats: F, version: SchemaVersion) -> Scope
36 where
37 F: FnOnce(&mut keywords::format::FormatBuilders),
38 {
39 let mut scope = Scope::without_formats(version);
40 scope.add_keyword(
41 vec!["format"],
42 keywords::format::Format::with(build_formats),
43 );
44 scope
45 }
46
47 pub fn set_version(mut self, version: SchemaVersion) -> Self {
48 self.schema_version = version;
49 self
50 }
51
52 #[must_use]
115 pub fn supply_defaults(self) -> Self {
116 Scope {
117 keywords: self.keywords,
118 schemes: self.schemes,
119 supply_defaults: true,
120 schema_version: SchemaVersion::Draft7,
121 }
122 }
123
124 pub fn compile(
125 &mut self,
126 def: Value,
127 ban_unknown: bool,
128 ) -> Result<url::Url, schema::SchemaError> {
129 let mut schema = schema::compile(
130 def,
131 None,
132 schema::CompilationSettings::new(&self.keywords, ban_unknown, self.schema_version),
133 )?;
134 let id = schema.id.clone().unwrap();
135 if self.supply_defaults {
136 schema.add_defaults(&id, self);
137 }
138 self.add(&id, schema)?;
139 Ok(id)
140 }
141
142 pub fn compile_with_id(
143 &mut self,
144 id: &url::Url,
145 def: Value,
146 ban_unknown: bool,
147 ) -> Result<(), schema::SchemaError> {
148 let mut schema = schema::compile(
149 def,
150 Some(id.clone()),
151 schema::CompilationSettings::new(&self.keywords, ban_unknown, self.schema_version),
152 )?;
153 if self.supply_defaults {
154 schema.add_defaults(id, self);
155 }
156 self.add(id, schema)
157 }
158
159 pub fn compile_and_return(
160 &'_ mut self,
161 def: Value,
162 ban_unknown: bool,
163 ) -> Result<schema::ScopedSchema<'_>, schema::SchemaError> {
164 let mut schema = schema::compile(
165 def,
166 None,
167 schema::CompilationSettings::new(&self.keywords, ban_unknown, self.schema_version),
168 )?;
169 let id = schema.id.clone().unwrap();
170 if self.supply_defaults {
171 schema.add_defaults(&id, self);
172 }
173 self.add_and_return(&id, schema)
174 }
175
176 pub fn compile_and_return_with_id<'a>(
177 &'a mut self,
178 id: &url::Url,
179 def: Value,
180 ban_unknown: bool,
181 ) -> Result<schema::ScopedSchema<'a>, schema::SchemaError> {
182 let mut schema = schema::compile(
183 def,
184 Some(id.clone()),
185 schema::CompilationSettings::new(&self.keywords, ban_unknown, self.schema_version),
186 )?;
187 if self.supply_defaults {
188 schema.add_defaults(id, self);
189 }
190 self.add_and_return(id, schema)
191 }
192
193 pub fn add_keyword<T>(&mut self, keys: Vec<&'static str>, keyword: T)
194 where
195 T: keywords::Keyword + 'static,
196 {
197 keywords::decouple_keyword((keys, Box::new(keyword)), &mut self.keywords);
198 }
199
200 #[allow(clippy::map_entry)] fn add(&mut self, id: &url::Url, schema: schema::Schema) -> Result<(), schema::SchemaError> {
202 let (id_str, fragment) = helpers::serialize_schema_path(id);
203
204 if fragment.is_some() {
205 return Err(schema::SchemaError::WrongId);
206 }
207
208 if !self.schemes.contains_key(&id_str) {
209 self.schemes.insert(id_str, schema);
210 Ok(())
211 } else {
212 Err(schema::SchemaError::IdConflicts)
213 }
214 }
215
216 #[allow(clippy::map_entry)] fn add_and_return<'a>(
218 &'a mut self,
219 id: &url::Url,
220 schema: schema::Schema,
221 ) -> Result<schema::ScopedSchema<'a>, schema::SchemaError> {
222 let (id_str, fragment) = helpers::serialize_schema_path(id);
223
224 if fragment.is_some() {
225 return Err(schema::SchemaError::WrongId);
226 }
227
228 if !self.schemes.contains_key(&id_str) {
229 self.schemes.insert(id_str.clone(), schema);
230 Ok(schema::ScopedSchema::new(self, &self.schemes[&id_str]))
231 } else {
232 Err(schema::SchemaError::IdConflicts)
233 }
234 }
235
236 pub fn resolve<'a>(&'a self, id: &url::Url) -> Option<schema::ScopedSchema<'a>> {
237 let (schema_path, fragment) = helpers::serialize_schema_path(id);
238
239 let schema = self.schemes.get(&schema_path).or_else(|| {
240 for (_, schema) in self.schemes.iter() {
242 let internal_schema = schema.resolve(schema_path.as_ref());
243 if internal_schema.is_some() {
244 return internal_schema;
245 }
246 }
247
248 None
249 });
250
251 schema.and_then(|schema| match fragment {
252 Some(ref fragment) => schema
253 .resolve_fragment(fragment)
254 .map(|schema| schema::ScopedSchema::new(self, schema)),
255 None => Some(schema::ScopedSchema::new(self, schema)),
256 })
257 }
258}
259
260#[test]
261fn lookup() {
262 let mut scope = Scope::new();
263
264 scope
265 .compile(
266 jsonway::object(|schema| schema.set("$id", "http://example.com/schema".to_string()))
267 .unwrap(),
268 false,
269 )
270 .ok()
271 .unwrap();
272
273 scope
274 .compile(
275 jsonway::object(|schema| {
276 schema.set("$id", "http://example.com/schema#sub".to_string());
277 schema.object("subschema", |subschema| {
278 subschema.set("$id", "#subschema".to_string());
279 })
280 })
281 .unwrap(),
282 false,
283 )
284 .ok()
285 .unwrap();
286
287 assert!(scope
288 .resolve(&url::Url::parse("http://example.com/schema").ok().unwrap())
289 .is_some());
290 assert!(scope
291 .resolve(
292 &url::Url::parse("http://example.com/schema#sub")
293 .ok()
294 .unwrap()
295 )
296 .is_some());
297 assert!(scope
298 .resolve(
299 &url::Url::parse("http://example.com/schema#sub/subschema")
300 .ok()
301 .unwrap()
302 )
303 .is_some());
304 assert!(scope
305 .resolve(
306 &url::Url::parse("http://example.com/schema#subschema")
307 .ok()
308 .unwrap()
309 )
310 .is_some());
311}