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,
}
};
}
#[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())
}
}
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 a_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;
}
}