Skip to content

Compiled path test suite #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 44 additions & 17 deletions jsonpath-ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,23 @@ macro_rules! terminating_from_pest {
use super::parse::{JSPathParser, Rule};
#[cfg(feature = "compiled-path")]
use crate::syn_parse::parse_impl::{
ParseUtilsExt, parse_bool, parse_float, validate_function_name, validate_js_int,
validate_js_str, validate_member_name_shorthand,
parse_bool, parse_float, validate_js_int, validate_js_str,
validate_member_name_shorthand, ParseUtilsExt,
};
use derive_new::new;
use from_pest::{ConversionError, FromPest, Void};
use pest::Parser;
use pest::iterators::{Pair, Pairs};
use pest::Parser;
use pest_ast::FromPest;
use proc_macro2::Span;
#[allow(unused_imports)]
use syn::LitBool;
use quote::ToTokens;
#[cfg(feature = "compiled-path")]
use syn::parse::ParseStream;
use syn::punctuated::Punctuated;
use syn::token::Bracket;
use syn::{Ident, Token, token};
#[allow(unused_imports)]
use syn::LitBool;
use syn::{token, Token};
#[cfg(feature = "compiled-path")]
use syn_derive::Parse;

Expand Down Expand Up @@ -611,12 +612,12 @@ impl<'pest> FromPest<'pest> for CompOp {
}

