pub mod automaton;
pub mod builder;
pub mod error;
mod parser;
use aligners::{alignment, AlignedBytes, AlignedSlice};
use cfg_if::cfg_if;
use log::*;
use std::fmt::{self, Display};
cfg_if! {
if #[cfg(feature = "simd")] {
pub type LabelAlignment = alignment::SimdBlock;
}
else {
pub type LabelAlignment = alignment::One;
}
}
pub struct Label {
label: AlignedBytes<LabelAlignment>,
label_with_quotes: AlignedBytes<LabelAlignment>,
}
impl std::fmt::Debug for Label {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
r#"{}"#,
std::str::from_utf8(&self.label_with_quotes).unwrap_or("[invalid utf8]")
)
}
}
impl Clone for Label {
#[inline]
fn clone(&self) -> Self {
let label_clone = AlignedBytes::from(self.label.as_ref());
let quoted_clone = AlignedBytes::from(self.label_with_quotes.as_ref());
Self {
label: label_clone,
label_with_quotes: quoted_clone,
}
}
}
impl Label {
#[must_use]
#[inline]
pub fn new(label: &str) -> Self {
let bytes = label.as_bytes();
let without_quotes = AlignedBytes::<LabelAlignment>::from(bytes);
let mut with_quotes = AlignedBytes::<LabelAlignment>::new_zeroed(bytes.len() + 2);
with_quotes[0] = b'"';
with_quotes[1..bytes.len() + 1].copy_from_slice(bytes);
with_quotes[bytes.len() + 1] = b'"';
Self {
label: without_quotes,
label_with_quotes: with_quotes,
}
}
#[must_use]
#[inline(always)]
pub fn bytes(&self) -> &AlignedSlice<LabelAlignment> {
&self.label
}
#[must_use]
#[inline(always)]
pub fn bytes_with_quotes(&self) -> &AlignedSlice<LabelAlignment> {
&self.label_with_quotes
}
#[must_use]
#[inline(always)]
pub fn display(&self) -> impl Display + '_ {
std::str::from_utf8(&self.label).unwrap_or("[invalid utf8]")
}
}
impl std::ops::Deref for Label {
type Target = AlignedSlice<LabelAlignment>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.bytes()
}
}
impl PartialEq<Self> for Label {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.label == other.label
}
}
impl Eq for Label {}
impl PartialEq<Label> for [u8] {
#[inline(always)]
fn eq(&self, other: &Label) -> bool {
self == &other.label
}
}
impl PartialEq<Label> for &[u8] {
#[inline(always)]
fn eq(&self, other: &Label) -> bool {
*self == &other.label
}
}
impl PartialEq<[u8]> for Label {
#[inline(always)]
fn eq(&self, other: &[u8]) -> bool {
&self.label == other
}
}
impl PartialEq<&[u8]> for Label {
#[inline(always)]
fn eq(&self, other: &&[u8]) -> bool {
&self.label == *other
}
}
impl std::hash::Hash for Label {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let slice: &[u8] = &self.label;
slice.hash(state);
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum JsonPathQueryNode {
Root(Option<Box<JsonPathQueryNode>>),
Child(Label, Option<Box<JsonPathQueryNode>>),
AnyChild(Option<Box<JsonPathQueryNode>>),
Descendant(Label, Option<Box<JsonPathQueryNode>>),
}
use JsonPathQueryNode::*;
use self::error::ParserError;
impl JsonPathQueryNode {
#[must_use]
#[inline(always)]
pub fn child(&self) -> Option<&Self> {
match self {
Root(node) | Child(_, node) | AnyChild(node) | Descendant(_, node) => node.as_deref(),
}
}
#[must_use]
#[inline(always)]
pub fn iter(&self) -> JsonPathQueryIterator {
JsonPathQueryIterator { node: Some(self) }
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct JsonPathQuery {
root: Box<JsonPathQueryNode>,
}
pub struct JsonPathQueryIterator<'a> {
node: Option<&'a JsonPathQueryNode>,
}
impl<'a> Iterator for JsonPathQueryIterator<'a> {
type Item = &'a JsonPathQueryNode;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let result = self.node;
if let Some(node) = result {
self.node = node.child()
}
result
}
}
impl JsonPathQuery {
#[must_use]
#[inline(always)]
pub fn root(&self) -> &JsonPathQueryNode {
self.root.as_ref()
}
#[inline(always)]
pub fn parse(query_string: &str) -> Result<Self, ParserError> {
self::parser::parse_json_path_query(query_string)
}
#[inline]
#[must_use]
pub fn new(node: Box<JsonPathQueryNode>) -> Self {
let root = if node.is_root() {
node
} else {
info!("Implicitly using the Root expression (`$`) at the start of the query.");
Box::new(Root(Some(node)))
};
Self { root }
}
}
impl Display for JsonPathQuery {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.root.as_ref())
}
}
impl Display for JsonPathQueryNode {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Root(_) => write!(f, "$"),
Child(label, _) => write!(f, "['{}']", label.display()),
AnyChild(_) => write!(f, "[*]"),
Descendant(label, _) => write!(f, "..['{}']", label.display()),
}?;
if let Some(child) = self.child() {
write!(f, "{child}")
} else {
Ok(())
}
}
}
pub trait JsonPathQueryNodeType {
fn is_root(&self) -> bool;
fn is_descendant(&self) -> bool;
fn is_child(&self) -> bool;
fn label(&self) -> Option<&Label>;
}
impl JsonPathQueryNodeType for JsonPathQueryNode {
#[inline(always)]
fn is_root(&self) -> bool {
matches!(self, Root(_))
}
#[inline(always)]
fn is_descendant(&self) -> bool {
matches!(self, Descendant(_, _))
}
#[inline(always)]
fn is_child(&self) -> bool {
matches!(self, Child(_, _))
}
#[inline(always)]
fn label(&self) -> Option<&Label> {
match self {
Child(label, _) | Descendant(label, _) => Some(label),
Root(_) | AnyChild(_) => None,
}
}
}
impl<T: std::ops::Deref<Target = JsonPathQueryNode>> JsonPathQueryNodeType for Option<T> {
#[inline(always)]
fn is_root(&self) -> bool {
self.as_ref().map_or(false, |x| x.is_root())
}
#[inline(always)]
fn is_descendant(&self) -> bool {
self.as_ref().map_or(false, |x| x.is_descendant())
}
#[inline(always)]
fn is_child(&self) -> bool {
self.as_ref().map_or(false, |x| x.is_child())
}
#[inline(always)]
fn label(&self) -> Option<&Label> {
self.as_ref().and_then(|x| x.label())
}
}
#[cfg(test)]
mod tests {
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
use super::*;
#[test]
fn label_equality() {
let label1 = Label::new("dog");
let label2 = Label::new("dog");
assert_eq!(label1, label2);
}
#[test]
fn label_inequality() {
let label1 = Label::new("dog");
let label2 = Label::new("doc");
assert_ne!(label1, label2);
}
#[test]
fn label_hash() {
let label1 = Label::new("dog");
let label2 = Label::new("dog");
let mut s1 = DefaultHasher::new();
label1.hash(&mut s1);
let h1 = s1.finish();
let mut s2 = DefaultHasher::new();
label2.hash(&mut s2);
let h2 = s2.finish();
assert_eq!(h1, h2);
}
}