use serde_json::Value;
use std::borrow::Cow;
#[inline]
pub fn normalize_to_json_pointer(path: &str) -> Cow<'_, str> {
if path.is_empty() {
return Cow::Borrowed("");
}
if path.starts_with("#/") {
let stripped = &path[1..];
if !stripped.contains("//") {
return Cow::Borrowed(stripped);
}
}
if path.starts_with('/') && !path.contains("//") {
return if path == "/" {
Cow::Borrowed("")
} else {
Cow::Borrowed(path)
};
}
let mut normalized = String::with_capacity(path.len() + 1);
let source = if path.starts_with("#/") {
&path[1..]
} else if !path.starts_with('/') {
normalized.push('/');
path
} else {
path
};
let mut prev_slash = normalized.ends_with('/');
for ch in source.chars() {
let c = if ch == '.' && !path.starts_with('/') && !path.starts_with('#') {
'/'
} else {
ch
};
if c == '/' {
if !prev_slash {
normalized.push('/');
}
prev_slash = true;
} else {
normalized.push(c);
prev_slash = false;
}
}
if normalized == "/" {
Cow::Borrowed("")
} else {
Cow::Owned(normalized)
}
}
#[inline]
pub fn schema_path_to_data_pointer(path: &str) -> Cow<'_, str> {
if path.is_empty() {
return Cow::Borrowed("");
}
let no_hash = if path.starts_with('#') {
&path[1..]
} else {
path
};
let clean_path = if no_hash.starts_with('/') {
&no_hash[1..]
} else {
no_hash
};
if !clean_path.contains("properties/") && clean_path != "properties" {
if path.starts_with('/') && path.len() == clean_path.len() + 1 {
return Cow::Borrowed(path);
}
let mut s = String::with_capacity(clean_path.len() + 1);
s.push('/');
s.push_str(clean_path);
return Cow::Owned(s);
}
let parts = clean_path.split('/');
let mut s = String::with_capacity(clean_path.len() + 1);
for part in parts {
if part.is_empty() || part == "properties" {
continue;
}
s.push('/');
s.push_str(part);
}
if s.is_empty() {
Cow::Borrowed("")
} else {
Cow::Owned(s)
}
}
#[inline]
pub fn dot_notation_to_schema_pointer(path: &str) -> String {
if path.starts_with('#') || path.starts_with('/') {
return path.to_string();
}
if path.starts_with("properties.") || path.contains(".properties.") {
return format!("#/{}", path.replace('.', "/"));
}
let parts: Vec<&str> = path.split('.').collect();
if parts.is_empty() {
return "#/".to_string();
}
let mut result = String::from("#");
for (i, part) in parts.iter().enumerate() {
if part.eq(&"properties") {
continue;
}
if i > 0 && !path.starts_with('$') {
result.push_str("/properties");
}
result.push_str("/");
result.push_str(part);
}
result
}
#[inline]
pub fn pointer_to_dot_notation(path: &str) -> String {
if path.is_empty() {
return String::new();
}
if !path.starts_with('#') && !path.starts_with('/') {
return path.to_string();
}
let clean_path = if path.starts_with("#/") {
&path[2..]
} else if path.starts_with('/') {
&path[1..]
} else if path.starts_with('#') {
&path[1..]
} else {
path
};
clean_path.replace('/', ".")
}
pub fn canonicalize_schema_path(path: &str) -> Cow<'_, str> {
if path.is_empty() {
return Cow::Borrowed("");
}
if path.starts_with("/$") && !path.contains('.') && !path.contains("//") {
return Cow::Borrowed(path);
}
let is_system = path.starts_with('$') || path.starts_with("/$") || path.starts_with("#/$");
let clean_path = if path.starts_with("#/") {
&path[2..]
} else if path.starts_with('/') {
&path[1..]
} else if path.starts_with('#') {
&path[1..]
} else {
path
};
if !is_system
&& !clean_path.contains('.')
&& !clean_path.contains('/')
&& !clean_path.is_empty()
{
if path.starts_with('/') && path.len() == clean_path.len() + 1 {
return Cow::Borrowed(path);
}
let mut s = String::with_capacity(clean_path.len() + 1);
s.push('/');
s.push_str(clean_path);
return Cow::Owned(s);
}
if clean_path.starts_with("properties/")
|| clean_path.starts_with("properties.")
|| clean_path.contains("/properties/")
|| clean_path.contains(".properties.")
{
let mut s = String::with_capacity(clean_path.len() + 1);
s.push('/');
for c in clean_path.chars() {
if c == '.' {
s.push('/');
} else {
s.push(c);
}
}
if s == path {
return Cow::Borrowed(path);
} else {
return Cow::Owned(s);
}
}
let mut result = String::with_capacity(path.len() * 2);
result.push('/');
let parts = clean_path.split(|c| c == '/' || c == '.');
let mut first = true;
for part in parts {
if part.is_empty() || part == "properties" {
continue;
}
if !first && !is_system {
result.push_str("properties/");
}
result.push_str(part);
result.push('/');
first = false;
}
if result.len() > 1 {
result.pop(); }
if result == path {
Cow::Borrowed(path)
} else {
Cow::Owned(result)
}
}
#[inline]
pub fn get_value_by_pointer<'a>(data: &'a Value, pointer: &str) -> Option<&'a Value> {
if pointer.is_empty() {
Some(data)
} else {
data.pointer(pointer)
}
}
#[inline]
pub fn get_value_by_pointer_without_properties<'a>(
data: &'a Value,
pointer: &str,
) -> Option<&'a Value> {
if pointer.is_empty() {
Some(data)
} else {
data.pointer(&pointer.replace("properties/", ""))
}
}
pub fn get_values_by_pointers<'a>(data: &'a Value, pointers: &[String]) -> Vec<Option<&'a Value>> {
pointers
.iter()
.map(|pointer| get_value_by_pointer(data, pointer))
.collect()
}
#[inline]
pub fn get_array_element<'a>(data: &'a Value, index: usize) -> Option<&'a Value> {
data.as_array()?.get(index)
}
#[inline]
pub fn get_array_element_by_pointer<'a>(
data: &'a Value,
pointer: &str,
index: usize,
) -> Option<&'a Value> {
get_value_by_pointer(data, pointer)?.as_array()?.get(index)
}
#[derive(Debug, Clone)]
pub struct ArrayMetadata {
pub pointer: String,
pub length: usize,
pub column_names: Vec<String>,
pub is_uniform: bool,
}
impl ArrayMetadata {
pub fn build(data: &Value, pointer: &str) -> Option<Self> {
let array = get_value_by_pointer(data, pointer)?.as_array()?;
let length = array.len();
if length == 0 {
return Some(ArrayMetadata {
pointer: pointer.to_string(),
length: 0,
column_names: Vec::new(),
is_uniform: true,
});
}
let first_element = &array[0];
let column_names = if let Value::Object(obj) = first_element {
obj.keys().cloned().collect()
} else {
Vec::new()
};
let is_uniform = if !column_names.is_empty() {
array.iter().all(|elem| {
if let Value::Object(obj) = elem {
obj.keys().len() == column_names.len()
&& column_names.iter().all(|col| obj.contains_key(col))
} else {
false
}
})
} else {
let first_type = std::mem::discriminant(first_element);
array
.iter()
.all(|elem| std::mem::discriminant(elem) == first_type)
};
Some(ArrayMetadata {
pointer: pointer.to_string(),
length,
column_names,
is_uniform,
})
}
#[inline]
pub fn get_column_value<'a>(
&self,
data: &'a Value,
row_index: usize,
column: &str,
) -> Option<&'a Value> {
if !self.is_uniform || row_index >= self.length {
return None;
}
get_array_element_by_pointer(data, &self.pointer, row_index)?
.as_object()?
.get(column)
}
#[inline]
pub fn is_valid_index(&self, index: usize) -> bool {
index < self.length
}
}