1use serde_json::Value;
40use std::collections::HashMap;
41use std::env;
42use std::fs;
43use std::mem;
44use std::path::PathBuf;
45use url::Url;
46use snafu::{Snafu, ResultExt};
47
48#[derive(Debug, Snafu)]
49pub enum Error {
50 #[snafu(display("Could not open schema from {}: {}", filename, source))]
51 SchemaFromFile {
52 filename: String,
53 source: std::io::Error,
54 },
55 #[snafu(display("Could not open schema from url {}: {}", url, source))]
56 SchemaFromUrl {
57 url: String,
58 source: ureq::Error,
59 },
60 #[snafu(display("Parse error for url {}: {}", url, source))]
61 UrlParseError {
62 url: String,
63 source: url::ParseError,
64 },
65 #[snafu(display("schema from {} not valid JSON: {}", url, source))]
66 SchemaNotJson {
67 url: String,
68 source: std::io::Error,
69 },
70 #[snafu(display("schema from {} not valid JSON: {}", url, source))]
71 SchemaNotJsonSerde {
72 url: String,
73 source: serde_json::Error,
74 },
75 #[snafu(display("json pointer {} not found", pointer))]
76 JsonPointerNotFound {
77 pointer: String,
78 },
79 #[snafu(display("{}", "Json Ref Error"))]
80 JSONRefError {
81 source: std::io::Error,
82 }
83}
84
85type Result<T, E = Error> = std::result::Result<T, E>;
86
87#[derive(Debug)]
97pub struct JsonRef {
98 schema_cache: HashMap<String, Value>,
99 reference_key: Option<String>,
100}
101
102impl JsonRef {
103 pub fn new() -> JsonRef {
105 return JsonRef {
106 schema_cache: HashMap::new(),
107 reference_key: None,
108 };
109 }
110
111 pub fn set_reference_key(&mut self, reference_key: &str) {
142 self.reference_key = Some(reference_key.to_owned());
143 }
144
145 pub fn deref_value(&mut self, value: &mut Value) -> Result<()> {
148 let anon_file_url = format!("file://{}/anon.json", env::current_dir().context(JSONRefError {})?.to_string_lossy());
149 self.schema_cache
150 .insert(anon_file_url.clone(), value.clone());
151
152 self.deref(value, anon_file_url, &vec![])?;
153 Ok(())
154 }
155
156 pub fn deref_url(&mut self, url: &str) -> Result<Value> {
171 let mut value: Value = ureq::get(url).call().context(SchemaFromUrl {url: url.to_owned()})?.into_json().context(SchemaNotJson {url: url.to_owned()})?;
172
173 self.schema_cache.insert(url.to_string(), value.clone());
174 self.deref(&mut value, url.to_string(), &vec![])?;
175 Ok(value)
176 }
177
178 pub fn deref_file(&mut self, file_path: &str) -> Result<Value> {
196 let file = fs::File::open(file_path).context(SchemaFromFile {filename: file_path.to_owned()})?;
197 let mut value: Value = serde_json::from_reader(file).context(SchemaNotJsonSerde {url: file_path.to_owned()})?;
198 let path = PathBuf::from(file_path);
199 let absolute_path = fs::canonicalize(path).context(JSONRefError {})?;
200 let url = format!("file://{}", absolute_path.to_string_lossy());
201
202 self.schema_cache.insert(url.clone(), value.clone());
203 self.deref(&mut value, url, &vec![])?;
204 Ok(value)
205 }
206
207 fn deref(
208 &mut self,
209 value: &mut Value,
210 id: String,
211 used_refs: &Vec<String>,
212 ) -> Result<()> {
213 let mut new_id = id;
214 if let Some(id_value) = value.get("$id") {
215 if let Some(id_string) = id_value.as_str() {
216 new_id = id_string.to_string()
217 }
218 }
219
220 if let Some(obj) = value.as_object_mut() {
221 if let Some(ref_value) = obj.remove("$ref") {
222 if let Some(ref_string) = ref_value.as_str() {
223 let id_url = Url::parse(&new_id).context(UrlParseError {url: new_id.clone()})?;
224 let ref_url = id_url.join(ref_string).context(UrlParseError {url: ref_string.to_owned()})?;
225
226 let mut ref_url_no_fragment = ref_url.clone();
227 ref_url_no_fragment.set_fragment(None);
228 let ref_no_fragment = ref_url_no_fragment.to_string();
229
230 let mut schema = match self.schema_cache.get(&ref_no_fragment) {
231 Some(cached_schema) => cached_schema.clone(),
232 None => {
233 if ref_no_fragment.starts_with("http") {
234 ureq::get(&ref_no_fragment)
235 .call().context(SchemaFromUrl {url: ref_no_fragment.clone()})?
236 .into_json().context(SchemaNotJson {url: ref_no_fragment.clone()})?
237 } else if ref_no_fragment.starts_with("file") {
238 let file = fs::File::open(ref_url_no_fragment.path()).context(SchemaFromFile {filename: ref_no_fragment.clone()})?;
239 serde_json::from_reader(file).context(SchemaNotJsonSerde {url: ref_no_fragment.clone()} )?
240 } else {
241 panic!("need url to be a file or a http based url")
242 }
243 }
244 };
245
246 if !self.schema_cache.contains_key(&ref_no_fragment) {
247 self.schema_cache
248 .insert(ref_no_fragment.clone(), schema.clone());
249 }
250
251 let ref_url_string = ref_url.to_string();
252 if let Some(ref_fragment) = ref_url.fragment() {
253 schema = schema.pointer(ref_fragment).ok_or(
254 Error::JsonPointerNotFound {pointer: format!("ref `{}` can not be resolved as pointer `{}` can not be found in the schema", ref_string, ref_fragment)}
255 )?.clone();
256 }
257 if used_refs.contains(&ref_url_string) {
258 return Ok(());
259 }
260
261 let mut new_used_refs = used_refs.clone();
262 new_used_refs.push(ref_url_string);
263
264 self.deref(&mut schema, ref_no_fragment, &new_used_refs)?;
265 let old_value = mem::replace(value, schema);
266
267 if let Some(reference_key) = &self.reference_key {
268 if let Some(new_obj) = value.as_object_mut() {
269 new_obj.insert(reference_key.clone(), old_value);
270 }
271 }
272 }
273 }
274 }
275
276 if let Some(obj) = value.as_object_mut() {
277 for obj_value in obj.values_mut() {
278 self.deref(obj_value, new_id.clone(), used_refs)?
279 }
280 }
281 Ok(())
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::JsonRef;
288 use serde_json::{json, Value};
289 use std::fs;
290
291 #[test]
292 fn json_no_refs() {
293 let no_ref_example = json!({"properties": {"prop1": {"title": "proptitle"}}});
294
295 let mut jsonref = JsonRef::new();
296
297 let mut input = no_ref_example.clone();
298
299 jsonref.deref_value(&mut input).unwrap();
300
301 assert_eq!(input, no_ref_example)
302 }
303
304 #[test]
305 fn json_with_recursion() {
306 let mut simple_refs_example = json!(
307 {"properties": {"prop1": {"$ref": "#"}}}
308 );
309
310 let simple_refs_expected = json!(
311 {"properties": {"prop1": {"properties": {"prop1": {}}}}
312 }
313 );
314
315 let mut jsonref = JsonRef::new();
316 jsonref.deref_value(&mut simple_refs_example).unwrap();
317 jsonref.set_reference_key("__reference__");
318
319 println!(
320 "{}",
321 serde_json::to_string_pretty(&simple_refs_example).unwrap()
322 );
323
324 assert_eq!(simple_refs_example, simple_refs_expected)
325 }
326
327 #[test]
328 fn simple_from_url() {
329 let mut simple_refs_example = json!(
330 {"properties": {"prop1": {"title": "name"},
331 "prop2": {"$ref": "https://gist.githubusercontent.com/kindly/35a631d33792413ed8e34548abaa9d61/raw/b43dc7a76cc2a04fde2a2087f0eb389099b952fb/test.json", "title": "old_title"}}
332 }
333 );
334
335 let simple_refs_expected = json!(
336 {"properties": {"prop1": {"title": "name"},
337 "prop2": {"title": "title from url", "__reference__": {"title": "old_title"}}}
338 }
339 );
340
341 let mut jsonref = JsonRef::new();
342 jsonref.set_reference_key("__reference__");
343 jsonref.deref_value(&mut simple_refs_example).unwrap();
344
345 assert_eq!(simple_refs_example, simple_refs_expected)
346 }
347
348 #[test]
349 fn nested_with_ref_from_url() {
350 let mut simple_refs_example = json!(
351 {"properties": {"prop1": {"title": "name"},
352 "prop2": {"$ref": "https://gist.githubusercontent.com/kindly/35a631d33792413ed8e34548abaa9d61/raw/0a691c035251f742e8710f71ba92ead307823385/test_nested.json"}}
353 }
354 );
355
356 let simple_refs_expected = json!(
357 {"properties": {"prop1": {"title": "name"},
358 "prop2": {"__reference__": {},
359 "title": "title from url",
360 "properties": {"prop1": {"title": "sub property title in url"},
361 "prop2": {"__reference__": {}, "title": "sub property title in url"}}
362 }}
363 }
364 );
365
366 let mut jsonref = JsonRef::new();
367 jsonref.set_reference_key("__reference__");
368 jsonref.deref_value(&mut simple_refs_example).unwrap();
369
370 assert_eq!(simple_refs_example, simple_refs_expected)
371 }
372
373 #[test]
374 fn nested_ref_from_local_file() {
375 let mut jsonref = JsonRef::new();
376 jsonref.set_reference_key("__reference__");
377 let file_example = jsonref
378 .deref_file("fixtures/nested_relative/base.json")
379 .unwrap();
380
381 let file = fs::File::open("fixtures/nested_relative/expected.json").unwrap();
382 let file_expected: Value = serde_json::from_reader(file).unwrap();
383
384 println!("{}", serde_json::to_string_pretty(&file_example).unwrap());
385
386 assert_eq!(file_example, file_expected)
387 }
388
389}