зеркало из https://github.com/mozilla/jexl-rs.git
Adds filtering collections
This commit is contained in:
Родитель
b9b1fe6805
Коммит
848181fab7
|
@ -29,6 +29,8 @@ pub enum EvaluationError<'a> {
|
||||||
JSONError(#[from] serde_json::Error),
|
JSONError(#[from] serde_json::Error),
|
||||||
#[error("Custom error: {0}")]
|
#[error("Custom error: {0}")]
|
||||||
CustomError(#[from] anyhow::Error),
|
CustomError(#[from] anyhow::Error),
|
||||||
|
#[error("Invalid filter")]
|
||||||
|
InvalidFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<ParseError<usize, Token<'a>, &'a str>> for EvaluationError<'a> {
|
impl<'a> From<ParseError<usize, Token<'a>, &'a str>> for EvaluationError<'a> {
|
||||||
|
|
|
@ -177,6 +177,22 @@ impl<'a> Evaluator<'a> {
|
||||||
|
|
||||||
Expression::IndexOperation { subject, index } => {
|
Expression::IndexOperation { subject, index } => {
|
||||||
let subject = self.eval_ast(*subject, context)?;
|
let subject = self.eval_ast(*subject, context)?;
|
||||||
|
if let Expression::Filter { ident, op, right } = *index {
|
||||||
|
let subject_arr = subject.as_array().ok_or(EvaluationError::InvalidFilter)?;
|
||||||
|
let right = self.eval_ast(*right, context)?;
|
||||||
|
let filtered = subject_arr
|
||||||
|
.iter()
|
||||||
|
.filter(|e| {
|
||||||
|
let left = e.get(&ident).unwrap_or(&value!(null));
|
||||||
|
// returns false if any members fail the op, could happen if array members are missing the identifier
|
||||||
|
Self::apply_op(op, left.clone(), right.clone())
|
||||||
|
.unwrap_or(value!(false))
|
||||||
|
.is_truthy()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
return Ok(value!(filtered));
|
||||||
|
}
|
||||||
|
|
||||||
let index = self.eval_ast(*index, context)?;
|
let index = self.eval_ast(*index, context)?;
|
||||||
match index {
|
match index {
|
||||||
Value::String(inner) => {
|
Value::String(inner) => {
|
||||||
|
@ -197,46 +213,7 @@ impl<'a> Evaluator<'a> {
|
||||||
} => {
|
} => {
|
||||||
let left = self.eval_ast(*left, context)?;
|
let left = self.eval_ast(*left, context)?;
|
||||||
let right = self.eval_ast(*right, context)?;
|
let right = self.eval_ast(*right, context)?;
|
||||||
match (operation, left, right) {
|
Self::apply_op(operation, left, right)
|
||||||
(OpCode::And, a, b) => Ok(if a.is_truthy() { b } else { a }),
|
|
||||||
(OpCode::Or, a, b) => Ok(if a.is_truthy() { a } else { b }),
|
|
||||||
|
|
||||||
(op, Value::Number(a), Value::Number(b)) => {
|
|
||||||
let left = a.as_f64().unwrap();
|
|
||||||
let right = b.as_f64().unwrap();
|
|
||||||
Ok(match op {
|
|
||||||
OpCode::Add => value!(left + right),
|
|
||||||
OpCode::Subtract => value!(left - right),
|
|
||||||
OpCode::Multiply => value!(left * right),
|
|
||||||
OpCode::Divide => value!(left / right),
|
|
||||||
OpCode::FloorDivide => value!((left / right).floor()),
|
|
||||||
OpCode::Modulus => value!(left % right),
|
|
||||||
OpCode::Exponent => value!(left.powf(right)),
|
|
||||||
OpCode::Less => value!(left < right),
|
|
||||||
OpCode::Greater => value!(left > right),
|
|
||||||
OpCode::LessEqual => value!(left <= right),
|
|
||||||
OpCode::GreaterEqual => value!(left >= right),
|
|
||||||
OpCode::Equal => value!((left - right).abs() < EPSILON),
|
|
||||||
OpCode::NotEqual => value!((left - right).abs() > EPSILON),
|
|
||||||
OpCode::In => value!(false),
|
|
||||||
OpCode::And | OpCode::Or => {
|
|
||||||
unreachable!("Covered by previous case in parent match")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
(OpCode::Add, Value::String(a), Value::String(b)) => {
|
|
||||||
Ok(value!(format!("{}{}", a, b)))
|
|
||||||
}
|
|
||||||
(OpCode::In, Value::String(a), Value::String(b)) => Ok(value!(b.contains(&a))),
|
|
||||||
(OpCode::In, left, Value::Array(v)) => Ok(value!(v.contains(&left))),
|
|
||||||
(OpCode::Equal, Value::String(a), Value::String(b)) => Ok(value!(a == b)),
|
|
||||||
(operation, left, right) => Err(EvaluationError::InvalidBinaryOp {
|
|
||||||
operation,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Expression::Transform {
|
Expression::Transform {
|
||||||
name,
|
name,
|
||||||
|
@ -269,6 +246,57 @@ impl<'a> Evaluator<'a> {
|
||||||
self.eval_ast(*falsy, context)
|
self.eval_ast(*falsy, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression::Filter {
|
||||||
|
ident: _,
|
||||||
|
op: _,
|
||||||
|
right: _,
|
||||||
|
} => {
|
||||||
|
// Filters shouldn't be evaluated individually
|
||||||
|
// instead, they are evaluated as a part of an IndexOperation
|
||||||
|
return Err(EvaluationError::InvalidFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_op<'b>(operation: OpCode, left: Value, right: Value) -> Result<'b, Value> {
|
||||||
|
match (operation, left, right) {
|
||||||
|
(OpCode::And, a, b) => Ok(if a.is_truthy() { b } else { a }),
|
||||||
|
(OpCode::Or, a, b) => Ok(if a.is_truthy() { a } else { b }),
|
||||||
|
|
||||||
|
(op, Value::Number(a), Value::Number(b)) => {
|
||||||
|
let left = a.as_f64().unwrap();
|
||||||
|
let right = b.as_f64().unwrap();
|
||||||
|
Ok(match op {
|
||||||
|
OpCode::Add => value!(left + right),
|
||||||
|
OpCode::Subtract => value!(left - right),
|
||||||
|
OpCode::Multiply => value!(left * right),
|
||||||
|
OpCode::Divide => value!(left / right),
|
||||||
|
OpCode::FloorDivide => value!((left / right).floor()),
|
||||||
|
OpCode::Modulus => value!(left % right),
|
||||||
|
OpCode::Exponent => value!(left.powf(right)),
|
||||||
|
OpCode::Less => value!(left < right),
|
||||||
|
OpCode::Greater => value!(left > right),
|
||||||
|
OpCode::LessEqual => value!(left <= right),
|
||||||
|
OpCode::GreaterEqual => value!(left >= right),
|
||||||
|
OpCode::Equal => value!((left - right).abs() < EPSILON),
|
||||||
|
OpCode::NotEqual => value!((left - right).abs() > EPSILON),
|
||||||
|
OpCode::In => value!(false),
|
||||||
|
OpCode::And | OpCode::Or => {
|
||||||
|
unreachable!("Covered by previous case in parent match")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
(OpCode::Add, Value::String(a), Value::String(b)) => Ok(value!(format!("{}{}", a, b))),
|
||||||
|
(OpCode::In, Value::String(a), Value::String(b)) => Ok(value!(b.contains(&a))),
|
||||||
|
(OpCode::In, left, Value::Array(v)) => Ok(value!(v.contains(&left))),
|
||||||
|
(OpCode::Equal, Value::String(a), Value::String(b)) => Ok(value!(a == b)),
|
||||||
|
(operation, left, right) => Err(EvaluationError::InvalidBinaryOp {
|
||||||
|
operation,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -350,7 +378,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
|
||||||
fn test_context_filter_arrays() {
|
fn test_context_filter_arrays() {
|
||||||
let context = value!({
|
let context = value!({
|
||||||
"foo": {
|
"foo": {
|
||||||
|
@ -606,4 +633,22 @@ mod tests {
|
||||||
panic!("Should have returned a Custom error!")
|
panic!("Should have returned a Custom error!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_collections_many_returned() {
|
||||||
|
let evaluator = Evaluator::new();
|
||||||
|
let context = value!({
|
||||||
|
"foo": [
|
||||||
|
{"bobo": 50, "fofo": 100},
|
||||||
|
{"bobo": 60, "baz": 90},
|
||||||
|
{"bobo": 10, "bar": 83},
|
||||||
|
{"bobo": 20, "yam": 12},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
let exp = "foo[.bobo >= 50]";
|
||||||
|
assert_eq!(
|
||||||
|
evaluator.eval_in_context(exp, context).unwrap(),
|
||||||
|
value!([{"bobo": 50, "fofo": 100}, {"bobo": 60, "baz": 90}])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,12 @@ pub enum Expression {
|
||||||
truthy: Box<Expression>,
|
truthy: Box<Expression>,
|
||||||
falsy: Box<Expression>,
|
falsy: Box<Expression>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Filter {
|
||||||
|
ident: String,
|
||||||
|
op: OpCode,
|
||||||
|
right: Box<Expression>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
|
|
@ -132,7 +132,8 @@ Identifier: String = {
|
||||||
}
|
}
|
||||||
|
|
||||||
Index: Box<Expression> = {
|
Index: Box<Expression> = {
|
||||||
"[" <Expression> "]"
|
"[" "." <ident: Identifier> <op: Op20> <right: Expr80> "]" => Box::new(Expression::Filter {ident, op, right}),
|
||||||
|
"[" <Expression> "]",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче