mirror of
https://github.com/fluencelabs/jsonpath
synced 2025-03-15 06:50:50 +00:00
First commit of filter 'in' operator
This commit is contained in:
parent
6f20013076
commit
5402da7da9
@ -64,6 +64,7 @@ pub enum FilterToken {
|
||||
LittleOrEqual,
|
||||
Greater,
|
||||
GreaterOrEqual,
|
||||
In,
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
@ -487,6 +488,9 @@ impl Parser {
|
||||
| Ok(Token::LittleOrEqual(_))
|
||||
| Ok(Token::Greater(_))
|
||||
| Ok(Token::GreaterOrEqual(_)) => true,
|
||||
Ok(Token::Key(_, key)) => {
|
||||
Self::get_filter_token(key).is_some()
|
||||
}
|
||||
_ => false,
|
||||
} {
|
||||
Self::op(node, tokenizer)
|
||||
@ -527,6 +531,41 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
fn term_array(tokenizer: &mut TokenReader) -> ParseResult<Node> {
|
||||
debug!("#term_array");
|
||||
|
||||
Self::eat_token(tokenizer);
|
||||
Self::eat_whitespace(tokenizer);
|
||||
|
||||
let mut keys = vec![];
|
||||
loop {
|
||||
match tokenizer.next_token() {
|
||||
Ok(Token::SingleQuoted(_, val)) | Ok(Token::DoubleQuoted(_, val)) => {
|
||||
keys.push(val);
|
||||
}
|
||||
Ok(Token::Key(_, val)) => {
|
||||
keys.push(val);
|
||||
}
|
||||
_ => return Err(tokenizer.err_msg()),
|
||||
}
|
||||
|
||||
Self::eat_whitespace(tokenizer);
|
||||
|
||||
match tokenizer.peek_token() {
|
||||
Ok(Token::Comma(_)) => {
|
||||
Self::eat_token(tokenizer);
|
||||
Self::eat_whitespace(tokenizer);
|
||||
}
|
||||
Ok(Token::CloseArray(_)) => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Self::eat_whitespace(tokenizer);
|
||||
|
||||
Self::close_token(Self::node(ParseToken::Keys(keys)), Token::CloseArray(DUMMY), tokenizer)
|
||||
}
|
||||
|
||||
fn term(tokenizer: &mut TokenReader) -> ParseResult<Node> {
|
||||
debug!("#term");
|
||||
|
||||
@ -545,16 +584,19 @@ impl Parser {
|
||||
}
|
||||
Ok(Token::Absolute(_)) => {
|
||||
Self::json_path(tokenizer)
|
||||
},
|
||||
}
|
||||
Ok(Token::DoubleQuoted(_, _)) | Ok(Token::SingleQuoted(_, _)) => {
|
||||
Self::array_quote_value(tokenizer)
|
||||
},
|
||||
}
|
||||
Ok(Token::Key(_, key)) => {
|
||||
match key.as_bytes()[0] {
|
||||
b'-' | b'0'..=b'9' => Self::term_num(tokenizer),
|
||||
_ => Self::boolean(tokenizer),
|
||||
}
|
||||
}
|
||||
Ok(Token::OpenArray(_)) => {
|
||||
Self::term_array(tokenizer)
|
||||
}
|
||||
_ => {
|
||||
Err(tokenizer.err_msg())
|
||||
}
|
||||
@ -570,6 +612,10 @@ impl Parser {
|
||||
Ok(Token::LittleOrEqual(_)) => ParseToken::Filter(FilterToken::LittleOrEqual),
|
||||
Ok(Token::Greater(_)) => ParseToken::Filter(FilterToken::Greater),
|
||||
Ok(Token::GreaterOrEqual(_)) => ParseToken::Filter(FilterToken::GreaterOrEqual),
|
||||
Ok(Token::Key(_, key)) => match Self::get_filter_token(&key) {
|
||||
Some(filter_token) => ParseToken::Filter(filter_token),
|
||||
_ => return Err(tokenizer.err_msg())
|
||||
}
|
||||
_ => {
|
||||
return Err(tokenizer.err_msg());
|
||||
}
|
||||
@ -609,10 +655,19 @@ impl Parser {
|
||||
_ => Err(tokenizer.err_msg()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_filter_token(op_candidate: &str) -> Option<FilterToken> {
|
||||
match op_candidate {
|
||||
"in" | "In" | "iN" | "IN" => Some(FilterToken::In),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NodeVisitor {
|
||||
fn visit(&mut self, node: &Node) {
|
||||
fn visit(&mut self, node: &Node, indent: usize, prefix: &str) {
|
||||
debug!("{:indent$}{} {:?}", "", prefix, node.token, indent = indent);
|
||||
|
||||
match &node.token {
|
||||
ParseToken::Absolute
|
||||
| ParseToken::Relative
|
||||
@ -627,48 +682,48 @@ pub trait NodeVisitor {
|
||||
}
|
||||
ParseToken::In | ParseToken::Leaves => {
|
||||
if let Some(n) = &node.left {
|
||||
self.visit(&*n);
|
||||
self.visit(&*n, indent + 1, "1.LEFT");
|
||||
}
|
||||
|
||||
self.visit_token(&node.token);
|
||||
|
||||
if let Some(n) = &node.right {
|
||||
self.visit(&*n);
|
||||
self.visit(&*n, indent + 1, "1.RIGHT");
|
||||
}
|
||||
}
|
||||
ParseToken::Array => {
|
||||
if let Some(n) = &node.left {
|
||||
self.visit(&*n);
|
||||
self.visit(&*n, indent + 1, "2.LEFT");
|
||||
}
|
||||
|
||||
self.visit_token(&node.token);
|
||||
|
||||
if let Some(n) = &node.right {
|
||||
self.visit(&*n);
|
||||
self.visit(&*n, indent + 1, "2.RIGHT");
|
||||
}
|
||||
|
||||
self.visit_token(&ParseToken::ArrayEof);
|
||||
}
|
||||
ParseToken::Filter(FilterToken::And) | ParseToken::Filter(FilterToken::Or) => {
|
||||
if let Some(n) = &node.left {
|
||||
self.visit(&*n);
|
||||
self.visit(&*n, indent + 1, "3.LEFT");
|
||||
}
|
||||
|
||||
if let Some(n) = &node.right {
|
||||
self.visit(&*n);
|
||||
self.visit(&*n, indent + 1, "3.RIGTH");
|
||||
}
|
||||
|
||||
self.visit_token(&node.token);
|
||||
}
|
||||
ParseToken::Filter(_) => {
|
||||
if let Some(n) = &node.left {
|
||||
self.visit(&*n);
|
||||
self.visit(&*n, indent + 1, "4.LEFT");
|
||||
}
|
||||
|
||||
self.end_term();
|
||||
|
||||
if let Some(n) = &node.right {
|
||||
self.visit(&*n);
|
||||
self.visit(&*n, indent + 1, "4.RIGTH");
|
||||
}
|
||||
|
||||
self.end_term();
|
||||
@ -702,7 +757,7 @@ mod parser_tests {
|
||||
|
||||
fn start(&mut self) -> Result<Vec<ParseToken>, String> {
|
||||
let node = Parser::compile(self.input)?;
|
||||
self.visit(&node);
|
||||
self.visit(&node, 0, "-");
|
||||
Ok(self.stack.split_off(0))
|
||||
}
|
||||
}
|
||||
|
@ -371,11 +371,11 @@ impl<'a> TokenReader<'a> {
|
||||
pub fn peek_token(&self) -> Result<&Token, TokenError> {
|
||||
match self.tokens.last() {
|
||||
Some((_, t)) => {
|
||||
trace!("%{:?}", t);
|
||||
// trace!("[PEEK].{:?}", t);
|
||||
Ok(t)
|
||||
}
|
||||
_ => {
|
||||
trace!("%{:?}", self.err);
|
||||
// trace!("[PEEK]!{:?}", self.err);
|
||||
Err(self.err.clone())
|
||||
}
|
||||
}
|
||||
@ -385,11 +385,11 @@ impl<'a> TokenReader<'a> {
|
||||
match self.tokens.pop() {
|
||||
Some((pos, t)) => {
|
||||
self.curr_pos = Some(pos);
|
||||
trace!("@{:?}", t);
|
||||
trace!("[NEXT].{:?}", t);
|
||||
Ok(t)
|
||||
}
|
||||
_ => {
|
||||
trace!("@{:?}", self.err);
|
||||
trace!("[NEXT]!{:?}", self.err);
|
||||
Err(self.err.clone())
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
use array_tool::vec::{Intersect, Union};
|
||||
use serde_json::map::Entry;
|
||||
use serde_json::{Number, Value};
|
||||
use serde_json::map::Entry;
|
||||
|
||||
use parser::*;
|
||||
|
||||
@ -31,6 +31,14 @@ trait Cmp {
|
||||
}
|
||||
}
|
||||
|
||||
trait CmpRight {
|
||||
fn cmp<'a>(&self, v1: &[&'a Value], v2: &[String]) -> Vec<&'a Value>;
|
||||
|
||||
fn default(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
struct CmpEq;
|
||||
|
||||
impl Cmp for CmpEq {
|
||||
@ -191,11 +199,40 @@ impl Cmp for CmpOr {
|
||||
}
|
||||
}
|
||||
|
||||
struct CmpIn;
|
||||
|
||||
impl CmpRight for CmpIn {
|
||||
fn cmp<'a>(&self, v1: &[&'a Value], v2: &[String]) -> Vec<&'a Value> {
|
||||
v1.iter()
|
||||
.filter(|v| match v {
|
||||
Value::Object(map) => {
|
||||
for value in map.values() {
|
||||
if match value {
|
||||
Value::String(s) => v2.contains(&s),
|
||||
_ => false
|
||||
} {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
Value::String(s) => {
|
||||
v2.contains(s)
|
||||
}
|
||||
_ => false
|
||||
})
|
||||
.map(|v| *v)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ExprTerm<'a> {
|
||||
String(String),
|
||||
Number(Number),
|
||||
Bool(bool),
|
||||
Array(Vec<String>),
|
||||
Json(Option<Vec<&'a Value>>, Option<FilterKey>, Vec<&'a Value>),
|
||||
}
|
||||
|
||||
@ -222,6 +259,9 @@ impl<'a> ExprTerm<'a> {
|
||||
ExprTerm::Json(_, _, _) => other.cmp(&self, reverse_cmp_fn, cmp_fn),
|
||||
_ => ExprTerm::Bool(cmp_fn.default()),
|
||||
},
|
||||
ExprTerm::Array(_) => {
|
||||
unreachable!("#ExprTerm::Array, - unreachable!!");
|
||||
}
|
||||
ExprTerm::Json(rel, fk1, vec1) => {
|
||||
let ret: Vec<&Value> = match &other {
|
||||
ExprTerm::String(s2) => vec1
|
||||
@ -272,6 +312,9 @@ impl<'a> ExprTerm<'a> {
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
ExprTerm::Array(_) => {
|
||||
unreachable!("#ExprTerm::Json, ExprTerm::Array unreachable!!");
|
||||
}
|
||||
ExprTerm::Json(parent, _, vec2) => {
|
||||
if let Some(vec1) = rel {
|
||||
cmp_fn.cmp_json(vec1, vec2)
|
||||
@ -294,6 +337,23 @@ impl<'a> ExprTerm<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp_right<C: CmpRight>(&self, other: &Self, cmp_fn: &C) -> ExprTerm<'a> {
|
||||
match &self {
|
||||
ExprTerm::Json(rel, _, vec) => {
|
||||
if let ExprTerm::Array(keys) = &other {
|
||||
if let Some(parent) = rel {
|
||||
ExprTerm::Json(Some(parent.to_vec()), None, cmp_fn.cmp(&vec, keys))
|
||||
} else {
|
||||
ExprTerm::Json(None, None, cmp_fn.cmp(&vec, keys))
|
||||
}
|
||||
} else {
|
||||
unreachable!("#cmp_right")
|
||||
}
|
||||
}
|
||||
_ => ExprTerm::Bool(cmp_fn.default())
|
||||
}
|
||||
}
|
||||
|
||||
fn eq(&self, other: &Self, ret: &mut Option<ExprTerm<'a>>) {
|
||||
debug!("eq - {:?} : {:?}", &self, &other);
|
||||
let _ = ret.take();
|
||||
@ -342,6 +402,14 @@ impl<'a> ExprTerm<'a> {
|
||||
*ret = Some(tmp);
|
||||
}
|
||||
|
||||
fn inn(&self, other: &Self, ret: &mut Option<ExprTerm<'a>>) {
|
||||
debug!("in - {:?} : {:?}", &self, &other);
|
||||
let _ = ret.take();
|
||||
let tmp = self.cmp_right(other, &CmpIn);
|
||||
debug!("in = {:?}", tmp);
|
||||
*ret = Some(tmp);
|
||||
}
|
||||
|
||||
fn and(&self, other: &Self, ret: &mut Option<ExprTerm<'a>>) {
|
||||
debug!("and - {:?} : {:?}", &self, &other);
|
||||
let _ = ret.take();
|
||||
@ -529,7 +597,7 @@ impl<'a, 'b> Selector<'a, 'b> {
|
||||
fn _select(&mut self) -> Result<(), JsonPathError> {
|
||||
if self.node_ref.is_some() {
|
||||
let node_ref = self.node_ref.take().unwrap();
|
||||
self.visit(node_ref);
|
||||
self.visit(node_ref, 0, "-");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -538,7 +606,7 @@ impl<'a, 'b> Selector<'a, 'b> {
|
||||
}
|
||||
|
||||
let node = self.node.take().unwrap();
|
||||
self.visit(&node);
|
||||
self.visit(&node, 0, "-");
|
||||
self.node = Some(node);
|
||||
|
||||
Ok(())
|
||||
@ -954,7 +1022,9 @@ impl<'a, 'b> Selector<'a, 'b> {
|
||||
|
||||
fn visit_keys(&mut self, keys: &[String]) {
|
||||
if !self.terms.is_empty() {
|
||||
unimplemented!("keys in filter");
|
||||
debug!("\t - array in key {:?}", self.tokens);
|
||||
self.terms.push(Some(ExprTerm::Array(keys.to_vec())));
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ParseToken::Array) = self.tokens.pop() {
|
||||
@ -964,7 +1034,7 @@ impl<'a, 'b> Selector<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_filter(&mut self, ft: &FilterToken) {
|
||||
fn visit_filter(&mut self, filter_token: &FilterToken) {
|
||||
let right = match self.terms.pop() {
|
||||
Some(Some(right)) => right,
|
||||
Some(None) => ExprTerm::Json(
|
||||
@ -992,13 +1062,14 @@ impl<'a, 'b> Selector<'a, 'b> {
|
||||
};
|
||||
|
||||
let mut ret = None;
|
||||
match ft {
|
||||
match filter_token {
|
||||
FilterToken::Equal => left.eq(&right, &mut ret),
|
||||
FilterToken::NotEqual => left.ne(&right, &mut ret),
|
||||
FilterToken::Greater => left.gt(&right, &mut ret),
|
||||
FilterToken::GreaterOrEqual => left.ge(&right, &mut ret),
|
||||
FilterToken::Little => left.lt(&right, &mut ret),
|
||||
FilterToken::LittleOrEqual => left.le(&right, &mut ret),
|
||||
FilterToken::In => left.inn(&right, &mut ret),
|
||||
FilterToken::And => left.and(&right, &mut ret),
|
||||
FilterToken::Or => left.or(&right, &mut ret),
|
||||
};
|
||||
@ -1095,8 +1166,7 @@ impl<'a, 'b> NodeVisitor for Selector<'a, 'b> {
|
||||
ParseToken::Key(key) => self.visit_key(key),
|
||||
ParseToken::Keys(keys) => self.visit_keys(keys),
|
||||
ParseToken::Number(v) => {
|
||||
self.terms
|
||||
.push(Some(ExprTerm::Number(Number::from_f64(*v).unwrap())));
|
||||
self.terms.push(Some(ExprTerm::Number(Number::from_f64(*v).unwrap())));
|
||||
}
|
||||
ParseToken::Filter(ref ft) => self.visit_filter(ft),
|
||||
ParseToken::Range(from, to, step) => self.visit_range(from, to, step),
|
||||
|
34
tests/extended_filter.rs
Normal file
34
tests/extended_filter.rs
Normal file
@ -0,0 +1,34 @@
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
use common::{select_and_then_compare, setup};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn extended_filter_in() {
|
||||
setup();
|
||||
|
||||
select_and_then_compare(
|
||||
"$..[?(@.size in ['M', 'L', 0])]",
|
||||
json!({
|
||||
"red" : {
|
||||
"size": "M"
|
||||
},
|
||||
"blue" : {
|
||||
"size" : "L"
|
||||
},
|
||||
"yellow" : {
|
||||
"size" : "XL"
|
||||
}
|
||||
}),
|
||||
json!([
|
||||
{
|
||||
"size" : "M"
|
||||
},
|
||||
{
|
||||
"size" : "L"
|
||||
}
|
||||
]),
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user