#![allow(unused_macros)]
use std::borrow::Cow;
use std::collections::VecDeque;
use std::fmt::Display;
use std::ops::Add;
const PATH_SEPARATOR: char = '.';
#[derive(Debug, Clone, PartialEq)]
pub enum JsonPathComponent<'a> {
Root,
NameSelector(Cow<'a, str>),
WildcardSelector,
IndexSelector(usize),
RangeSelector(usize, usize),
}
impl<'a> Display for JsonPathComponent<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Root => write!(f, "$"),
Self::NameSelector(s) => write!(f, "['{}']", s),
Self::WildcardSelector => write!(f, "[*]"),
Self::IndexSelector(i) => write!(f, "[{}]", i),
Self::RangeSelector(i, j) => write!(f, "[{}..{}]", i, j),
}
}
}
macro_rules! is_index_selector {
($comp : expr) => {
match $comp {
JsonPathComponent::IndexSelector(_) => true,
_ => false,
}
};
}
macro_rules! is_root_selector {
($comp : expr) => {
match $comp {
JsonPathComponent::Root => true,
_ => false,
}
};
}
macro_rules! is_wildcard_selector {
($comp : expr) => {
match $comp {
JsonPathComponent::WildcardSelector => true,
_ => false,
}
};
}
macro_rules! is_name_selector {
($comp : expr) => {
match $comp {
JsonPathComponent::NameSelector(_) => true,
_ => false,
}
};
}
macro_rules! is_range_selector {
($comp : expr) => {
match $comp {
JsonPathComponent::RangeSelector(_, _) => true,
_ => false,
}
};
}
#[derive(Debug)]
pub struct JsonPath<'a> {
components: Vec<JsonPathComponent<'a>>,
}
impl<'a> JsonPath<'a> {
pub fn new_partial() -> Self {
JsonPath { components: vec![] }
}
pub fn new() -> Self {
JsonPath {
components: vec![JsonPathComponent::Root],
}
}
pub fn is_empty(&self) -> bool {
self.components.is_empty()
}
pub fn is_partial(&self) -> bool {
if !self.is_empty() {
self.components[0] != JsonPathComponent::Root
} else {
true
}
}
pub fn as_string(&self) -> Cow<'a, str> {
Cow::Owned(
self.components
.iter()
.map(|s| s.to_string())
.collect::<Vec<String>>()
.join("."),
)
}
pub fn push_str_selector(&mut self, name: &str) {
self.components
.push(JsonPathComponent::NameSelector(Cow::Owned(String::from(
name.replace("\"", ""),
))));
}
pub fn push_index_select(&mut self, index: usize) {
self.components
.push(JsonPathComponent::IndexSelector(index));
}
pub fn push_range_selector(&mut self, start: usize, end: usize) {
self.components
.push(JsonPathComponent::RangeSelector(start, end));
}
pub fn push_wildcard_selector(&mut self) {
self.components.push(JsonPathComponent::WildcardSelector);
}
pub fn push(&mut self, component: JsonPathComponent<'a>) {
self.components.push(component);
}
pub fn pop(&mut self) -> Option<JsonPathComponent<'a>> {
self.components.pop()
}
pub fn is_array_path(&self) -> bool {
if self.is_empty() {
return false;
}
is_index_selector!(self.components.last().unwrap())
}
pub fn len(&self) -> usize {
self.components.len()
}
pub fn matches_strict(&self, rhs: &JsonPath<'a>) -> bool {
if self.is_empty() && rhs.is_empty() {
return true;
}
if self.len() != rhs.len() {
return false;
};
self.components
.iter()
.zip(rhs.components.iter())
.fold(true, |acc, comps| acc && comps.0 == comps.1)
}
pub fn matches(&self, rhs: &JsonPath<'a>) -> bool {
if self.is_empty() && rhs.is_empty() {
return true;
}
if self.len() != rhs.len() {
return false;
};
self.components
.iter()
.zip(rhs.components.iter())
.fold(true, |acc, comp| acc && Self::match_path_components(comp))
}
fn match_path_components(pair: (&JsonPathComponent, &JsonPathComponent)) -> bool {
match pair {
(JsonPathComponent::IndexSelector(i), JsonPathComponent::IndexSelector(j)) => i == j,
(JsonPathComponent::IndexSelector(i), JsonPathComponent::RangeSelector(j, k)) => {
j <= i && i <= k
}
(JsonPathComponent::RangeSelector(j, k), JsonPathComponent::IndexSelector(i)) => {
j <= i && i <= k
}
(JsonPathComponent::WildcardSelector, JsonPathComponent::IndexSelector(_)) => true,
(JsonPathComponent::IndexSelector(_), JsonPathComponent::WildcardSelector) => true,
(a, b) => a == b,
}
}
}
impl<'a> Display for JsonPath<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_string())
}
}
impl<'a> Add<&JsonPath<'a>> for JsonPath<'a> {
type Output = Self;
fn add(mut self, rhs: &JsonPath<'a>) -> Self {
if !self.is_partial() && !rhs.is_partial() {
panic!("attempted to concatenate two rooted paths")
}
if self.is_partial() && !rhs.is_partial() {
panic!("attempted to concatenate a rooted path to a partial path")
}
rhs.components
.iter()
.for_each(|c| self.components.push(c.clone()));
self
}
}
#[cfg(test)]
mod tests {
use crate::paths::{JsonPath, JsonPathComponent};
#[test]
fn a_new_partial_path_should_be_partial() {
let path = JsonPath::new_partial();
assert!(path.is_partial())
}
#[test]
fn a_new_partial_path_should_have_an_empty_representation() {
let path = JsonPath::new_partial();
assert_eq!("".to_string(), path.as_string())
}
#[test]
fn a_new_partial_path_should_be_empty() {
let path = JsonPath::new_partial();
assert!(path.is_empty())
}
#[test]
fn a_new_rooted_path_should_not_be_partial() {
let path = JsonPath::new();
assert!(!path.is_partial())
}
#[test]
fn a_new_rooted_path_should_have_the_correct_representation() {
let path = JsonPath::new();
assert_eq!("$".to_string(), path.as_string())
}
#[test]
fn a_new_rooted_path_should_not_be_empty() {
let path = JsonPath::new();
assert!(!path.is_empty())
}
#[test]
fn simple_paths_should_have_correct_representations() {
let mut path = JsonPath::new();
path.push_str_selector("a");
path.push_str_selector("b");
path.push_str_selector("c");
path.push_str_selector("d");
assert_eq!(&path.as_string(), "$.['a'].['b'].['c'].['d']")
}
#[test]
fn complex_paths_should_have_correct_representations() {
let mut path = JsonPath::new();
path.push_str_selector("array");
path.push_wildcard_selector();
path.push_index_select(4);
path.push_range_selector(6, 7);
assert_eq!(path.as_string(), "$.['array'].[*].[4].[6..7]")
}
#[test]
fn popping_elements_should_correctly_alter_representation() {
let mut path = JsonPath::new();
path.push_str_selector("a");
path.push_str_selector("b");
path.push_str_selector("c");
path.push_str_selector("d");
path.pop();
path.pop();
assert_eq!(&path.as_string(), "$.['a'].['b']")
}
#[test]
fn array_paths_should_be_identified_as_such() {
let mut path = JsonPath::new();
path.push_str_selector("a");
path.push_index_select(4);
assert!(path.is_array_path())
}
#[test]
fn root_and_partial_paths_can_be_concatenated_correctly() {
let mut root = JsonPath::new();
let mut partial = JsonPath::new_partial();
partial.push_str_selector("a");
root = root + &partial;
assert_eq!(root.as_string(), "$.['a']")
}
#[test]
#[should_panic]
fn concatenating_two_rooted_paths_should_panic() {
let root1 = JsonPath::new();
let root2 = JsonPath::new();
let _combined = root1 + &root2;
}
#[test]
#[should_panic]
fn concatenating_a_root_path_to_a_partial_should_panic() {
let partial = JsonPath::new_partial();
let root = JsonPath::new();
let _combined = partial + &root;
}
#[test]
fn empty_paths_should_strictly_match() {
let left = JsonPath::new();
let right = JsonPath::new();
assert!(left.matches_strict(&right))
}
#[test]
fn complex_paths_should_strictly_match() {
let mut left = JsonPath::new();
let mut right = JsonPath::new();
left.push_str_selector("a");
right.push_str_selector("a");
left.push_index_select(6);
right.push_index_select(6);
left.push_range_selector(4, 5);
right.push_range_selector(4, 5);
assert!(left.matches_strict(&right));
assert_eq!(left.to_string(), right.to_string())
}
#[test]
fn slightly_different_complex_paths_should_not_strictly_match() {
let mut left = JsonPath::new();
let mut right = JsonPath::new();
left.push_str_selector("a");
right.push_str_selector("a");
left.push_index_select(6);
right.push_index_select(6);
left.push_range_selector(4, 5);
right.push_range_selector(3, 5);
assert!(!left.matches_strict(&right));
assert_ne!(left.to_string(), right.to_string())
}
#[test]
fn partial_paths_should_match_strictly() {
let mut left = JsonPath::new_partial();
let mut right = JsonPath::new_partial();
left.push_str_selector("a");
right.push_str_selector("a");
left.push_wildcard_selector();
right.push_wildcard_selector();
assert!(left.matches(&right));
assert_eq!(left.to_string(), right.to_string())
}
#[test]
fn indexes_should_match_on_ranges() {
let mut left = JsonPath::new();
let mut right = JsonPath::new();
left.push_range_selector(0, 4);
right.push_index_select(3);
assert!(left.matches(&right));
let mut left = JsonPath::new();
let mut right = JsonPath::new();
left.push_range_selector(0, 4);
right.push_index_select(3);
assert!(right.matches(&left))
}
#[test]
fn indexes_should_not_match_outside_of_ranges() {
let mut left = JsonPath::new();
let mut right = JsonPath::new();
left.push_range_selector(0, 4);
right.push_index_select(6);
assert!(!left.matches(&right));
let mut left = JsonPath::new();
let mut right = JsonPath::new();
left.push_range_selector(0, 4);
right.push_index_select(6);
assert!(!right.matches(&left))
}
}