#[derive(Debug, new, PartialEq)]
#[cfg_attr(feature = "compiled-path", derive(Parse))]
// #[cfg_attr(feature = "compiled-path", derive(Parse))]
pub struct FunctionExpr {
pub(crate) name: FunctionName,
#[cfg_attr(feature = "compiled-path", syn(parenthesized))]
// #[cfg_attr(feature = "compiled-path", syn(parenthesized))]
pub(crate) paren: token::Paren,
#[cfg_attr(feature = "compiled-path", syn(in = paren))]
// #[cfg_attr(feature = "compiled-path", syn(in = paren))]
// #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestIgnoredPunctuated::parse_terminated(i)))]
pub(crate) args: PestIgnoredPunctuated<FunctionArgument, Token![,]>,
}
Expand Down Expand Up @@ -650,9 +651,27 @@ impl<'pest> from_pest::FromPest<'pest> for FunctionExpr {

#[derive(Debug, new, PartialEq)]
#[cfg_attr(feature = "compiled-path", derive(Parse))]
pub struct FunctionName {
#[cfg_attr(feature = "compiled-path", parse(validate_function_name))]
name: Ident,
pub enum FunctionName {
#[cfg_attr(feature = "compiled-path", parse(peek = kw::length))]
Length(kw::length),
#[cfg_attr(feature = "compiled-path", parse(peek = kw::value))]
Value(kw::value),
#[cfg_attr(feature = "compiled-path", parse(peek = kw::count))]
Count(kw::count),
#[cfg_attr(feature = "compiled-path", parse(peek = kw::search))]
Search(kw::search),
#[cfg_attr(feature = "compiled-path", parse(peek = Token![match]))]
Match(Token![match]),
#[cfg_attr(feature = "compiled-path", parse(peek = Token![in]))]
In(Token![in]),
#[cfg_attr(feature = "compiled-path", parse(peek = kw::nin))]
Nin(kw::nin),
#[cfg_attr(feature = "compiled-path", parse(peek = kw::none_of))]
NoneOf(kw::none_of),
#[cfg_attr(feature = "compiled-path", parse(peek = kw::any_of))]
AnyOf(kw::any_of),
#[cfg_attr(feature = "compiled-path", parse(peek = kw::subset_of))]
SubsetOf(kw::subset_of),
}

impl<'pest> FromPest<'pest> for FunctionName {
Expand All @@ -666,12 +685,20 @@ impl<'pest> FromPest<'pest> for FunctionName {
let pair = clone.next().ok_or(ConversionError::NoMatch)?;
if matches!(
pair.as_rule(),
Rule::function_name_one_arg | Rule::function_name_two_arg
Rule::function_name
) {
let mut inner = pair.into_inner();
let inner = &mut inner;
let this = FunctionName {
name: Ident::new(inner.to_string().as_str().trim(), Span::call_site()),
let this = match pair.as_str().trim() {
"length" => Self::Length(Default::default()),
"value" => Self::Value(Default::default()),
"count" => Self::Count(Default::default()),
"search" => Self::Search(Default::default()),
"match" => Self::Match(Default::default()),
"in" => Self::In(Default::default()),
"nin" => Self::Nin(Default::default()),
"none_of" => Self::NoneOf(Default::default()),
"any_of" => Self::AnyOf(Default::default()),
"subset_of" => Self::SubsetOf(Default::default()),
_ => unreachable!("Invalid function name should be impossible, error in pest grammar")
};
*pest = clone;
Ok(this)
Expand Down
127 changes: 63 additions & 64 deletions jsonpath-ast/src/syn_parse.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
#[cfg(feature = "compiled-path")]
pub(crate) mod parse_impl {
use crate::ast::parse::{JSPathParser, Rule};
use crate::ast::{kw, CompOp, IndexSelector, Main, NameSelector};
use crate::ast::{
AbsSingularQuery, AtomExpr, Bool, BracketName, BracketedSelection, ChildSegment, CompExpr,
Comparable, DescendantSegment, EOI, FilterSelector, FunctionArgument, FunctionExpr,
FunctionName, IndexSegment, JPQuery, JSInt, JSString, Literal, LogicalExpr, LogicalExprAnd,
MemberNameShorthand, NameSegment, NotOp, Null, Number, ParenExpr, PestIgnoredPunctuated,
PestLiteralWithoutRule, RelQuery, RelSingularQuery, Root, Segment, Segments, Selector,
SingularQuery, SingularQuerySegment, SingularQuerySegments, SliceEnd, SliceSelector,
SliceStart, SliceStep, Test, TestExpr, WildcardSelector,
WildcardSelectorOrMemberNameShorthand,
Comparable, DescendantSegment, FilterSelector, FunctionArgument, FunctionExpr, FunctionName,
IndexSegment, JPQuery, JSInt, JSString, Literal, LogicalExpr, LogicalExprAnd, MemberNameShorthand,
NameSegment, NotOp, Null, Number, ParenExpr, PestIgnoredPunctuated, PestLiteralWithoutRule,
RelQuery, RelSingularQuery, Root, Segment, Segments, Selector, SingularQuery,
SingularQuerySegment, SingularQuerySegments, SliceEnd, SliceSelector, SliceStart,
SliceStep, Test, TestExpr, WildcardSelector, WildcardSelectorOrMemberNameShorthand,
EOI,
};
use crate::ast::{CompOp, IndexSelector, Main, NameSelector, kw};
use pest::Parser;
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, quote};
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Token;
use syn::{LitBool, LitInt, LitStr, Token, token};
use syn::{token, LitBool, LitInt, LitStr, Token};

pub trait ParseUtilsExt: Parse {
fn peek(input: ParseStream) -> bool;
Expand Down Expand Up @@ -629,11 +630,27 @@ pub(crate) mod parse_impl {

impl ToTokens for FunctionName {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(quote! {
::jsonpath_ast::ast::FunctionName::new(
::proc_macro2::Ident::new("function_name", ::proc_macro2::Span::call_site())
)
});
// tokens.extend(quote! {
// ::jsonpath_ast::ast::FunctionName::new(
// ::proc_macro2::Ident::new("function_name", ::proc_macro2::Span::call_site())
// )
// });
let variant = match self {
// Literal::Number(inner) => {
// quote!(new_number(#inner))
// }
FunctionName::Length(_) => { quote!(new_length(Default::default())) }
FunctionName::Value(_) => { quote!(new_value(Default::default())) }
FunctionName::Count(_) => { quote!(new_count(Default::default())) }
FunctionName::Search(_) => { quote!(new_search(Default::default())) }
FunctionName::Match(_) => { quote!(new_match(Default::default())) }
FunctionName::In(_) => { quote!(new_in(Default::default())) }
FunctionName::Nin(_) => { quote!(new_nin(Default::default())) }
FunctionName::NoneOf(_) => { quote!(new_none_of(Default::default())) }
FunctionName::AnyOf(_) => { quote!(new_any_of(Default::default())) }
FunctionName::SubsetOf(_) => { quote!(new_subset_of(Default::default())) }
};
tokens.extend(quote!(::jsonpath_ast::ast::FunctionName::#variant))
}
}

Expand Down Expand Up @@ -840,9 +857,13 @@ pub(crate) mod parse_impl {
impl ToTokens for TestExpr {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { not_op, test } = self;
let repr_not = match not_op {
Some(not_op) => quote! {Some(#not_op)},
None => quote! {None},
};
tokens.extend(quote! {
::jsonpath_ast::ast::TestExpr::new(
#not_op,
#repr_not,
#test
)
});
Expand Down Expand Up @@ -992,7 +1013,11 @@ pub(crate) mod parse_impl {

impl ParseUtilsExt for CompExpr {
fn peek(input: ParseStream) -> bool {
Comparable::peek(input)
let fork = input.fork();
// This is very suboptimal but the only option because at this point in the stream a comp_expr and a test_expr
// look identical if they're both functions, IE: $[?match(@, $.regex)] is a test_exp while $[?match(@, $.regex) == true]
// is a comp_exp
fork.parse::<Comparable>().is_ok() && fork.parse::<CompOp>().is_ok()
}
}
impl ParseUtilsExt for TestExpr {
Expand Down Expand Up @@ -1084,6 +1109,27 @@ pub(crate) mod parse_impl {
Ok(num)
}

fn function_name_expected_args(name: &FunctionName) -> (String, usize) {
(format!("{:?}", name), match name {
FunctionName::Length(_) | FunctionName::Value(_) | FunctionName::Count(_) => { 1 },
FunctionName::Search(_) | FunctionName::Match(_)
| FunctionName::In(_) | FunctionName::Nin(_)
| FunctionName::NoneOf(_) | FunctionName::AnyOf(_) | FunctionName::SubsetOf(_) => { 2 },
})
}
impl Parse for FunctionExpr {
fn parse(__input: ParseStream) -> ::syn::Result<Self> {
let paren;
let ret = Self { name: __input.parse()?, paren: syn::parenthesized!(paren in __input ), args: PestIgnoredPunctuated::parse_separated_nonempty(&paren)? };
let (func_name, expected_num_args) = function_name_expected_args(&ret.name);
if expected_num_args == ret.args.0.len() {
Ok(ret)
} else {
Err(syn::Error::new(ret.args.span(), format!("Invalid number of arguments for function {}, expected {}", func_name, expected_num_args)))
}
}
}

impl ParseUtilsExt for FunctionExpr {
fn peek(input: ParseStream) -> bool {
FunctionName::peek(input)
Expand All @@ -1105,53 +1151,6 @@ pub(crate) mod parse_impl {
}
}

pub fn validate_function_name(input: ParseStream) -> Result<Ident, syn::Error> {
if input.peek(kw::length) {
input.parse::<kw::length>()?;
return Ok(Ident::new("length", input.span()));
}
if input.peek(kw::value) {
input.parse::<kw::value>()?;
return Ok(Ident::new("value", input.span()));
}
if input.peek(kw::count) {
input.parse::<kw::count>()?;
return Ok(Ident::new("count", input.span()));
}
if input.peek(kw::search) {
input.parse::<kw::search>()?;
return Ok(Ident::new("search", input.span()));
}
if input.peek(Token![match]) {
input.parse::<Token![match]>()?;
return Ok(Ident::new("match", input.span()));
}
if input.peek(Token![in]) {
input.parse::<Token![in]>()?;
return Ok(Ident::new("in", input.span()));
}
if input.peek(kw::nin) {
input.parse::<kw::nin>()?;
return Ok(Ident::new("nin", input.span()));
}
if input.peek(kw::none_of) {
input.parse::<kw::none_of>()?;
return Ok(Ident::new("none_of", input.span()));
}
if input.peek(kw::any_of) {
input.parse::<kw::any_of>()?;
return Ok(Ident::new("any_of", input.span()));
}
if input.peek(kw::subset_of) {
input.parse::<kw::subset_of>()?;
return Ok(Ident::new("subset_of", input.span()));
}
Err(syn::Error::new(
input.span(),
"invalid function name, expected one of: length, value, count, search, match, in, nin, none_of, any_of, subset_of",
))
}

impl ParseUtilsExt for RelQuery {
fn peek(input: ParseStream) -> bool {
input.peek(Token![@])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Test case: 00_count_function
// Tags: function, count
#[test]
fn test_00_count_function() {
let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@..*)>2]);
let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@..*)>2]"#).expect("failed to parse");
assert_eq!(q_pest, q_ast);
}

// Test case: 01_single_node_arg
// Tags: function, count
#[test]
fn test_01_single_node_arg() {
let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@.a)>1]);
let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@.a)>1]"#).expect("failed to parse");
assert_eq!(q_pest, q_ast);
}

// Test case: 02_multiple_selector_arg
// Tags: function, count
#[test]
fn test_02_multiple_selector_arg() {
let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@["a","d"])>1]);
let q_pest_double = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@["a","d"])>1]"#).expect("failed to parse");
let q_pest_single = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@['a','d'])>1]"#).expect("failed to parse");
assert_eq!(q_pest_double, q_ast);
assert_eq!(q_pest_single, q_ast);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Test case: 03_non_query_arg_number
// Tags: function, count
#[test]
fn test_03_non_query_arg_number() {
// let q_ast = ::jsonpath_rust_impl::json_query!($[?count(1)>2]);
let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(1)>2]"#).expect_err("should not parse");
}

// Test case: 04_non_query_arg_string
// Tags: function, count
#[test]
fn test_04_non_query_arg_string() {
// let q_ast = ::jsonpath_rust_impl::json_query!($[?count('string')>2]);
let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count('string')>2]"#).expect_err("should not parse");
}

