use serde_json::Value;
pub const COMBINATORS: [&str; 3] = ["oneOf", "anyOf", "allOf"];
pub fn resolve_ref<'d>(root: &'d Value, pointer: &str) -> Option<&'d Value> {
let rest = pointer.strip_prefix('#')?;
if rest.is_empty() {
return Some(root);
}
let mut node = root;
for token in rest.strip_prefix('/')?.split('/') {
let token = token.replace("~1", "/").replace("~0", "~");
node = match node {
Value::Object(map) => map.get(&token)?,
Value::Array(items) => items.get(token.parse::<usize>().ok()?)?,
_ => return None,
};
}
Some(node)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn pointers_resolve_and_unfollowable_ones_say_so() {
let root = json!({
"$defs": {"A": {"type": "string"}, "a/b": {"type": "number"}, "c~d": {"x": 1}},
"arr": [{"first": true}],
});
assert_eq!(resolve_ref(&root, "#"), Some(&root), "the root itself");
assert_eq!(
resolve_ref(&root, "#/$defs/A"),
Some(&json!({"type": "string"}))
);
assert_eq!(
resolve_ref(&root, "#/$defs/a~1b"),
Some(&json!({"type": "number"})),
"~1 is an escaped slash, not a path separator"
);
assert_eq!(resolve_ref(&root, "#/$defs/c~0d"), Some(&json!({"x": 1})));
assert_eq!(
resolve_ref(&root, "#/arr/0"),
Some(&json!({"first": true})),
"array indices are pointer tokens too"
);
for unfollowable in [
"#/$defs/Missing", "https://example.invalid/s#/A", "$defs/A", "#/$defs/A/nope", "#/arr/9", ] {
assert_eq!(
resolve_ref(&root, unfollowable),
None,
"{unfollowable} must read as could-not-follow"
);
}
}
}