1use serde_json::json;
40use serde_json::Value;
41use snafu::{ResultExt, Snafu};
42use std::collections::HashMap;
43use std::env;
44use std::fs;
45use std::io;
46use std::mem;
47use std::path::PathBuf;
48use url::Url;
49
50#[derive(Debug, Snafu)]
51pub enum Error {
52 #[snafu(display("Could not open schema from {}: {}", filename, source))]
53 SchemaFromFile {
54 filename: String,
55 source: std::io::Error,
56 },
57 #[snafu(display("Could not open schema from url {}: {}", url, source))]
58 SchemaFromUrl { url: String, source: ureq::Error },
59 #[snafu(display("Parse error for url {}: {}", url, source))]
60 UrlParseError {
61 url: String,
62 source: url::ParseError,
63 },
64 #[snafu(display("schema from {} not valid JSON: {}", url, source))]
65 SchemaNotJson { url: String, source: std::io::Error },
66 #[snafu(display("schema from {} not valid JSON: {}", url, source))]
67 SchemaNotJsonSerde {
68 url: String,
69 source: serde_json::Error,
70 },
71 #[snafu(display("json pointer {} not found", pointer))]
72 JsonPointerNotFound { pointer: String },
73 #[snafu(display("{}", "Json Ref Error"))]
74 JSONRefError { source: std::io::Error },
75}
76
77pub trait Remove {
79 fn remove(&mut self, json_pointer: &str) -> io::Result<Option<Value>>;
81}
82
83impl Remove for serde_json::Value {
84 fn remove(&mut self, json_pointer: &str) -> io::Result<Option<Value>> {
103 let fields: Vec<&str> = json_pointer.split('/').skip(1).collect();
104
105 remove(self, fields)
106 }
107}
108
109fn remove(json_value: &mut Value, fields: Vec<&str>) -> io::Result<Option<Value>> {
110 if fields.is_empty() {
111 return Ok(None);
112 }
113
114 let mut fields = fields.clone();
115 let field = fields.remove(0);
116
117 if field.is_empty() {
118 return Ok(None);
119 }
120
121 match fields.is_empty() {
122 true => match json_value {
123 Value::Array(vec) => {
124 let index = match field.parse::<usize>() {
125 Ok(index) => index,
126 Err(e) => {
127 return Err(io::Error::new(
128 io::ErrorKind::InvalidInput,
129 format!(
130 "{}. Can't find the field '{}' in {}.",
131 e,
132 field,
133 json_value
134 ),
135 ))
136 }
137 };
138 let len = vec.len();
139 if index >= len {
140 return Err(io::Error::new(
141 io::ErrorKind::InvalidInput,
142 format!(
143 "removal index (is {}) should be < len (is {}) from {}",
144 index,
145 len,
146 json_value
147 ),
148 ));
149 }
150 Ok(Some(vec.remove(index)))
151 }
152 Value::Object(map) => Ok(map.remove(field)),
153 _ => Ok(None),
154 },
155 false => match json_value.pointer_mut(format!("/{}", field).as_str()) {
156 Some(json_targeted) => remove(json_targeted, fields),
157 None => Ok(None),
158 },
159 }
160}
161
162type Result<T, E = Error> = std::result::Result<T, E>;
163
164#[derive(Debug)]
174pub struct JsonRef {
175 schema_cache: HashMap<String, Value>,
176 reference_key: Option<String>,
177}
178
179impl JsonRef {
180 pub fn new() -> JsonRef {
182 JsonRef {
183 schema_cache: HashMap::new(),
184 reference_key: None,
185 }
186 }
187
188 pub fn set_reference_key(&mut self, reference_key: &str) {
219 self.reference_key = Some(reference_key.to_owned());
220 }
221
222 pub fn deref_value(&mut self, value: &mut Value) -> Result<()> {
225 let anon_file_url = format!(
226 "file://{}/anon.json",
227 env::current_dir()
228 .context(JSONRefError {})?
229 .to_string_lossy()
230 );
231 self.schema_cache
232 .insert(anon_file_url.clone(), value.clone());
233
234 let mut definitions = json!({});
235
236 self.deref(value, anon_file_url, &vec![], &mut definitions)?;
237
238 let val = value.as_object_mut().unwrap();
239 val.insert("definitions".to_string(), definitions);
240 Ok(())
241 }
242
243 pub fn deref_url(&mut self, url: &str) -> Result<Value> {
258 let mut value: Value = ureq::get(url)
259 .call()
260 .context(SchemaFromUrl {
261 url: url.to_owned(),
262 })?
263 .into_json()
264 .context(SchemaNotJson {
265 url: url.to_owned(),
266 })?;
267
268 self.schema_cache.insert(url.to_string(), value.clone());
269 let mut definitions = json!({});
270 self.deref(&mut value, url.to_string(), &vec![], &mut definitions)?;
271
272 let val = value.as_object_mut().unwrap();
273 val.insert("definitions".to_string(), definitions);
274
275 Ok(value)
276 }
277
278 pub fn deref_file(&mut self, file_path: &str) -> Result<Value> {
296 let file = fs::File::open(file_path).context(SchemaFromFile {
297 filename: file_path.to_owned(),
298 })?;
299 let mut value: Value = serde_json::from_reader(file).context(SchemaNotJsonSerde {
300 url: file_path.to_owned(),
301 })?;
302 let path = PathBuf::from(file_path);
303 let absolute_path = fs::canonicalize(path).context(JSONRefError {})?;
304 let url = format!("file://{}", absolute_path.to_string_lossy());
305
306 self.schema_cache.insert(url.clone(), value.clone());
307 let mut definitions = json!({});
308 self.deref(&mut value, url, &vec![], &mut definitions)?;
309
310 let val = value.as_object_mut().unwrap();
311 val.insert("definitions".to_string(), definitions);
312
313 Ok(value)
314 }
315
316 fn deref(
317 &mut self,
318 value: &mut Value,
319 id: String,
320 used_refs: &Vec<String>,
321 definitions: &mut Value,
322 ) -> Result<()> {
323 let mut new_id = id;
324 if let Some(id_value) = value.get("$id") {
325 if let Some(id_string) = id_value.as_str() {
326 new_id = id_string.to_string()
327 }
328 }
329
330 if let Some(obj) = value.as_object_mut() {
331 if let Some(defs) = obj.get_mut("definitions") {
332 let mut defs_clone = defs.clone();
333 obj.remove("definitions").unwrap();
334
335 if let Some(def_obj) = defs_clone.as_object_mut() {
336 let accumulated_defs = definitions.as_object_mut().unwrap();
337 for (key, val) in def_obj.iter_mut() {
338 accumulated_defs.insert(key.to_string(), val.clone());
339 }
340 }
341 }
342
343 if let Some(ref_value) = obj.remove("$ref") {
344 if let Some(ref_string) = ref_value.as_str() {
345 let id_url = Url::parse(&new_id).context(UrlParseError {
346 url: new_id.clone(),
347 })?;
348 let ref_url = id_url.join(ref_string).context(UrlParseError {
349 url: ref_string.to_owned(),
350 })?;
351
352 let mut ref_url_no_fragment = ref_url.clone();
353 ref_url_no_fragment.set_fragment(None);
354 let ref_no_fragment = ref_url_no_fragment.to_string();
355
356 let mut schema = match self.schema_cache.get(&ref_no_fragment) {
357 Some(cached_schema) => cached_schema.clone(),
358 None => {
359 if ref_no_fragment.starts_with("http") {
360 ureq::get(&ref_no_fragment)
361 .call()
362 .context(SchemaFromUrl {
363 url: ref_no_fragment.clone(),
364 })?
365 .into_json()
366 .context(SchemaNotJson {
367 url: ref_no_fragment.clone(),
368 })?
369 } else if ref_no_fragment.starts_with("file") {
370 let file = fs::File::open(ref_url_no_fragment.path()).context(
371 SchemaFromFile {
372 filename: ref_no_fragment.clone(),
373 },
374 )?;
375 serde_json::from_reader(file).context(SchemaNotJsonSerde {
376 url: ref_no_fragment.clone(),
377 })?
378 } else {
379 panic!("need url to be a file or a http based url")
380 }
381 }
382 };
383
384 if !self.schema_cache.contains_key(&ref_no_fragment) {
385 self.schema_cache
386 .insert(ref_no_fragment.clone(), schema.clone());
387 }
388
389 let ref_url_string = ref_url.to_string();
390 if let Some(ref_fragment) = ref_url.fragment() {
391 schema = schema.pointer(ref_fragment).ok_or(
392 Error::JsonPointerNotFound {pointer: format!("ref `{}` can not be resolved as pointer `{}` can not be found in the schema", ref_string, ref_fragment)}
393 )?.clone();
394 }
395 if used_refs.contains(&ref_url_string) {
396 return Ok(());
397 }
398
399 let mut new_used_refs = used_refs.clone();
400 new_used_refs.push(ref_url_string);
401
402 self.deref(&mut schema, ref_no_fragment, &new_used_refs, definitions)?;
403 let old_value = mem::replace(value, schema);
404
405 if let Some(reference_key) = &self.reference_key {
406 if let Some(new_obj) = value.as_object_mut() {
407 new_obj.insert(reference_key.clone(), old_value);
408 }
409 }
410 }
411 }
412 }
413
414 if let Some(obj) = value.as_object_mut() {
415 for obj_value in obj.values_mut() {
416 self.deref(obj_value, new_id.clone(), used_refs, definitions)?
417 }
418 }
419 Ok(())
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::JsonRef;
426 use serde_json::{json, Value};
427 use std::fs;
428
429 #[test]
430 fn json_no_refs() {
431 let no_ref_example = json!({"properties": {"prop1": {"title": "proptitle"}}});
432
433 let mut jsonref = JsonRef::new();
434
435 let mut input = no_ref_example.clone();
436
437 jsonref.deref_value(&mut input).unwrap();
438
439 assert_eq!(input, no_ref_example)
440 }
441
442 #[test]
443 fn json_with_recursion() {
444 let mut simple_refs_example = json!(
445 {"properties": {"prop1": {"$ref": "#"}}}
446 );
447
448 let simple_refs_expected = json!(
449 {"properties": {"prop1": {"properties": {"prop1": {}}}}
450 }
451 );
452
453 let mut jsonref = JsonRef::new();
454 jsonref.deref_value(&mut simple_refs_example).unwrap();
455 jsonref.set_reference_key("__reference__");
456
457 println!(
458 "{}",
459 serde_json::to_string_pretty(&simple_refs_example).unwrap()
460 );
461
462 assert_eq!(simple_refs_example, simple_refs_expected)
463 }
464
465 #[test]
466 fn simple_from_url() {
467 let mut simple_refs_example = json!(
468 {"properties": {"prop1": {"title": "name"},
469 "prop2": {"$ref": "https://gist.githubusercontent.com/kindly/35a631d33792413ed8e34548abaa9d61/raw/b43dc7a76cc2a04fde2a2087f0eb389099b952fb/test.json", "title": "old_title"}}
470 }
471 );
472
473 let simple_refs_expected = json!(
474 {"properties": {"prop1": {"title": "name"},
475 "prop2": {"title": "title from url", "__reference__": {"title": "old_title"}}}
476 }
477 );
478
479 let mut jsonref = JsonRef::new();
480 jsonref.set_reference_key("__reference__");
481 jsonref.deref_value(&mut simple_refs_example).unwrap();
482
483 assert_eq!(simple_refs_example, simple_refs_expected)
484 }
485
486 #[test]
487 fn nested_with_ref_from_url() {
488 let mut simple_refs_example = json!(
489 {"properties": {"prop1": {"title": "name"},
490 "prop2": {"$ref": "https://gist.githubusercontent.com/kindly/35a631d33792413ed8e34548abaa9d61/raw/0a691c035251f742e8710f71ba92ead307823385/test_nested.json"}}
491 }
492 );
493
494 let simple_refs_expected = json!(
495 {"properties": {"prop1": {"title": "name"},
496 "prop2": {"__reference__": {},
497 "title": "title from url",
498 "properties": {"prop1": {"title": "sub property title in url"},
499 "prop2": {"__reference__": {}, "title": "sub property title in url"}}
500 }}
501 }
502 );
503
504 let mut jsonref = JsonRef::new();
505 jsonref.set_reference_key("__reference__");
506 jsonref.deref_value(&mut simple_refs_example).unwrap();
507
508 assert_eq!(simple_refs_example, simple_refs_expected)
509 }
510
511 #[test]
512 fn nested_ref_from_local_file() {
513 let mut jsonref = JsonRef::new();
514 jsonref.set_reference_key("__reference__");
515 let file_example = jsonref
516 .deref_file("fixtures/nested_relative/base.json")
517 .unwrap();
518
519 let file = fs::File::open("fixtures/nested_relative/expected.json").unwrap();
520 let file_expected: Value = serde_json::from_reader(file).unwrap();
521
522 println!("{}", serde_json::to_string_pretty(&file_example).unwrap());
523
524 assert_eq!(file_example, file_expected)
525 }
526
527 #[test]
528 fn test_defs() {
529 let mut jsonref = JsonRef::new();
530 jsonref.set_reference_key("__reference__");
531 let file_example = jsonref
532 .deref_file("fixtures/definitions/base.json")
533 .unwrap();
534
535 let file = fs::File::open("fixtures/definitions/expected.json").unwrap();
536 let file_expected: Value = serde_json::from_reader(file).unwrap();
537
538 println!("{}", serde_json::to_string_pretty(&file_example).unwrap());
539
540 assert_eq!(file_example, file_expected)
541 }
542}