#![forbid(unsafe_code)]
#![warn(
explicit_outlives_requirements,
semicolon_in_expressions_from_macros,
unreachable_pub,
unused_import_braces,
unused_lifetimes
)]
#![warn(
clippy::allow_attributes_without_reason,
clippy::cargo_common_metadata,
clippy::cast_lossless,
clippy::cloned_instead_of_copied,
clippy::empty_drop,
clippy::empty_line_after_outer_attr,
clippy::equatable_if_let,
clippy::expl_impl_clone_on_copy,
clippy::explicit_deref_methods,
clippy::explicit_into_iter_loop,
clippy::explicit_iter_loop,
clippy::fallible_impl_from,
clippy::flat_map_option,
clippy::if_then_some_else_none,
clippy::inconsistent_struct_constructor,
clippy::large_digit_groups,
clippy::let_underscore_must_use,
clippy::manual_ok_or,
clippy::map_err_ignore,
clippy::map_unwrap_or,
clippy::match_same_arms,
clippy::match_wildcard_for_single_variants,
clippy::missing_inline_in_public_items,
clippy::mod_module_files,
clippy::must_use_candidate,
clippy::needless_continue,
clippy::needless_for_each,
clippy::needless_pass_by_value,
clippy::ptr_as_ptr,
clippy::redundant_closure_for_method_calls,
clippy::ref_binding_to_reference,
clippy::ref_option_ref,
clippy::rest_pat_in_fully_bound_structs,
clippy::undocumented_unsafe_blocks,
clippy::unneeded_field_pattern,
clippy::unseparated_literal_suffix,
clippy::unreadable_literal,
clippy::unused_self,
clippy::use_self
)]
#![warn(clippy::exit)]
#![cfg_attr(not(test), warn(clippy::unwrap_used))]
#![cfg_attr(
not(debug_assertions),
warn(clippy::print_stderr, clippy::print_stdout, clippy::todo)
)]
#![cfg_attr(
not(debug_assertions),
warn(missing_docs, clippy::missing_errors_doc, clippy::missing_panics_doc,)
)]
#![cfg_attr(not(debug_assertions), warn(rustdoc::missing_crate_level_docs))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(html_logo_url = "https://raw.githubusercontent.com/V0ldek/rsonpath/main/img/rsonquery-logo.svg")]
pub mod builder;
pub mod error;
pub mod num;
mod parser;
pub mod str;
use std::{
fmt::{self, Display},
ops::Deref,
};
#[derive(Debug, Clone, Default)]
pub struct Parser {
options: ParserOptions,
}
#[derive(Debug, Clone, Default)]
pub struct ParserBuilder {
options: ParserOptions,
}
#[derive(Debug, Clone)]
struct ParserOptions {
relaxed_whitespace: bool,
}
impl ParserBuilder {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
options: ParserOptions::default(),
}
}
#[inline]
pub fn allow_surrounding_whitespace(&mut self, value: bool) -> &mut Self {
self.options.relaxed_whitespace = value;
self
}
#[inline]
#[must_use]
pub fn build(&self) -> Parser {
Parser {
options: self.options.clone(),
}
}
}
impl ParserOptions {
fn is_leading_whitespace_allowed(&self) -> bool {
self.relaxed_whitespace
}
fn is_trailing_whitespace_allowed(&self) -> bool {
self.relaxed_whitespace
}
}
impl Default for ParserOptions {
#[inline(always)]
fn default() -> Self {
Self {
relaxed_whitespace: false,
}
}
}
impl From<ParserBuilder> for Parser {
#[inline(always)]
fn from(value: ParserBuilder) -> Self {
Self { options: value.options }
}
}
pub type Result<T> = std::result::Result<T, error::ParseError>;
#[inline]
pub fn parse(str: &str) -> Result<JsonPathQuery> {
Parser::default().parse(str)
}
impl Parser {
#[inline]
pub fn parse(&self, str: &str) -> Result<JsonPathQuery> {
crate::parser::parse_json_path_query(str, &self.options)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum Segment {
Child(Selectors),
Descendant(Selectors),
}
#[cfg(feature = "arbitrary")]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
impl<'a> arbitrary::Arbitrary<'a> for Selectors {
#[inline]
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let first = u.arbitrary::<Selector>()?;
let mut rest = u.arbitrary::<Vec<Selector>>()?;
rest.push(first);
Ok(Self::many(rest))
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct Selectors {
inner: Vec<Selector>,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum Selector {
Name(str::JsonString),
Wildcard,
Index(Index),
Slice(Slice),
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Index {
FromStart(num::JsonUInt),
FromEnd(num::JsonNonZeroUInt),
}
#[cfg(feature = "arbitrary")]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
impl<'a> arbitrary::Arbitrary<'a> for Index {
#[inline]
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let num = u.arbitrary::<num::JsonInt>()?;
Ok(Self::from(num))
}
}
impl From<num::JsonInt> for Index {
#[inline]
fn from(value: num::JsonInt) -> Self {
if value.as_i64() >= 0 {
Self::FromStart(value.abs())
} else {
Self::FromEnd(value.abs().try_into().expect("checked for zero already"))
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Step {
Forward(num::JsonUInt),
Backward(num::JsonNonZeroUInt),
}
#[cfg(feature = "arbitrary")]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
impl<'a> arbitrary::Arbitrary<'a> for Step {
#[inline]
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let num = u.arbitrary::<num::JsonInt>()?;
Ok(Self::from(num))
}
}
impl From<num::JsonInt> for Step {
#[inline]
fn from(value: num::JsonInt) -> Self {
if value.as_i64() >= 0 {
Self::Forward(value.abs())
} else {
Self::Backward(value.abs().try_into().expect("checked for zero already"))
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Slice {
start: Index,
end: Option<Index>,
step: Step,
}
pub struct SliceBuilder {
inner: Slice,
}
impl Slice {
const DEFAULT_START: Index = Index::FromStart(num::JsonUInt::ZERO);
const DEFAULT_STEP: Step = Step::Forward(num::JsonUInt::ONE);
#[inline(always)]
#[must_use]
pub fn new(start: Index, end: Option<Index>, step: Step) -> Self {
Self { start, end, step }
}
#[inline(always)]
#[must_use]
pub fn start(&self) -> Index {
self.start
}
#[inline(always)]
#[must_use]
pub fn end(&self) -> Option<Index> {
self.end
}
#[inline(always)]
#[must_use]
pub fn step(&self) -> Step {
self.step
}
}
impl Default for Slice {
#[inline]
fn default() -> Self {
Self {
start: Index::FromStart(0.into()),
end: None,
step: Step::Forward(1.into()),
}
}
}
impl SliceBuilder {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
inner: Slice::default(),
}
}
#[inline]
pub fn with_start<N: Into<num::JsonInt>>(&mut self, start: N) -> &mut Self {
self.inner.start = start.into().into();
self
}
#[inline]
pub fn with_end<N: Into<num::JsonInt>>(&mut self, end: N) -> &mut Self {
self.inner.end = Some(end.into().into());
self
}
#[inline]
pub fn with_step<N: Into<num::JsonInt>>(&mut self, step: N) -> &mut Self {
self.inner.step = step.into().into();
self
}
#[inline]
#[must_use]
pub fn to_slice(&mut self) -> Slice {
self.inner.clone()
}
}
impl From<SliceBuilder> for Slice {
#[inline]
#[must_use]
fn from(value: SliceBuilder) -> Self {
value.inner
}
}
impl Default for SliceBuilder {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct JsonPathQuery {
segments: Vec<Segment>,
}
impl JsonPathQuery {
#[inline(always)]
#[must_use]
pub fn segments(&self) -> &[Segment] {
&self.segments
}
}
impl Segment {
#[inline(always)]
#[must_use]
pub fn selectors(&self) -> &Selectors {
match self {
Self::Child(s) | Self::Descendant(s) => s,
}
}
#[inline(always)]
#[must_use]
pub fn is_child(&self) -> bool {
matches!(self, Self::Child(_))
}
#[inline(always)]
#[must_use]
pub fn is_descendant(&self) -> bool {
matches!(self, Self::Descendant(_))
}
}
impl Selectors {
#[inline(always)]
#[must_use]
pub fn one(selector: Selector) -> Self {
Self { inner: vec![selector] }
}
#[inline]
#[must_use]
pub fn many(vec: Vec<Selector>) -> Self {
assert!(!vec.is_empty(), "cannot create an empty Selectors collection");
Self { inner: vec }
}
#[inline]
#[must_use]
pub fn first(&self) -> &Selector {
&self.inner[0]
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[Selector] {
self
}
}
impl Selector {
#[inline(always)]
#[must_use]
pub const fn is_name(&self) -> bool {
matches!(self, Self::Name(_))
}
#[inline(always)]
#[must_use]
pub const fn is_wildcard(&self) -> bool {
matches!(self, Self::Wildcard)
}
#[inline(always)]
#[must_use]
pub const fn is_index(&self) -> bool {
matches!(self, Self::Index(_))
}
}
impl Index {
#[inline(always)]
#[must_use]
pub const fn is_start_based(&self) -> bool {
matches!(self, Self::FromStart(_))
}
#[inline(always)]
#[must_use]
pub const fn is_end_based(&self) -> bool {
matches!(self, Self::FromEnd(_))
}
}
impl Deref for Selectors {
type Target = [Selector];
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Display for JsonPathQuery {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "$")?;
for s in &self.segments {
write!(f, "{s}")?;
}
Ok(())
}
}
impl Display for Segment {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Child(s) => write!(f, "{s}"),
Self::Descendant(s) => write!(f, "..{s}"),
}
}
}
impl Display for Selectors {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}", self.first())?;
for s in self.inner.iter().skip(1) {
write!(f, ", {s}")?;
}
write!(f, "]")?;
Ok(())
}
}
impl Display for Selector {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Name(n) => write!(f, "'{}'", str::escape(n.unquoted(), str::EscapeMode::SingleQuoted)),
Self::Wildcard => write!(f, "*"),
Self::Index(idx) => write!(f, "{idx}"),
Self::Slice(slice) => write!(f, "{slice}"),
}
}
}
impl Display for Index {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FromStart(idx) => write!(f, "{idx}"),
Self::FromEnd(idx) => write!(f, "-{idx}"),
}
}
}
impl Display for Step {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Forward(idx) => write!(f, "{idx}"),
Self::Backward(idx) => write!(f, "-{idx}"),
}
}
}
impl Display for Slice {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.start != Self::DEFAULT_START {
write!(f, "{}", self.start)?;
}
write!(f, ":")?;
if let Some(end) = self.end {
write!(f, "{end}")?;
}
if self.step != Self::DEFAULT_STEP {
write!(f, ":{}", self.step)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn leading_whitespace_is_disallowed() {
let err = parse(" $").expect_err("should fail");
let display = format!("{err}");
let expected = r"error: query starting with whitespace
$
^^ leading whitespace is disallowed
(bytes 0-1)
suggestion: did you mean `$` ?
";
assert_eq!(display, expected);
}
#[test]
fn trailing_whitespace_is_disallowed() {
let err = parse("$ ").expect_err("should fail");
let display = format!("{err}");
let expected = r"error: query ending with whitespace
$
^^ trailing whitespace is disallowed
(bytes 1-2)
suggestion: did you mean `$` ?
";
assert_eq!(display, expected);
}
mod name_selector {
use super::*;
use pretty_assertions::assert_eq;
use test_case::test_case;
fn parse_single_quoted_name_selector(src: &str) -> Result<JsonPathQuery> {
let query_string = format!("$['{src}']");
parse(&query_string)
}
#[test_case("", ""; "empty")]
#[test_case("dog", "dog"; "ascii")]
#[test_case(r"\\", r"\"; "backslash")]
#[test_case("unescaped 🔥 fire emoji", "unescaped 🔥 fire emoji"; "unescaped emoji")]
#[test_case(r"escape \b backspace", "escape \u{0008} backspace"; "BS escape")]
#[test_case(r"escape \t tab", "escape \t tab"; "HT escape")]
#[test_case(r"escape \n endln", "escape \n endln"; "LF escape")]
#[test_case(r"escape \f formfeed", "escape \u{000C} formfeed"; "FF escape")]
#[test_case(r"escape \r carriage", "escape \r carriage"; "CR escape")]
#[test_case(r#"escape \' apost"#, r"escape ' apost"; "apostrophe escape")]
#[test_case(r"escape \/ slash", r"escape / slash"; "slash escape")]
#[test_case(r"escape \\ backslash", r"escape \ backslash"; "backslash escape")]
#[test_case(r"escape \u2112 script L", "escape â„’ script L"; "U+2112 Script Capital L escape")]
#[test_case(r"escape \u211269 script L", "escape â„’69 script L"; "U+2112 Script Capital L escape followed by digits")]
#[test_case(r"escape \u21a7 bar down arrow", "escape ↧ bar down arrow"; "U+21a7 Downwards Arrow From Bar (lowercase hex)")]
#[test_case(r"escape \u21A7 bar down arrow", "escape ↧ bar down arrow"; "U+21A7 Downwards Arrow From Bar (uppercase hex)")]
#[test_case(r"escape \ud83d\udd25 fire emoji", "escape 🔥 fire emoji"; "U+1F525 fire emoji escape (lowercase hex)")]
#[test_case(r"escape \uD83D\uDD25 fire emoji", "escape 🔥 fire emoji"; "U+1F525 fire emoji escape (uppercase hex)")]
fn parse_correct_single_quoted_name(src: &str, expected: &str) {
let res = parse_single_quoted_name_selector(src).expect("should successfully parse");
match res.segments().first() {
Some(Segment::Child(selectors)) => match selectors.first() {
Selector::Name(name) => assert_eq!(name.unquoted(), expected),
_ => panic!("expected to parse a single name selector, got {res:?}"),
},
_ => panic!("expected to parse a single name selector, got {res:?}"),
}
}
#[test]
fn parse_double_quoted_name_with_escaped_double_quote() {
let query_string = r#"$["escape \" quote"]"#;
let res = parse(query_string).expect("should successfully parse");
match res.segments().first() {
Some(Segment::Child(selectors)) => match selectors.first() {
Selector::Name(name) => assert_eq!(name.unquoted(), "escape \" quote"),
_ => panic!("expected to parse a single name selector, got {res:?}"),
},
_ => panic!("expected to parse a single name selector, got {res:?}"),
}
}
}
}