mod change;
mod compare;
mod context;
mod operations;
mod resolve;
mod schema;
mod setops;
use std::fmt::Debug;
pub use change::*;
pub use compare::compare;
#[derive(Clone)]
pub struct JsonPathStack {
top: String,
stack: Vec<String>,
}
impl Debug for JsonPathStack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut out = f.debug_list();
out.entry(&self.top);
self.stack.iter().rev().for_each(|path| {
let _ = out.entry(path);
});
out.finish()
}
}
impl JsonPathStack {
fn new() -> Self {
Self {
top: "#".to_string(),
stack: Vec::new(),
}
}
fn append(&self, segment: &str) -> JsonPathStack {
let Self { top, stack } = self;
Self {
top: format!("{top}/{segment}"),
stack: stack.clone(),
}
}
fn push(&self, path: &str) -> JsonPathStack {
let Self { top, stack } = self;
let mut stack = stack.clone();
stack.push(format!("{top}/$ref"));
Self {
top: path.to_string(),
stack,
}
}
pub fn contains_cycle(&self) -> bool {
self.stack
.iter()
.any(|item| is_path_ancestor_of(&self.top, item))
}
pub fn iter(&self) -> impl Iterator<Item = &String> {
std::iter::once(&self.top).chain(self.stack.iter().rev())
}
}
impl Default for JsonPathStack {
fn default() -> Self {
Self::new()
}
}
fn is_path_ancestor_of(ancestor: &str, path: &str) -> bool {
path.starts_with(ancestor)
&& path
.as_bytes()
.get(ancestor.len())
.is_none_or(|&b| b == b'/')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contains_cycle() {
let stack = JsonPathStack::new()
.push("#/components/schemas/Tree")
.append("properties")
.append("children")
.push("#/components/schemas/Tree");
assert!(stack.contains_cycle());
let stack = JsonPathStack::new()
.push("#/components/schemas/ItemPage")
.append("properties")
.append("items")
.append("items")
.push("#/components/schemas/Item");
assert!(!stack.contains_cycle());
}
#[test]
fn test_is_path_ancestor_of_basics() {
assert!(is_path_ancestor_of(
"#/components/schemas/Item",
"#/components/schemas/Item"
));
assert!(is_path_ancestor_of(
"#/components/schemas/Item",
"#/components/schemas/Item/properties/value"
));
assert!(!is_path_ancestor_of(
"#/components/schemas/Item",
"#/components/schemas/ItemPage"
));
assert!(!is_path_ancestor_of(
"#/components/schemas/Item",
"#/components/schemas/Other"
));
}
}