use super::{Schema, SchemaValue};
pub fn ref_name(ref_str: &str) -> &str {
ref_str.rsplit('/').next().unwrap_or(ref_str)
}
pub fn resolve_ref<'a>(schema: &'a Schema, root: &'a Schema) -> &'a Schema {
if let Some(ref ref_str) = schema.ref_
&& let Some(path) = ref_str.strip_prefix("#/")
{
let Ok(root_value) = serde_json::to_value(root) else {
return schema;
};
let mut current = &root_value;
for segment in path.split('/') {
let decoded = segment.replace("~1", "/").replace("~0", "~");
match current.get(&decoded) {
Some(next) => current = next,
None => return schema,
}
}
let _ = current;
return schema;
}
schema
}
pub fn navigate_pointer<'a>(
schema: &'a SchemaValue,
root: &'a SchemaValue,
pointer: &str,
) -> Result<&'a SchemaValue, String> {
let path = pointer.strip_prefix('/').unwrap_or(pointer);
if path.is_empty() {
return Ok(schema);
}
let mut current = resolve_schema_value_ref(schema, root);
let mut segments = path.split('/').peekable();
while let Some(segment) = segments.next() {
let decoded = segment.replace("~1", "/").replace("~0", "~");
current = resolve_schema_value_ref(current, root);
let Some(schema) = current.as_schema() else {
return Err(format!(
"cannot resolve segment '{decoded}' in pointer '{pointer}'"
));
};
if is_map_keyword(&decoded) {
let key_segment = segments
.next()
.ok_or_else(|| format!("expected key after '{decoded}' in pointer '{pointer}'"))?;
let key = key_segment.replace("~1", "/").replace("~0", "~");
if let Some(entry) = schema.get_map_entry(&decoded, &key) {
current = entry;
continue;
}
return Err(format!(
"cannot resolve segment '{key}' in '{decoded}' in pointer '{pointer}'"
));
}
if is_array_keyword(&decoded) {
let idx_segment = segments.next().ok_or_else(|| {
format!("expected index after '{decoded}' in pointer '{pointer}'")
})?;
let idx: usize = idx_segment.parse().map_err(|_| {
format!("expected numeric index after '{decoded}', got '{idx_segment}'")
})?;
if let Some(entry) = schema.get_array_entry(&decoded, idx) {
current = entry;
continue;
}
return Err(format!(
"index {idx} out of bounds in '{decoded}' in pointer '{pointer}'"
));
}
if let Some(sv) = schema.get_keyword(&decoded) {
current = sv;
continue;
}
if let Some(sv) = schema.get_map_entry_by_pointer_segment(&decoded) {
current = sv;
continue;
}
if let Ok(idx) = decoded.parse::<usize>() {
let found = ["allOf", "anyOf", "oneOf", "prefixItems"]
.iter()
.find_map(|kw| schema.get_array_entry(kw, idx));
if let Some(entry) = found {
current = entry;
continue;
}
}
return Err(format!(
"cannot resolve segment '{decoded}' in pointer '{pointer}'"
));
}
Ok(resolve_schema_value_ref(current, root))
}
fn is_map_keyword(segment: &str) -> bool {
matches!(
segment,
"properties" | "patternProperties" | "$defs" | "dependentSchemas"
)
}
fn is_array_keyword(segment: &str) -> bool {
matches!(segment, "allOf" | "anyOf" | "oneOf" | "prefixItems")
}
fn resolve_schema_value_ref<'a>(sv: &'a SchemaValue, root: &'a SchemaValue) -> &'a SchemaValue {
let Some(schema) = sv.as_schema() else {
return sv;
};
if let Some(ref ref_str) = schema.ref_
&& let Some(path) = ref_str.strip_prefix("#/")
{
let mut current = root;
let mut segments = path.split('/').peekable();
while let Some(segment) = segments.next() {
let decoded = segment.replace("~1", "/").replace("~0", "~");
let Some(inner) = current.as_schema() else {
return sv;
};
if is_map_keyword(&decoded) {
let Some(key_segment) = segments.next() else {
return sv;
};
let key = key_segment.replace("~1", "/").replace("~0", "~");
match inner.get_map_entry(&decoded, &key) {
Some(n) => current = n,
None => return sv,
}
continue;
}
if is_array_keyword(&decoded) {
let Some(idx_segment) = segments.next() else {
return sv;
};
let Ok(idx) = idx_segment.parse::<usize>() else {
return sv;
};
match inner.get_array_entry(&decoded, idx) {
Some(n) => current = n,
None => return sv,
}
continue;
}
if let Some(n) = inner.get_keyword(&decoded) {
current = n;
continue;
}
if let Some(n) = inner.get_map_entry_by_pointer_segment(&decoded) {
current = n;
continue;
}
return sv;
}
return current;
}
sv
}