// Test case: 05_non_query_arg_true
// Tags: function, count
#[test]
fn test_05_non_query_arg_true() {
// let q_ast = ::jsonpath_rust_impl::json_query!($[?count(true)>2]);
let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(true)>2]"#).expect_err("should not parse");
}

// Test case: 06_non_query_arg_false
// Tags: function, count
#[test]
fn test_06_non_query_arg_false() {
// let q_ast = ::jsonpath_rust_impl::json_query!($[?count(false)>2]);
let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(false)>2]"#).expect_err("should not parse");
}

// Test case: 07_non_query_arg_null
// Tags: function, count
#[test]
fn test_07_non_query_arg_null() {
// let q_ast = ::jsonpath_rust_impl::json_query!($[?count(null)>2]);
let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(null)>2]"#).expect_err("should not parse");
}

// Test case: 08_result_must_be_compared
// Tags: function, count
#[test]
fn test_08_result_must_be_compared() {
// let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@..*)]);
let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@..*)]"#).expect_err("should not parse");
}

// Test case: 09_no_params
// Tags: function, count
#[test]
fn test_09_no_params() {
// let q_ast = ::jsonpath_rust_impl::json_query!($[?count()==1]);
let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count()==1]"#).expect_err("should not parse");
}

// Test case: 10_too_many_params
// Tags: function, count
#[test]
fn test_10_too_many_params() {
// let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@.a,@.b)==1]);
let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@.a,@.b)==1]"#).expect_err("should not parse");
}
Loading
Loading