use std::cell::{OnceCell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use super::SchemaError::{self, UnresolvableRef};
use super::Subschema;
use serde_json::Value;
pub struct RefResolver<'a> {
root_value: &'a Value,
cache: RefCell<HashMap<String, Rc<OnceCell<Subschema<'a>>>>>,
}
impl<'a> RefResolver<'a> {
pub fn new(root_value: &'a Value) -> Self {
Self { root_value, cache: RefCell::new(HashMap::new()) }
}
pub fn resolve<'p>(
&self,
pointer: &'p str,
) -> Result<Rc<OnceCell<Subschema<'a>>>, SchemaError<'p>>
where
'a: 'p,
{
if let Some(result) = self.cache.borrow().get(pointer) {
return Ok(Rc::clone(result));
}
let entry = Rc::new(OnceCell::new());
self.cache.borrow_mut().insert(pointer.to_owned(), Rc::clone(&entry));
let resolved_value =
self.root_value.pointer(&pointer[1..]).ok_or(UnresolvableRef(pointer))?;
assert!(
entry.set(Subschema::from_json(resolved_value, self)?).is_ok(),
"Expected the entry to be unset."
);
Ok(Rc::clone(&entry))
}
}
#[cfg(test)]
mod test {
use super::*;
use serde_json::json;
#[test]
fn test_resolve() {
let v = json!({
"$defs": {
"p1": {
"type": "integer"
},
}
});
let resolver = RefResolver::new(&v);
let result = resolver.resolve("#/$defs/p1").unwrap();
assert!(result.get().unwrap().validate(&json!(42)).is_ok());
assert!(result.get().unwrap().validate(&json!("42")).is_err());
assert!(matches!(resolver.resolve("#/$defs/nonexistentnode"), Err(UnresolvableRef(_))));
}
#[test]
fn test_resolve_recursive() {
let v = json!({
"$defs": {
"p1": {
"properties": {
"foo": {
"type": "object",
"$ref": "#/$defs/p2"
}
}
},
"p2": {
"properties": {
"baz": {
"const": 42
}
}
}
}
});
let resolver = RefResolver::new(&v);
let result = resolver.resolve("#/$defs/p1").unwrap();
assert!(result.get().unwrap().validate(&json!({"foo":{"baz": 42}})).is_ok());
assert!(result.get().unwrap().validate(&json!({"foo":{"baz": "42"}})).is_err());
}
#[test]
fn test_resolve_with_cycle() {
let v = json!({
"$defs": {
"p1": {
"properties": {
"foo": {
"type": "integer"
},
"bar": {
"$ref": "#/$defs/p2"
}
}
},
"p2": {
"properties": {
"baz": {
"$ref": "#/$defs/p1"
},
}
}
}
});
let resolver = RefResolver::new(&v);
let result = resolver.resolve("#/$defs/p1").unwrap();
assert!(result
.get()
.unwrap()
.validate(&json!({"foo":42,"bar":{"baz":{"foo":43}}}))
.is_ok());
assert!(result
.get()
.unwrap()
.validate(&json!({"foo":42,"bar":{"baz":{"foo":43.1}}}))
.is_err());
}
}